rflickr 2006.02.01

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