caring-r2flickr 0.1.1.7

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,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,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