digitalpardoe-rflickr 1.0.0.0

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