digitalpardoe-rflickr 1.0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,22 @@
1
+ To use this version of rFlickr, first you will need to install
2
+ the RubyGem you can do by (from a command line):
3
+
4
+ $ gem sources -a http://gems.github.com (you only have to do this once)
5
+ $ sudo gem install digitalpardoe-rflickr
6
+
7
+ Then you'll need to get a Flickr API key as detailed here:
8
+
9
+ http://www.flickr.com/services/api/misc.api_keys.html
10
+
11
+ You'll need to authorize your application's API key from each
12
+ Flickr user account which wants to use it.
13
+
14
+ A short guide to get you started can be found here:
15
+
16
+ http://digitalpardoe.co.uk/blog/show/87
17
+
18
+ The guide is a little out of date in it's gem installation technique,
19
+ use the instructions above instead.
20
+
21
+ This project is still a work in progress, bear with me whilst it is
22
+ set up.
data/lib/flickr.rb ADDED
@@ -0,0 +1,39 @@
1
+ # rFlickr: A Ruby based Flickr API implementation.
2
+ # Copyright (C) 2009, Alex Pardoe (digital:pardoe)
3
+ #
4
+ # Derrived from work by Trevor Schroeder, see here:
5
+ # http://rubyforge.org/projects/rflickr/.
6
+ #
7
+ # This program is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU General Public License
9
+ # as published by the Free Software Foundation; either version 2
10
+ # of the License, or (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program; if not, write to the Free Software
19
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
+
21
+ require 'flickr/auth'
22
+ require 'flickr/base'
23
+ require 'flickr/blogs'
24
+ require 'flickr/contacts'
25
+ require 'flickr/favorites'
26
+ require 'flickr/groups'
27
+ require 'flickr/licenses'
28
+ require 'flickr/notes'
29
+ require 'flickr/people'
30
+ require 'flickr/photos'
31
+ require 'flickr/photosets'
32
+ require 'flickr/pools'
33
+ require 'flickr/reflection'
34
+ require 'flickr/test'
35
+ require 'flickr/transform'
36
+ require 'flickr/upload'
37
+ require 'flickr/urls'
38
+ require 'flickr/tags'
39
+ require 'flickr/interestingness'
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # rFlickr: A Ruby based Flickr API implementation.
4
+ # Copyright (C) 2009, Alex Pardoe (digital:pardoe)
5
+ #
6
+ # Derrived from work by Trevor Schroeder, see here:
7
+ # http://rubyforge.org/projects/rflickr/.
8
+ #
9
+ # This program is free software; you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License
11
+ # as published by the Free Software Foundation; either version 2
12
+ # of the License, or (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program; if not, write to the Free Software
21
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
+
23
+ require 'flickr/base'
24
+
25
+ class Flickr::Auth < Flickr::APIBase
26
+ attr_accessor :cache_file, :token
27
+
28
+ def clear_cache
29
+ @token = nil
30
+ @frob = nil
31
+ end
32
+
33
+ def initialize(flickr,cache_file=nil)
34
+ super(flickr)
35
+ @frob = nil
36
+ @token = nil
37
+ @cache_file = cache_file
38
+ if @cache_file && File.exists?(@cache_file)
39
+ load_token
40
+ end
41
+ end
42
+
43
+ def login_link(perms='delete')
44
+ args={ 'api_key' => @flickr.api_key, 'perms' => perms}
45
+ args['frob'] = self.frob
46
+ args['api_sig'] = @flickr.sign(args)
47
+ return "http://flickr.com/services/auth/?"+
48
+ args.to_a.map{|arr| arr.join('=')}.join('&')
49
+ end
50
+
51
+ def frob=(frob) @frob = frob end
52
+ def frob() return @frob || getFrob end
53
+
54
+ def cache_token
55
+ File.open(@cache_file,'w'){ |f| f.write @token.to_xml } if token
56
+ end
57
+
58
+ def load_token
59
+ token = nil
60
+ File.open(@cache_file,'r'){ |f| token = f.read }
61
+ # Dirt stupid check to see if it's probably XML or
62
+ # not. If it is, then we don't call checkToken.
63
+ #
64
+ # Backwwards compatible with old token storage.
65
+ @token = token.include?('<') ?
66
+ Flickr::Token.from_xml(REXML::Document.new(token)) :
67
+ @token = checkToken(token)
68
+ end
69
+
70
+ def getToken(frob=nil)
71
+ frob ||= @frob
72
+ res=@flickr.call_unauth_method('flickr.auth.getToken',
73
+ 'frob'=>frob)
74
+ @token = Flickr::Token.from_xml(res)
75
+ end
76
+
77
+ def getFullToken(mini_token)
78
+ res = flickr.call_unauth_method('flickr.auth.getFullToken',
79
+ 'mini_token' => mini_token)
80
+ @token = Flickr::Token.from_xml(res)
81
+ end
82
+
83
+ def getFrob
84
+ doc = @flickr.call_unauth_method('flickr.auth.getFrob')
85
+ @frob = doc.elements['/frob'].text
86
+ return @frob
87
+ end
88
+
89
+ def checkToken(token=nil)
90
+ token ||= @token
91
+ token = token.token if token.class == Flickr::Token
92
+ res = @flickr.call_unauth_method('flickr.auth.checkToken',
93
+ 'auth_token' => token)
94
+ @token = Flickr::Token.from_xml(res)
95
+ end
96
+ end
@@ -0,0 +1,813 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # rFlickr: A Ruby based Flickr API implementation.
4
+ # Copyright (C) 2009, Alex Pardoe (digital:pardoe)
5
+ #
6
+ # Derrived from work by Trevor Schroeder, see here:
7
+ # http://rubyforge.org/projects/rflickr/.
8
+ #
9
+ # This program is free software; you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License
11
+ # as published by the Free Software Foundation; either version 2
12
+ # of the License, or (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program; if not, write to the Free Software
21
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
+
23
+ ## Structure this class hierarchy the way the Flickr API is structured.
24
+ ## Flickr::Auth and so on. At least think about whether it makes sense or
25
+ ## not.
26
+
27
+ require 'xmlrpc/client'
28
+ require 'md5'
29
+ require 'rexml/document'
30
+ require 'parsedate'
31
+
32
+ class Flickr
33
+ API_KEY=''
34
+ SHARED_SECRET=''
35
+
36
+ attr_reader :api_key
37
+ attr_accessor :async, :debug, :caching, :auth_mode
38
+
39
+ ############################### CACHE ACCESSORS ###########################
40
+ def ticket_cache_lookup(id) @ticket_by_id[id] if @caching end
41
+
42
+ def ticket_cache_store(ticket)
43
+ @ticket_by_id[ticket.id] = ticket if @caching
44
+ end
45
+
46
+ def person_cache_lookup(nsid) @person_by_nsid[nsid] if @caching end
47
+
48
+ def person_cache_store(person)
49
+ @person_by_nsid[person.nsid] = person if @caching
50
+ end
51
+
52
+ def photo_cache_lookup(id) @photo_by_id[id] if @caching end
53
+
54
+ def photo_cache_store(photo)
55
+ @photo_by_id[photo.id] = photo if @caching
56
+ end
57
+
58
+ def license_cache_lookup() @license_cache if @caching end
59
+
60
+ def license_cache_store(licenses)
61
+ @license_cache = licenses if @caching
62
+ end
63
+
64
+ def blog_cache_lookup() @blog_cache if @caching end
65
+
66
+ def blog_cache_store(blogs) @blog_cache = blogs if @caching end
67
+
68
+ def photoset_cache_lookup(id) @photoset_by_id[id] if @caching end
69
+
70
+ def photoset_cache_store(set)
71
+ @photoset_by_id[set.id] = set if @caching
72
+ end
73
+
74
+ def photopool_cache_lookup(id) @photopool_by_id[id] if @caching end
75
+
76
+ def photopool_cache_store(pool)
77
+ @photopool_by_id[pool.id] = pool if @caching
78
+ end
79
+
80
+ def group_cache_lookup(id) @group_by_id[id] if @caching end
81
+
82
+ def group_cache_store(group)
83
+ @group_by_id[group.id] = group if @caching
84
+ end
85
+ ############################################################################
86
+
87
+ def debug(*args) $stderr.puts(sprintf(*args)) if @debug end
88
+
89
+ def Flickr.todo
90
+ [
91
+ 'Refactor, especially more Class.from_xml methods',
92
+ 'More logical OO design, wrap the API methods to make transparent',
93
+ 'Class & method documentation',
94
+ 'Unit tests',
95
+ 'Implement missing methods (see flickr.reflection.missing_methods)'
96
+ ]
97
+ end
98
+ def todo()
99
+ Flickr.todo+reflection.missing_methods.map{|m| 'Implement '+m}
100
+ end
101
+
102
+
103
+ def initialize(token_cache=nil,api_key=API_KEY,
104
+ shared_secret=SHARED_SECRET,
105
+ endpoint='http://www.flickr.com/services/xmlrpc/')
106
+ @async = false
107
+ @caching = true
108
+ @auth_mode = true
109
+ @api_key=api_key
110
+ @shared_secret=shared_secret
111
+ @token_cache = token_cache
112
+ @endpoint=endpoint
113
+ proto,host,port,path,user,pass=parse_url(@endpoint)
114
+ raise ProtoUnknownError.new("Unhandled protocol '#{proto}'") if
115
+ proto.downcase != 'http'
116
+ @client=XMLRPC::Client.new(host,path,port)
117
+ clear_cache
118
+ end
119
+
120
+ def clear_cache()
121
+ @auth = nil
122
+ @blogs = nil
123
+ @contacts = nil
124
+ @favorites = nil
125
+ @groups = nil
126
+ @interestingness = nil
127
+ @reflection = nil
128
+ @people = nil
129
+ @photos = nil
130
+ @photosets = nil
131
+ @test = nil
132
+ @urls = nil
133
+
134
+ @ticket_by_id = {}
135
+ @person_by_nsid = {}
136
+ @photo_by_id = {}
137
+ @photoset_by_id = {}
138
+ @photopool_by_id = {}
139
+ @group_by_id = {}
140
+ @license_cache = nil
141
+ @blog_cache = nil
142
+ end
143
+
144
+ def auth() @auth ||= Auth.new(self,@token_cache) end
145
+ def blogs() @blogs ||= Blogs.new(self) end
146
+ def contacts() @contacts ||= Contacts.new(self) end
147
+ def favorites() @favorites ||= Favorites.new(self) end
148
+ def groups() @groups ||= Groups.new(self) end
149
+ def people() @people ||= People.new(self) end
150
+ def photos() @photos ||= Photos.new(self) end
151
+ def photosets() @photosets ||= PhotoSets.new(self) end
152
+ def reflection() @reflection ||= Reflection.new(self) end
153
+ def test() @test ||= Test.new(self) end
154
+ def urls() @urls ||= Urls.new(self) end
155
+ def tags() @tags ||= Tags.new(self) end
156
+ def interestingness() @interestingness ||= Interestingness.new(self) end
157
+
158
+ def call_method(method,args={})
159
+ @auth_mode ? call_auth_method(method,args) :
160
+ call_unauth_method(method,args)
161
+ end
162
+
163
+ def call_unauth_method(method,args={})
164
+ debug('%s(%s)', method, args.inspect)
165
+ tries = 3
166
+ args = args.dup
167
+ args['api_key'] = @api_key
168
+ api_sig=sign(args)
169
+ args['api_sig']=api_sig
170
+ begin
171
+ tries -= 1;
172
+ str = @async ? @client.call_async(method,args) :
173
+ @client.call(method,args)
174
+ debug('RETURN: %s',str)
175
+ return REXML::Document.new(str)
176
+ rescue Timeout::Error => te
177
+ $stderr.puts "Timed out, will try #{tries} more times."
178
+ if tries > 0
179
+ retry
180
+ else
181
+ raise te
182
+ end
183
+ rescue REXML::ParseException => pe
184
+ return REXML::Document.new('<rsp>'+str+'</rsp>').
185
+ elements['/rsp']
186
+ rescue XMLRPC::FaultException => fe
187
+ $stderr.puts "ERR: #{fe.faultString} (#{fe.faultCode})"
188
+ raise fe
189
+ end
190
+ end
191
+
192
+ def call_auth_method(method,args={})
193
+ at = args['auth_token']
194
+ args['auth_token'] ||= auth.token.token
195
+ res = call_unauth_method(method,args)
196
+ args.delete('auth_token') unless at
197
+ return res
198
+ end
199
+
200
+ def sign(args)
201
+ return MD5.md5(@shared_secret+args.sort.flatten.join).to_s
202
+ end
203
+
204
+ def parse_url(url)
205
+ url =~ /([^:]+):\/\/([^\/]*)(.*)/
206
+ proto = $1.to_s
207
+ hostplus = $2.to_s
208
+ path = $3.to_s
209
+
210
+ hostplus =~ /(?:(.*)@)?(.*)/
211
+ userpass = $1
212
+ hostport = $2
213
+ user,pass = userpass.to_s.split(':',2)
214
+ host,port = hostport.to_s.split(':',2)
215
+ port = port ? port.to_i : 80
216
+
217
+ return proto,host,port,path,user,pass
218
+ end
219
+
220
+ def mysql_datetime(time) time.strftime('%Y-%m-%d %H:%M:%S') end
221
+ def mysql_date(time) time.strftime('%Y-%m-%d') end
222
+ end
223
+
224
+ class Flickr::APIBase
225
+ attr_reader :flickr
226
+
227
+ def initialize(flickr) @flickr = flickr end
228
+ end
229
+
230
+ class Flickr::Token
231
+ attr_reader :token, :perms, :user
232
+
233
+ def initialize(token, perms, user)
234
+ @token = token
235
+ @perms = perms
236
+ @user = user
237
+ end
238
+
239
+ def self.from_xml(xml, flickr=nil)
240
+ token = xml.elements['/auth/token'].text
241
+ perms = xml.elements['/auth/perms'].text.intern
242
+ user = xml.elements['/auth/user']
243
+ nsid = user.attributes['nsid']
244
+ username = user.attributes['username']
245
+ fullname = user.attributes['fullname']
246
+
247
+ p = flickr.person_cache_lookup(nsid) if flickr
248
+ p ||= Flickr::Person.new(flickr,nsid,username)
249
+ p.realname=fullname
250
+ flickr.person_cache_store(p) if flickr
251
+
252
+ return Flickr::Token.new(token,perms,p)
253
+ end
254
+
255
+ def to_xml
256
+ return "<auth><token>#{self.token}</token>"+
257
+ "<perms>#{self.perms}</perms>"+
258
+ "<user nsid=\"#{self.user.nsid}\" "+
259
+ "username=\"#{self.user.username}\" "+
260
+ "fullname=\"#{self.user.realname}\" /></auth>"
261
+ end
262
+ end
263
+
264
+ class Flickr::Blog
265
+ attr_reader :id, :name, :needspassword, :url
266
+
267
+ def initialize(id,name,needspassword,url)
268
+ @id = id
269
+ @name = name
270
+ @needspassword = needspassword
271
+ @url = url
272
+ end
273
+ end
274
+
275
+ class Flickr::Person
276
+ attr_accessor :nsid, :username, :realname, :mbox_sha1sum, :location,
277
+ :photosurl, :profileurl, :photos_firstdate, :photos_firstdatetaken,
278
+ :photos_count, :info_fetched, :isadmin, :ispro, :iconserver,
279
+ :bandwidth_max, :bandwidth_used, :filesize_max, :upload_fetched,
280
+ :friend, :family, :ignored
281
+
282
+ def initialize(flickr, nsid, username)
283
+ @flickr = flickr
284
+ @nsid = nsid
285
+ @username = username
286
+ @info_fetched = false
287
+ @upload_fetched = false
288
+ end
289
+
290
+ def full_info()
291
+ self.info_fetched ? self : @flickr.people.getInfo(self)
292
+ end
293
+ def upload_status() self.upload_fetched ? self :
294
+ @flickr.people.getUploadStatus(self) end
295
+
296
+
297
+ # I think this will define a class method. You can't use
298
+ # Flickr::Person.from_xml and if you just say Person.from_xml, it
299
+ # can't resolve Flickr::Person::Person
300
+ def self.from_xml(xml,flickr=nil)
301
+ els = xml.elements
302
+ att = xml.root.attributes
303
+
304
+ nsid = cond_attr(att,'nsid')
305
+ username = cond_text(els,'/person/username')
306
+
307
+ p = flickr.person_cache_lookup(nsid) if flickr
308
+ p ||= Flickr::Person.new(flickr,nsid,username)
309
+
310
+ p.username = username
311
+ p.isadmin = cond_attr(att,'isadmin') &&
312
+ cond_attr(att,'isadmin') == '1'
313
+ p.ispro = cond_attr(att,'ispro') &&
314
+ cond_attr(att,'ispro') == '1'
315
+ p.iconserver = cond_attr(att,'iconserver') &&
316
+ cond_attr(att,'iconserver').to_i
317
+ p.realname = cond_text(els,'/person/realname')
318
+ p.mbox_sha1sum = cond_text(els,'/person/mbox_sha1sum')
319
+ p.location = cond_text(els,'/person/location')
320
+ p.photosurl = cond_text(els,'/person/photosurl')
321
+ p.profileurl = cond_text(els,'/person/profileurl')
322
+ tstr = cond_text(els,'/person/photos/firstdate')
323
+ p.photos_firstdate = Time.at(tstr.to_i) if tstr
324
+ tstr = cond_text(els, '/person/photos/firstdatetaken')
325
+ p.photos_firstdatetaken = Time.gm(*ParseDate.parsedate(tstr)) if
326
+ tstr
327
+ p.photos_count = cond_text(els,'/person/photos/count')
328
+ p.photos_count = p.photos_count if p.photos_count
329
+
330
+ p.info_fetched = true if p.photos_count
331
+
332
+ if els['/user/bandwidth']
333
+ att = els['/user/bandwidth'].attributes
334
+ p.bandwidth_max = cond_attr(att,'max') &&
335
+ cond_attr(att,'max').to_i
336
+ p.bandwidth_used = cond_attr(att,'used') &&
337
+ cond_attr(att,'used').to_i
338
+ end
339
+ if els['/user/filesize']
340
+ att = els['/user/filesize'].attributes
341
+ p.filesize_max = cond_attr(att,'max') &&
342
+ cond_attr(att,'max').to_i
343
+ end
344
+
345
+ p.upload_fetched = true if p.bandwidth_max
346
+
347
+ flickr.person_cache_store(p) if flickr
348
+ return p
349
+ end
350
+
351
+ private
352
+ def self.cond_text(elements,index)
353
+ elements[index] ? elements[index].text : nil
354
+ end
355
+
356
+ def self.cond_attr(attributes,name) attributes[name] end
357
+ end
358
+
359
+ class Flickr::Size
360
+ attr_reader :label,:width,:height,:source,:url
361
+
362
+ def initialize(label,width,height,source,url)
363
+ @label = label
364
+ @width = width
365
+ @height = height
366
+ @source = source
367
+ @url = url
368
+ end
369
+
370
+ def self.from_xml(xml)
371
+ att = xml.attributes
372
+ return Flickr::Size.new(att['label'],att['width'].to_i,
373
+ att['height'].to_i,att['source'],att['url'])
374
+ end
375
+ end
376
+
377
+ class Flickr::Photo
378
+ attr_accessor :id, :owner_id, :secret, :server, :title, :ispublic,
379
+ :isfriend, :isfamily, :ownername, :dateadded,
380
+ :license_id, :description, :dates, :taken,
381
+ :lastupdate, :takengranularity, :cancomment, :canaddmeta,
382
+ :comments, :rotation, :notes, :urls, :permaddmeta,
383
+ :permcomment, :originalformat
384
+
385
+ attr_reader :flickr
386
+
387
+ def owner() @owner ||= @flickr.people.getInfo(owner_id) end
388
+ def sizes() @sizes || @flickr.photos.getSizes(self).sizes end
389
+ def sizes=(sizes) @sizes = sizes end
390
+
391
+ def max_size
392
+ sizes[:Original] || sizes[:Large] || sizes[:Medium] ||
393
+ sizes[:Small]
394
+ end
395
+
396
+ def initialize(flickr,id)
397
+ @flickr = flickr
398
+ @id = id
399
+ end
400
+
401
+ def exif() @exif ||= @flickr.photos.getExif(self) end
402
+ def exif=(set) @exif = set end
403
+
404
+ def tags() @tags ||= @flickr.tags.getListPhoto(self) end
405
+ def tags=(set) @tags = set end
406
+
407
+ def license() @flickr.photos.licenses.getInfo[@license_id] end
408
+
409
+ def contexts() @contexts ||= @flickr.photos.getAllContexts(self) end
410
+
411
+ def url(size=nil)
412
+ base = 'http://static.flickr.com'
413
+ ext = (size == 'o') ? self.originalformat : 'jpg'
414
+ return size ?
415
+ "#{base}/#@server/#{@id}_#{@secret}_#{size}.#{ext}" :
416
+ "#{base}/#@server/#{@id}_#{@secret}.jpg"
417
+ end
418
+
419
+ def delete() @flickr.photos.delete(self) end
420
+
421
+ def self.from_xml(xml,flickr=nil)
422
+ att = xml.attributes
423
+ phid = att['id']
424
+
425
+ photo = flickr.photo_cache_lookup(phid) if flickr
426
+ photo ||= Flickr::Photo.new(flickr,phid)
427
+
428
+ photo.owner_id ||= att['owner'] || (xml.elements['owner'] &&
429
+ xml.elements['owner'].attributes['nsid'])
430
+ photo.secret = att['secret'] if att['secret']
431
+ photo.originalformat = att['originalformat'] if
432
+ att['originalformat']
433
+ photo.server = att['server'].to_i if att['server']
434
+ photo.title = att['title'] || cond_text(xml.elements,'title')
435
+ photo.license_id = att['license']
436
+ photo.rotation = att['rotation'].to_i if att['rotation']
437
+
438
+ photo.ispublic = (att['ispublic'].to_i == 1) if att['ispublic']
439
+ photo.isfriend = (att['isfriend'].to_i == 1) if att['isfriend']
440
+ photo.isfamily = (att['isfamily'].to_i == 1) if att['isfamily']
441
+ photo.ownername = att['ownername'] || (xml.elements['owner'] &&
442
+ xml.elements['owner'].attributes['username'])
443
+ photo.description = cond_text(xml.elements,'description')
444
+ photo.dateadded = Time.at(att['dateadded'].to_i) if
445
+ att['dateadded']
446
+ if xml.elements['exif']
447
+ list = []
448
+ xml.elements.each('exif') do |el|
449
+ exif = Flickr::Exif.from_xml(el)
450
+ list << exif
451
+ end
452
+ photo.exif = list
453
+ end
454
+ if xml.elements['visibility']
455
+ att = xml.elements['visibility'].attributes
456
+ photo.ispublic = (att['ispublic'].to_i == 1)
457
+ photo.isfriend = (att['isfriend'].to_i == 1)
458
+ photo.isfamily = (att['isfamily'].to_i == 1)
459
+ end
460
+ if xml.elements['dates']
461
+ att = xml.elements['dates'].attributes
462
+ dates = {}
463
+ dates[:posted] = Time.at(att['posted'].to_i)
464
+ dates[:taken] = Time.gm(*ParseDate.parsedate(att['taken']))
465
+ dates[:lastupdate] = Time.at(att['lastupdate'].to_i)
466
+ dates[:takengranularity] = att['takengranularity'].to_i
467
+ photo.dates = dates
468
+ end
469
+ if xml.elements['editability']
470
+ att = xml.elements['editability'].attributes
471
+ photo.cancomment = (att['cancomment'].to_i == 1)
472
+ photo.canaddmeta = (att['canaddmeta'].to_i == 1)
473
+ end
474
+ photo.comments = cond_text(xml.elements,'comments')
475
+ photo.comments &&= photo.comments.to_i
476
+ if xml.elements['notes']
477
+ notes = []
478
+ xml.elements['notes'].each_element do |el|
479
+ notes << Flickr::Note.from_xml(el,photo)
480
+ end
481
+ photo.notes = notes
482
+ end
483
+ if xml.elements['tags']
484
+ tags = []
485
+ xml.elements['tags'].each_element do |el|
486
+ tags << Flickr::Tag.from_xml(el,photo)
487
+ end
488
+ photo.tags = tags
489
+ end
490
+ if xml.elements['urls']
491
+ urls = {}
492
+ xml.elements['urls'].each_element do |el|
493
+ att = el.attributes
494
+ urls[att['type'].intern] = el.text
495
+ end
496
+ photo.urls = urls
497
+ end
498
+
499
+ flickr.photo_cache_store(photo) if flickr
500
+ return photo
501
+ end
502
+
503
+ private
504
+ def self.cond_text(elements,index)
505
+ elements[index] ? elements[index].text : nil
506
+ end
507
+ end
508
+
509
+ class Flickr::Exif
510
+ attr_reader :tagspace,:tagspaceid,:tag,:label
511
+ attr_accessor :raw,:clean
512
+ def initialize(tagspace,tagspaceid,tag,label)
513
+ @tagspace = tagspace
514
+ @tagspaceid = tagspaceid
515
+ @tag = tag
516
+ @label = label
517
+ end
518
+
519
+ def self.from_xml(element)
520
+ att = element.attributes
521
+ exif = Flickr::Exif.new(att['tagspace'],att['tagspaceid'].to_i,
522
+ att['tag'],att['label'])
523
+ exif.raw=element.elements['raw'].text if element.elements['raw']
524
+ exif.clean=element.elements['clean'].text if
525
+ element.elements['clean']
526
+ return exif
527
+ end
528
+ end
529
+
530
+ class Flickr::PhotoList < Array
531
+ attr_reader :page,:pages,:perpage,:total
532
+
533
+ def initialize(page,pages,perpage,total)
534
+ @page = page
535
+ @pages = pages
536
+ @perpage = perpage
537
+ @total = total
538
+ end
539
+
540
+ def self.from_xml(xml,flickr=self)
541
+ att = xml.root.attributes
542
+ list = Flickr::PhotoList.new(att['page'].to_i,att['pages'].to_i,
543
+ att['perpage'].to_i,att['total'].to_i)
544
+ xml.elements['/photos'].each_element do |e|
545
+ list << Flickr::Photo.from_xml(e,flickr)
546
+ end
547
+ return list
548
+ end
549
+ end
550
+
551
+ class Flickr::Category
552
+ attr_reader :name, :path, :pathids, :groups, :subcats, :id
553
+
554
+ def initialize(name,path,pathids)
555
+ @name = name
556
+ @path = path
557
+ @pathids = pathids
558
+ @groups = []
559
+ @subcats = []
560
+ @id = pathids.split('/').last
561
+ end
562
+ end
563
+
564
+ class Flickr::SubCategory
565
+ attr_reader :name, :id, :count
566
+
567
+ def initialize(name,id,count)
568
+ @name = name
569
+ @id = id
570
+ @count = count
571
+ end
572
+ end
573
+
574
+ class Flickr::Group
575
+ # The privacy attribute is 1 for private groups, 2 for invite-only public
576
+ # groups and 3 for open public groups.
577
+ PRIVACY = [nil,:private,:invite,:public]
578
+
579
+ attr_accessor :nsid, :name, :members, :online, :chatnsid, :inchat,
580
+ :description, :privacy, :eighteenplus, :fully_fetched, :admin,
581
+ :photo_count, :iconserver
582
+
583
+ def initialize(flickr,nsid, name=nil, members=nil, online=nil,
584
+ chatnsid=nil, inchat=nil)
585
+ @flickr = flickr
586
+ @nsid = nsid
587
+ @name = name
588
+ @members = members
589
+ @online = online
590
+ @chatnsid = chatnsid
591
+ @inchat = inchat
592
+ @fully_fetched = false
593
+ end
594
+
595
+ def full_info
596
+ self.fully_fetched ? self : @flickr.groups.getInfo(self)
597
+ end
598
+ end
599
+
600
+ class Flickr::GroupList < Array
601
+ attr_reader :page,:pages,:perpage,:total
602
+
603
+ def initialize(page,pages,perpage,total)
604
+ @page = page
605
+ @pages = pages
606
+ @perpage = perpage
607
+ @total = total
608
+ end
609
+ end
610
+
611
+ class Flickr::Context
612
+ attr_reader :prev_id,:prev_secret,:prev_title,:prev_url,
613
+ :next_id,:next_secret,:next_title,:next_url
614
+
615
+ def initialize(prev_id,prev_secret,prev_title,prev_url,
616
+ next_id,next_secret,next_title,next_url)
617
+ @prev_id = prev_id
618
+ @prev_secret = prev_secret
619
+ @prev_title = prev_title
620
+ @prev_url = prev_url
621
+ @next_id = next_id
622
+ @next_secret = next_secret
623
+ @next_title = next_title
624
+ @next_url = next_url
625
+ end
626
+
627
+ def self.from_xml(xml)
628
+ a0 = xml.elements['prevphoto'].attributes
629
+ a1 = xml.elements['nextphoto'].attributes
630
+ return Flickr::Context.new(
631
+ a0['id'],a0['secret'],a0['title'],a0['url'],
632
+ a1['id'],a1['secret'],a1['title'],a1['url'])
633
+ end
634
+ end
635
+
636
+ class Flickr::License
637
+ attr_reader :id, :name, :url
638
+ def initialize(id,name,url)
639
+ @id = id
640
+ @name = name
641
+ @url = url
642
+ end
643
+
644
+ def self.from_xml(xml)
645
+ att = xml.attributes
646
+ return Flickr::License.new(att['id'],att['name'],
647
+ att['url'])
648
+ end
649
+ end
650
+
651
+ class Flickr::Note
652
+ attr_accessor :photo, :x, :y, :w, :h, :text, :id, :author_id
653
+ def initialize(x, y, w, h, text, flickr = nil)
654
+ @x = x
655
+ @y = y
656
+ @w = w
657
+ @h = h
658
+ @text = text
659
+ @flickr = flickr
660
+ end
661
+
662
+ def author() @author_id && @flickr.people.getInfo(@author_id) end
663
+
664
+ def self.from_xml(xml,photo=nil)
665
+ att = xml.attributes
666
+ note = Flickr::Note.new(att['x'].to_i,att['y'].to_i,
667
+ att['w'].to_i,att['h'].to_i,xml.text,
668
+ photo && photo.flickr)
669
+ note.photo = photo
670
+ note.id = att['id']
671
+ note.author_id = att['author'] if att['author']
672
+ end
673
+ end
674
+
675
+ class Flickr::Count
676
+ attr_reader :fromdate, :todate, :count
677
+ def initialize(count,fromdate,todate)
678
+ @count = count
679
+ @fromdate = fromdate
680
+ @todate = todate
681
+ end
682
+
683
+ def self.from_xml(xml)
684
+ att = xml.attributes
685
+ return Flickr::Count.new(att['count'].to_i,
686
+ Time.at(att['fromdate'].to_i),
687
+ Time.at(att['todate'].to_i))
688
+ end
689
+ end
690
+
691
+ class Flickr::Tag
692
+ attr_reader :id, :author_id, :raw, :clean
693
+
694
+ def initialize(flickr, id,author_id,raw,clean)
695
+ @flickr = flickr
696
+ @id = id
697
+ @author_id = author_id
698
+ @raw = raw
699
+ @clean = clean
700
+ end
701
+
702
+ def author() @flickr.people.getInfo(@author_id) end
703
+
704
+ def self.from_xml(xml,flickr=nil)
705
+ att = xml.attributes
706
+ clean = xml.text
707
+ return Flickr::Tag.new(flickr,att['id'],att['author'],
708
+ att['raw'], clean)
709
+ end
710
+ end
711
+
712
+ class Flickr::PhotoSet < Array
713
+ attr_accessor :id, :title, :url, :server, :primary_id,
714
+ :photo_count, :description, :secret, :owner
715
+
716
+ def initialize(id,flickr)
717
+ @id = id
718
+ @flickr = flickr
719
+ end
720
+
721
+ def <<(photo,raw=false)
722
+ raw ? super(photo) : @flickr.photosets.addPhoto(self,photo)
723
+ return self
724
+ end
725
+
726
+ def fetch(extras=nil)
727
+ return self if @fetched
728
+ set = @flickr.photosets.getPhotos(self,extras)
729
+ @fetched = true
730
+ return set
731
+ end
732
+
733
+ alias photos fetch
734
+
735
+ def self.from_xml(xml,flickr=nil)
736
+ att = xml.attributes
737
+ psid = att['id']
738
+
739
+ set = flickr.photoset_cache_lookup(psid) if flickr
740
+ set ||= Flickr::PhotoSet.new(psid,flickr)
741
+
742
+ set.secret = att['secret']
743
+ set.owner = att['owner']
744
+ set.url = att['url']
745
+ set.server = att['server'].to_i
746
+ set.primary_id = att['primary'].to_i
747
+ set.photo_count = att['photos'].to_i
748
+ set.title = xml.elements['title'].text if xml.elements['title']
749
+ set.description = xml.elements['description'].text if
750
+ xml.elements['description']
751
+ if xml.elements['photo']
752
+ set.clear
753
+ xml.elements.each('photo') do |el|
754
+ set.<<(Flickr::Photo.from_xml(el,flickr),true)
755
+ end
756
+ end
757
+
758
+ flickr.photoset_cache_store(set) if flickr
759
+ return set
760
+ end
761
+
762
+ def url
763
+ owner = @owner || @flickr.photosets.getInfo(self).owner
764
+ return "http://www.flickr.com/photos/#{owner}/sets/#{@id}"
765
+ end
766
+
767
+ def primary() @primary ||= @flickr.photos.getInfo(@primary_id) end
768
+ end
769
+
770
+ class Flickr::PhotoPool < Array
771
+ attr_accessor :page, :pages, :perpage, :total, :title, :id
772
+
773
+ def initialize(id,flickr)
774
+ @id = id
775
+ @flickr = flickr
776
+ end
777
+
778
+ def <<(photo,raw=false)
779
+ raw ? super(photo) : @flickr.photosets.addPhoto(self,photo)
780
+ return self
781
+ end
782
+
783
+ def fetch(extras=nil)
784
+ return self if @fetched
785
+ pool = @flickr.groups.pools.getPhotos(self,nil,extras,500)
786
+ @fetched = true
787
+ return pool
788
+ end
789
+
790
+ def self.from_xml(xml,flickr=nil)
791
+ att = xml.attributes
792
+ ppid = att['id']
793
+
794
+ pool = flickr.photopool_cache_lookup(ppid)
795
+ pool ||= Flickr::PhotoPool.new(ppid,flickr)
796
+
797
+ pool.page = att['page'].to_i if att['page']
798
+ pool.pages = att['pages'].to_i if att['pages']
799
+ pool.perpage = att['perpage'].to_i if att['perpage']
800
+ pool.total = att['total'].to_i if att['total']
801
+ if xml.elements['photo']
802
+ # I'd like to clear the pool, but I can't because I don't know if I'm
803
+ # parsing the full set or just a single "page".
804
+ # pool.clear
805
+ xml.elements.each('photo') do |el|
806
+ pool.<<(Flickr::Photo.from_xml(el,flickr),true)
807
+ end
808
+ end
809
+
810
+ flickr.photopool_cache_store(pool) if flickr
811
+ return pool
812
+ end
813
+ end