rflickr 2006.02.01

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.
@@ -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