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.
- data/LICENCE +278 -0
- data/README +50 -0
- data/examples/album_test.rb +30 -0
- data/examples/comics-reorder.rb +16 -0
- data/examples/loadr.rb +80 -0
- data/examples/relatedness.rb +65 -0
- data/examples/setdumpr.rb +46 -0
- data/lib/flickr.rb +20 -0
- data/lib/flickr/auth.rb +60 -0
- data/lib/flickr/base.rb +811 -0
- data/lib/flickr/blogs.rb +30 -0
- data/lib/flickr/comments.rb +35 -0
- data/lib/flickr/contacts.rb +48 -0
- data/lib/flickr/favorites.rb +59 -0
- data/lib/flickr/groups.rb +83 -0
- data/lib/flickr/interestingness.rb +19 -0
- data/lib/flickr/licenses.rb +23 -0
- data/lib/flickr/notes.rb +24 -0
- data/lib/flickr/people.rb +79 -0
- data/lib/flickr/photos.rb +293 -0
- data/lib/flickr/photosets.rb +102 -0
- data/lib/flickr/pools.rb +73 -0
- data/lib/flickr/reflection.rb +89 -0
- data/lib/flickr/tags.rb +59 -0
- data/lib/flickr/test.rb +20 -0
- data/lib/flickr/token_cache.rb +33 -0
- data/lib/flickr/transform.rb +11 -0
- data/lib/flickr/upload.rb +205 -0
- data/lib/flickr/urls.rb +50 -0
- metadata +82 -0
@@ -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) }
|
data/lib/flickr.rb
ADDED
@@ -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'
|
data/lib/flickr/auth.rb
ADDED
@@ -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
|
data/lib/flickr/base.rb
ADDED
@@ -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
|