r2flickr 0.2

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