ruby-picasa 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/Rakefile +3 -0
- data/lib/ruby_picasa.rb +124 -30
- data/lib/ruby_picasa/types.rb +143 -31
- data/spec/ruby_picasa/types_spec.rb +82 -8
- data/spec/ruby_picasa_spec.rb +33 -9
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
=== 0.2.1 / 2009-03-05
|
2
|
+
|
3
|
+
* Now fully documented.
|
4
|
+
* Full spec suite with 100% coverage.
|
5
|
+
* Even easier to use the api.
|
6
|
+
* Move thumbnail and url to Base so it can be used for Photos and Albums.
|
7
|
+
* Enable url to optionally return width and height for image_tag.
|
8
|
+
* Use the numeric gphoto:id as id and rename tho standard atom feed id to feed_id
|
9
|
+
* Fixed handling of cropped thumbnails and automatically query for valid thumbnail sizes.
|
10
|
+
* Stopped using the wrong url to #load albums. Renamed method to #feed.
|
11
|
+
|
1
12
|
=== 0.2.0 / 2009-02-25
|
2
13
|
|
3
14
|
* First public release.
|
data/Rakefile
CHANGED
data/lib/ruby_picasa.rb
CHANGED
@@ -6,7 +6,7 @@ require 'net/https'
|
|
6
6
|
require File.join(File.dirname(__FILE__), 'ruby_picasa/types')
|
7
7
|
|
8
8
|
module RubyPicasa
|
9
|
-
VERSION = '0.2.
|
9
|
+
VERSION = '0.2.1'
|
10
10
|
|
11
11
|
class PicasaError < StandardError
|
12
12
|
end
|
@@ -15,20 +15,53 @@ module RubyPicasa
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
# == Authorization
|
19
|
+
#
|
20
|
+
# RubyPicasa makes authorizing a Rails app easy. It is a two step process:
|
21
|
+
#
|
22
|
+
# First redirect the user to the authorization url, if the user authorizes your
|
23
|
+
# application, Picasa will redirect the user back to the url you specify (in
|
24
|
+
# this case authorize_picasa_url).
|
25
|
+
#
|
26
|
+
# Next, pass the Rails request object to the authorize_token method which will
|
27
|
+
# make the api call to upgrade the token and if successful return an initialized
|
28
|
+
# Picasa session object ready to use. The token object can be retrieved from the
|
29
|
+
# token attribute.
|
30
|
+
#
|
31
|
+
# class PicasaController < ApplicationController
|
32
|
+
# def request_authorization
|
33
|
+
# redirect_to Picasa.authorization_url(authorize_picasa_url)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def authorize
|
37
|
+
# if Picasa.token_in_request?(request)
|
38
|
+
# begin
|
39
|
+
# picasa = Picasa.authorize_request(request)
|
40
|
+
# current_user.picasa_token = picasa.token
|
41
|
+
# current_user.save
|
42
|
+
# flash[:notice] = 'Picasa authorization complete'
|
43
|
+
# redirect_to picasa_path
|
44
|
+
# rescue PicasaTokenError => e
|
45
|
+
# #
|
46
|
+
# @error = e.message
|
47
|
+
# render
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
18
53
|
class Picasa
|
19
|
-
include RubyPicasa
|
20
|
-
|
21
54
|
class << self
|
22
55
|
# The user must be redirected to this address to authorize the application
|
23
56
|
# to access their Picasa account. The token_from_request and
|
24
57
|
# authorize_request methods can be used to handle the resulting redirect
|
25
58
|
# from Picasa.
|
26
|
-
def authorization_url(return_to_url, request_session = true, secure = false)
|
59
|
+
def authorization_url(return_to_url, request_session = true, secure = false, authsub_url = nil)
|
27
60
|
session = request_session ? '1' : '0'
|
28
61
|
secure = secure ? '1' : '0'
|
29
62
|
return_to_url = CGI.escape(return_to_url)
|
30
|
-
url = 'http://www.google.com/accounts/AuthSubRequest'
|
31
|
-
"#{ url }?scope=http%3A%2F%
|
63
|
+
url = authsub_url || 'http://www.google.com/accounts/AuthSubRequest'
|
64
|
+
"#{ url }?scope=http%3A%2F%2F#{ host }%2Fdata%2F&session=#{ session }&secure=#{ secure }&next=#{ return_to_url }"
|
32
65
|
end
|
33
66
|
|
34
67
|
# Takes a Rails request object and extracts the token from it. This would
|
@@ -38,10 +71,14 @@ class Picasa
|
|
38
71
|
if token = request.params['token']
|
39
72
|
return token
|
40
73
|
else
|
41
|
-
raise PicasaTokenError, 'No Picasa authorization token was found.'
|
74
|
+
raise RubyPicasa::PicasaTokenError, 'No Picasa authorization token was found.'
|
42
75
|
end
|
43
76
|
end
|
44
77
|
|
78
|
+
def token_in_request?(request)
|
79
|
+
request.params['token']
|
80
|
+
end
|
81
|
+
|
45
82
|
# Takes a Rails request object as in token_from_request, then makes the
|
46
83
|
# token authorization request to produce the permanent token. This will
|
47
84
|
# only work if request_session was true when you created the
|
@@ -52,10 +89,20 @@ class Picasa
|
|
52
89
|
p
|
53
90
|
end
|
54
91
|
|
92
|
+
# The url to make requests to without the protocol or path.
|
55
93
|
def host
|
56
|
-
'picasaweb.google.com'
|
94
|
+
@host ||= 'picasaweb.google.com'
|
95
|
+
end
|
96
|
+
|
97
|
+
# In the unlikely event that you need to access this api on a different url,
|
98
|
+
# you can set it here. It defaults to picasaweb.google.com
|
99
|
+
def host=(h)
|
100
|
+
@host = h
|
57
101
|
end
|
58
102
|
|
103
|
+
# A simple test used to determine if a given resource id is it's full
|
104
|
+
# identifier url. This is not intended to be a general purpose method as the
|
105
|
+
# test is just a check for the http/https protocol prefix.
|
59
106
|
def is_url?(path)
|
60
107
|
path.to_s =~ %r{\Ahttps?://}
|
61
108
|
end
|
@@ -99,6 +146,12 @@ class Picasa
|
|
99
146
|
path = path.join('/')
|
100
147
|
end
|
101
148
|
options['kind'] = 'photo' if args[:recent_photos] or args[:album_id]
|
149
|
+
if args[:thumbsize] and not args[:thumbsize].split(/,/).all? { |s| RubyPicasa::Photo::VALID.include?(s) }
|
150
|
+
raise RubyPicasa::PicasaError, 'Invalid thumbsize.'
|
151
|
+
end
|
152
|
+
if args[:imgmax] and not RubyPicasa::Photo::VALID.include?(args[:imgmax])
|
153
|
+
raise RubyPicasa::PicasaError, 'Invalid imgmax.'
|
154
|
+
end
|
102
155
|
[:max_results, :start_index, :tag, :q, :kind,
|
103
156
|
:access, :thumbsize, :imgmax, :bbox, :l].each do |arg|
|
104
157
|
options[arg.to_s.dasherize] = args[arg] if args[arg]
|
@@ -112,6 +165,8 @@ class Picasa
|
|
112
165
|
|
113
166
|
private
|
114
167
|
|
168
|
+
# Extract the path and a hash of key/value pairs from a given url with
|
169
|
+
# optional query string.
|
115
170
|
def parse_url(args)
|
116
171
|
url = args[:url]
|
117
172
|
url ||= args[:user_id] if is_url?(args[:user_id])
|
@@ -133,6 +188,7 @@ class Picasa
|
|
133
188
|
end
|
134
189
|
end
|
135
190
|
|
191
|
+
# The AuthSub token currently in use.
|
136
192
|
attr_reader :token
|
137
193
|
|
138
194
|
def initialize(token)
|
@@ -140,6 +196,8 @@ class Picasa
|
|
140
196
|
@request_cache = {}
|
141
197
|
end
|
142
198
|
|
199
|
+
# Attempt to upgrade the current AuthSub token to a permanent one. This only
|
200
|
+
# works if the Picasa session is initialized with a single use token.
|
143
201
|
def authorize_token!
|
144
202
|
http = Net::HTTP.new("www.google.com", 443)
|
145
203
|
http.use_ssl = true
|
@@ -153,15 +211,22 @@ class Picasa
|
|
153
211
|
@token
|
154
212
|
end
|
155
213
|
|
156
|
-
|
157
|
-
|
214
|
+
# Retrieve a RubyPicasa::User record including all user albums.
|
215
|
+
def user(user_id_or_url = nil, options = {})
|
216
|
+
options = make_options(:user_id, user_id_or_url, options)
|
217
|
+
get(options)
|
158
218
|
end
|
159
219
|
|
220
|
+
# Retrieve a RubyPicasa::Album record. If you pass an id or a feed url it will
|
221
|
+
# include all photos. If you pass an entry url, it will not include photos.
|
160
222
|
def album(album_id_or_url, options = {})
|
161
|
-
|
223
|
+
options = make_options(:album_id, album_id_or_url, options)
|
224
|
+
get(options)
|
162
225
|
end
|
163
226
|
|
164
|
-
# This request does not require authentication.
|
227
|
+
# This request does not require authentication. Returns a RubyPicasa::Search
|
228
|
+
# object containing the first 10 matches. You can call #next and #previous to
|
229
|
+
# navigate the paginated results on the Search object.
|
165
230
|
def search(q, options = {})
|
166
231
|
h = {}
|
167
232
|
h[:max_results] = 10
|
@@ -171,27 +236,32 @@ class Picasa
|
|
171
236
|
get(h.merge(options).merge(:q => q))
|
172
237
|
end
|
173
238
|
|
239
|
+
# Retrieve a RubyPicasa object determined by the type of xml results returned
|
240
|
+
# by Picasa. Any supported type of RubyPicasa resource can be requested with
|
241
|
+
# this method.
|
174
242
|
def get_url(url, options = {})
|
175
|
-
|
243
|
+
options = make_options(:url, url, options)
|
244
|
+
get(options)
|
176
245
|
end
|
177
246
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
h[:user_id] = user_id_or_url
|
185
|
-
h[:recent_photos] = true
|
186
|
-
get(options.merge(h))
|
247
|
+
# Retrieve a RubyPicasa::RecentPhotos object, essentially a User object which
|
248
|
+
# contains photos instead of albums.
|
249
|
+
def recent_photos(user_id_or_url, options = {})
|
250
|
+
options = make_options(:user_id, user_id_or_url, options)
|
251
|
+
options[:recent_photos] = true
|
252
|
+
get(options)
|
187
253
|
end
|
188
254
|
|
255
|
+
# Retrieves the user's albums and finds the first one with a matching title.
|
256
|
+
# Returns a RubyPicasa::Album object.
|
189
257
|
def album_by_title(title, options = {})
|
190
258
|
if a = user.albums.find { |a| title === a.title }
|
191
259
|
a.load options
|
192
260
|
end
|
193
261
|
end
|
194
262
|
|
263
|
+
# Returns the raw xml from Picasa. See the Picasa.path method for valid
|
264
|
+
# options.
|
195
265
|
def xml(options = {})
|
196
266
|
http = Net::HTTP.new(Picasa.host, 80)
|
197
267
|
path = Picasa.path(options)
|
@@ -201,14 +271,31 @@ class Picasa
|
|
201
271
|
end
|
202
272
|
end
|
203
273
|
|
274
|
+
private
|
275
|
+
|
276
|
+
# If the value parameter is a hash, treat it as the options hash, otherwise
|
277
|
+
# insert the value into the hash with the key specified.
|
278
|
+
#
|
279
|
+
# Uses merge to ensure that a new hash object is returned to prevent caller's
|
280
|
+
# has from accidentally being modified.
|
281
|
+
def make_options(key, value, options)
|
282
|
+
if value.is_a? Hash
|
283
|
+
{}.merge value
|
284
|
+
else
|
285
|
+
options ||= {}
|
286
|
+
options.merge(key => value)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Combines the cached xml request with the class_from_xml factory. See the
|
291
|
+
# Picasa.path method for valid options.
|
204
292
|
def get(options = {})
|
205
293
|
with_cache(options) do |xml|
|
206
294
|
class_from_xml(xml)
|
207
295
|
end
|
208
296
|
end
|
209
297
|
|
210
|
-
|
211
|
-
|
298
|
+
# Returns the header data needed to make AuthSub requests.
|
212
299
|
def auth_header
|
213
300
|
if token
|
214
301
|
{ "Authorization" => %{AuthSub token="#{ token }"} }
|
@@ -217,6 +304,7 @@ class Picasa
|
|
217
304
|
end
|
218
305
|
end
|
219
306
|
|
307
|
+
# Caches the raw xml returned from the API. Keyed on request url.
|
220
308
|
def with_cache(options)
|
221
309
|
path = Picasa.path(options)
|
222
310
|
@request_cache.delete(path) if options[:reload]
|
@@ -231,6 +319,10 @@ class Picasa
|
|
231
319
|
end
|
232
320
|
end
|
233
321
|
|
322
|
+
# Returns the first xml element in the document (see
|
323
|
+
# Objectify::Xml.first_element) with the xml data types of the feed and first entry
|
324
|
+
# element in the document, used to determine which RubyPicasa object should
|
325
|
+
# be initialized to handle the data.
|
234
326
|
def xml_data(xml)
|
235
327
|
if xml = Objectify::Xml.first_element(xml)
|
236
328
|
# There is something wrong with Nokogiri xpath/css search with
|
@@ -244,6 +336,8 @@ class Picasa
|
|
244
336
|
end
|
245
337
|
end
|
246
338
|
|
339
|
+
# Initialize the correct RubyPicasa object depending on the type of feed and
|
340
|
+
# entries in the document.
|
247
341
|
def class_from_xml(xml)
|
248
342
|
xml, feed_scheme, entry_scheme = xml_data(xml)
|
249
343
|
if xml
|
@@ -251,28 +345,28 @@ class Picasa
|
|
251
345
|
when /#user$/
|
252
346
|
case entry_scheme
|
253
347
|
when /#album$/
|
254
|
-
User.new(xml, self)
|
348
|
+
RubyPicasa::User.new(xml, self)
|
255
349
|
when /#photo$/
|
256
|
-
RecentPhotos.new(xml, self)
|
350
|
+
RubyPicasa::RecentPhotos.new(xml, self)
|
257
351
|
end
|
258
352
|
when /#album$/
|
259
353
|
case entry_scheme
|
260
354
|
when nil, /#photo$/
|
261
|
-
Album.new(xml, self)
|
355
|
+
RubyPicasa::Album.new(xml, self)
|
262
356
|
end
|
263
357
|
when /#photo$/
|
264
358
|
case entry_scheme
|
265
359
|
when /#photo$/
|
266
|
-
Search.new(xml, self)
|
360
|
+
RubyPicasa::Search.new(xml, self)
|
267
361
|
when nil
|
268
|
-
Photo.new(xml, self)
|
362
|
+
RubyPicasa::Photo.new(xml, self)
|
269
363
|
end
|
270
364
|
end
|
271
365
|
if r
|
272
366
|
r.session = self
|
273
367
|
r
|
274
368
|
else
|
275
|
-
raise PicasaError, "Unknown feed type\n feed: #{ feed_scheme }\n entry: #{ entry_scheme }"
|
369
|
+
raise RubyPicasa::PicasaError, "Unknown feed type\n feed: #{ feed_scheme }\n entry: #{ entry_scheme }"
|
276
370
|
end
|
277
371
|
end
|
278
372
|
end
|
data/lib/ruby_picasa/types.rb
CHANGED
@@ -2,34 +2,43 @@
|
|
2
2
|
# about. If you care about them, please feel free to add support for them,
|
3
3
|
# which should not be difficult.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# in the xml document is already plural. (Convention seems to be to label
|
7
|
-
# repeating elements in the singular.)
|
8
|
-
#
|
9
|
-
# If an attribute should be a non-trivial datatype, define the mapping from
|
10
|
-
# the fully namespaced attribute name to the class you wish to use in the
|
11
|
-
# class method #types.
|
12
|
-
#
|
13
|
-
# Define which namespaces you support in the class method #namespaces. Any
|
5
|
+
# Declare which namespaces are supported with the namespaces method. Any
|
14
6
|
# elements defined in other namespaces are automatically ignored.
|
15
7
|
module RubyPicasa
|
8
|
+
# attributes :url, :height, :width
|
16
9
|
class PhotoUrl < Objectify::ElementParser
|
17
10
|
attributes :url, :height, :width
|
18
11
|
end
|
19
12
|
|
20
13
|
|
21
14
|
class ThumbnailUrl < PhotoUrl
|
15
|
+
|
16
|
+
# The name of the current thumbnail. For possible names, see Photo#url
|
22
17
|
def thumb_name
|
23
|
-
url.scan(%r{/([^/]+)/[^/]+$}).flatten.compact.first
|
18
|
+
name = url.scan(%r{/s([^/]+)/[^/]+$}).flatten.compact.first
|
19
|
+
if name
|
20
|
+
name.sub(/-/, '')
|
21
|
+
end
|
24
22
|
end
|
25
23
|
end
|
26
24
|
|
27
25
|
|
26
|
+
# Base class for User, Photo and Album types, not used independently.
|
27
|
+
#
|
28
|
+
# attribute :id, 'gphoto:id'
|
29
|
+
# attribute :feed_id, 'id'
|
30
|
+
# attributes :updated, :title
|
31
|
+
#
|
32
|
+
# has_many :links, Objectify::Atom::Link, 'link'
|
33
|
+
# has_one :content, PhotoUrl, 'media:content'
|
34
|
+
# has_many :thumbnails, ThumbnailUrl, 'media:thumbnail'
|
35
|
+
# has_one :author, Objectify::Atom::Author, 'author'
|
28
36
|
class Base < Objectify::DocumentParser
|
29
37
|
namespaces :openSearch, :gphoto, :media
|
30
38
|
flatten 'media:group'
|
31
39
|
|
32
|
-
attribute :id, 'id'
|
40
|
+
attribute :id, 'gphoto:id'
|
41
|
+
attribute :feed_id, 'id'
|
33
42
|
attributes :updated, :title
|
34
43
|
|
35
44
|
has_many :links, Objectify::Atom::Link, 'link'
|
@@ -37,14 +46,16 @@ module RubyPicasa
|
|
37
46
|
has_many :thumbnails, ThumbnailUrl, 'media:thumbnail'
|
38
47
|
has_one :author, Objectify::Atom::Author, 'author'
|
39
48
|
|
49
|
+
# Return the link object with the specified rel attribute value.
|
40
50
|
def link(rel)
|
41
|
-
links.find { |l|
|
51
|
+
links.find { |l| rel === l.rel }
|
42
52
|
end
|
43
53
|
|
44
54
|
def session=(session)
|
45
55
|
@session = session
|
46
56
|
end
|
47
57
|
|
58
|
+
# Should return the Picasa instance that retrieved this data.
|
48
59
|
def session
|
49
60
|
if @session
|
50
61
|
@session
|
@@ -53,24 +64,93 @@ module RubyPicasa
|
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
56
|
-
|
57
|
-
|
67
|
+
# Retrieves the data feed at the url of the current record.
|
68
|
+
def feed(options = {})
|
69
|
+
session.get_url(link('http://schemas.google.com/g/2005#feed').href, options)
|
58
70
|
end
|
59
71
|
|
72
|
+
# If the results are paginated, retrieve the next page.
|
60
73
|
def next
|
61
74
|
if link = link('next')
|
62
75
|
session.get_url(link.href)
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
79
|
+
# If the results are paginated, retrieve the previous page.
|
66
80
|
def previous
|
67
81
|
if link = link('previous')
|
68
82
|
session.get_url(link.href)
|
69
83
|
end
|
70
84
|
end
|
85
|
+
|
86
|
+
# Thumbnail names are by image width in pixels. Sizes up to 160 may be
|
87
|
+
# either cropped (square) or uncropped:
|
88
|
+
#
|
89
|
+
# cropped: 32c, 48c, 64c, 72c, 144c, 160c
|
90
|
+
# uncropped: 32u, 48u, 64u, 72u, 144u, 160u
|
91
|
+
#
|
92
|
+
# The rest of the image sizes should be specified by the desired width
|
93
|
+
# alone. Widths up to 800px may be embedded on a webpage:
|
94
|
+
#
|
95
|
+
# embeddable: 200, 288, 320, 400, 512, 576, 640, 720, 800
|
96
|
+
# not embeddable: 912, 1024, 1152, 1280, 1440, 1600
|
97
|
+
#
|
98
|
+
# if a options is set to true or a hash is given, the width and height of
|
99
|
+
# the image will be added to the hash and returned. Useful for passing to
|
100
|
+
# the rails image_tag helper as follows:
|
101
|
+
#
|
102
|
+
# image_tag(*image.url('72c', { :class => 'thumb' }))
|
103
|
+
#
|
104
|
+
# which results in:
|
105
|
+
#
|
106
|
+
# <img href="..." class="thumb" width="72" height="72">
|
107
|
+
#
|
108
|
+
def url(thumb_name = nil, options = nil)
|
109
|
+
url = nil
|
110
|
+
if thumb_name.is_a? Hash
|
111
|
+
options = thumb_name
|
112
|
+
thumb_name = nil
|
113
|
+
end
|
114
|
+
options = {} if options and not options.is_a? Hash
|
115
|
+
if thumb_name
|
116
|
+
if thumb = thumbnail(thumb_name)
|
117
|
+
url = thumb.url
|
118
|
+
options = { :width => thumb.width, :height => thumb.height }.merge(options) if options
|
119
|
+
end
|
120
|
+
else
|
121
|
+
url = content.url
|
122
|
+
options = { :width => content.width, :height => content.height }.merge(options) if options
|
123
|
+
end
|
124
|
+
if options
|
125
|
+
[url, options]
|
126
|
+
else
|
127
|
+
url
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# See +url+ for possible image sizes
|
132
|
+
def thumbnail(thumb_name)
|
133
|
+
raise PicasaError, 'Invalid thumbnail size' unless Photo::VALID.include?(thumb_name.to_s)
|
134
|
+
thumb = thumbnails.find { |t| t.thumb_name == thumb_name }
|
135
|
+
if thumb
|
136
|
+
thumb
|
137
|
+
elsif session
|
138
|
+
f = feed(:thumbsize => thumb_name)
|
139
|
+
if f
|
140
|
+
f.thumbnails.first
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
71
144
|
end
|
72
145
|
|
73
146
|
|
147
|
+
# Includes attributes and associations defined on Base, plus:
|
148
|
+
#
|
149
|
+
# attributes :total_results, # represents total number of albums
|
150
|
+
# :start_index,
|
151
|
+
# :items_per_page,
|
152
|
+
# :thumbnail
|
153
|
+
# has_many :entries, :Album, 'entry'
|
74
154
|
class User < Base
|
75
155
|
attributes :total_results, # represents total number of albums
|
76
156
|
:start_index,
|
@@ -78,15 +158,20 @@ module RubyPicasa
|
|
78
158
|
:thumbnail
|
79
159
|
has_many :entries, :Album, 'entry'
|
80
160
|
|
161
|
+
# The current page of albums associated to the user.
|
81
162
|
def albums
|
82
163
|
entries
|
83
164
|
end
|
84
165
|
end
|
85
166
|
|
86
167
|
|
168
|
+
# Includes attributes and associations defined on Base and User, plus:
|
169
|
+
#
|
170
|
+
# has_many :entries, :Photo, 'entry'
|
87
171
|
class RecentPhotos < User
|
88
172
|
has_many :entries, :Photo, 'entry'
|
89
173
|
|
174
|
+
# The current page of recently updated photos associated to the user.
|
90
175
|
def photos
|
91
176
|
entries
|
92
177
|
end
|
@@ -95,11 +180,23 @@ module RubyPicasa
|
|
95
180
|
end
|
96
181
|
|
97
182
|
|
183
|
+
# Includes attributes and associations defined on Base, plus:
|
184
|
+
#
|
185
|
+
# attributes :published,
|
186
|
+
# :summary,
|
187
|
+
# :rights,
|
188
|
+
# :name,
|
189
|
+
# :access,
|
190
|
+
# :numphotos, # number of pictures in this album
|
191
|
+
# :total_results, # number of pictures matching this 'search'
|
192
|
+
# :start_index,
|
193
|
+
# :items_per_page,
|
194
|
+
# :allow_downloads
|
195
|
+
# has_many :entries, :Photo, 'entry'
|
98
196
|
class Album < Base
|
99
197
|
attributes :published,
|
100
198
|
:summary,
|
101
199
|
:rights,
|
102
|
-
:gphoto_id,
|
103
200
|
:name,
|
104
201
|
:access,
|
105
202
|
:numphotos, # number of pictures in this album
|
@@ -109,19 +206,25 @@ module RubyPicasa
|
|
109
206
|
:allow_downloads
|
110
207
|
has_many :entries, :Photo, 'entry'
|
111
208
|
|
209
|
+
# True if this album's rights are set to public
|
112
210
|
def public?
|
113
211
|
rights == 'public'
|
114
212
|
end
|
115
213
|
|
214
|
+
# True if this album's rights are set to private
|
116
215
|
def private?
|
117
216
|
rights == 'private'
|
118
217
|
end
|
119
218
|
|
219
|
+
# The current page of photos in the album.
|
120
220
|
def photos(options = {})
|
121
221
|
if entries.blank? and !@photos_requested
|
122
222
|
@photos_requested = true
|
123
|
-
|
124
|
-
|
223
|
+
if session and data = feed
|
224
|
+
self.entries = data.entries
|
225
|
+
else
|
226
|
+
[]
|
227
|
+
end
|
125
228
|
else
|
126
229
|
entries
|
127
230
|
end
|
@@ -130,13 +233,35 @@ module RubyPicasa
|
|
130
233
|
|
131
234
|
|
132
235
|
class Search < Album
|
236
|
+
# The current page of photos matching the search.
|
237
|
+
def photos(options = {})
|
238
|
+
super
|
239
|
+
end
|
133
240
|
end
|
134
241
|
|
135
242
|
|
243
|
+
# Includes attributes and associations defined on Base, plus:
|
244
|
+
#
|
245
|
+
# attributes :published,
|
246
|
+
# :summary,
|
247
|
+
# :version, # can use to determine if need to update...
|
248
|
+
# :position,
|
249
|
+
# :albumid, # useful from the recently updated feed for instance.
|
250
|
+
# :width,
|
251
|
+
# :height,
|
252
|
+
# :description,
|
253
|
+
# :keywords,
|
254
|
+
# :credit
|
255
|
+
# has_one :author, Objectify::Atom::Author, 'author'
|
136
256
|
class Photo < Base
|
257
|
+
CROPPED = %w[ 32c 48c 64c 72c 144c 160c ]
|
258
|
+
UNCROPPED = %w[ 32u 48u 64u 72u 144u 160u 32 48 64 72 144 160 ]
|
259
|
+
MEDIUM = %w[ 200 288 320 400 512 576 640 720 800 ]
|
260
|
+
LARGE = %w[ 912 1024 1152 1280 1440 1600 ]
|
261
|
+
VALID = CROPPED + UNCROPPED + MEDIUM + LARGE
|
262
|
+
|
137
263
|
attributes :published,
|
138
264
|
:summary,
|
139
|
-
:gphoto_id,
|
140
265
|
:version, # can use to determine if need to update...
|
141
266
|
:position,
|
142
267
|
:albumid, # useful from the recently updated feed for instance.
|
@@ -147,19 +272,6 @@ module RubyPicasa
|
|
147
272
|
:credit
|
148
273
|
has_one :author, Objectify::Atom::Author, 'author'
|
149
274
|
|
150
|
-
def url(thumb_name = nil)
|
151
|
-
if thumb_name
|
152
|
-
if thumb = thumbnail(thumb_name)
|
153
|
-
thumb.url
|
154
|
-
end
|
155
|
-
else
|
156
|
-
content.url
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def thumbnail(thumb_name)
|
161
|
-
thumbnails.find { |t| t.thumb_name == thumb_name }
|
162
|
-
end
|
163
275
|
end
|
164
276
|
end
|
165
277
|
|
@@ -3,8 +3,8 @@ require File.join(File.dirname(__FILE__), '../spec_helper')
|
|
3
3
|
include RubyPicasa
|
4
4
|
|
5
5
|
describe 'a RubyPicasa document', :shared => true do
|
6
|
-
it 'should have
|
7
|
-
@object.
|
6
|
+
it 'should have a feed_id' do
|
7
|
+
@object.feed_id.should_not be_nil
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'should have an author' do
|
@@ -25,9 +25,11 @@ describe 'a RubyPicasa document', :shared => true do
|
|
25
25
|
@object.next.should be_nil if @object.link('next').nil?
|
26
26
|
end
|
27
27
|
|
28
|
-
it 'should
|
29
|
-
@object.session.expects(:get_url).with(@object.
|
30
|
-
|
28
|
+
it 'should get the feed' do
|
29
|
+
@object.session.expects(:get_url).with(@object.feed_id.
|
30
|
+
gsub(/entry/, 'feed').
|
31
|
+
gsub(/default/, 'liz'), {})
|
32
|
+
@object.feed
|
31
33
|
end
|
32
34
|
|
33
35
|
it 'should have links' do
|
@@ -119,6 +121,11 @@ describe Album do
|
|
119
121
|
@album.session = mock('session')
|
120
122
|
end
|
121
123
|
|
124
|
+
it 'should have a numeric id' do
|
125
|
+
@object.id.should_not be_nil
|
126
|
+
@object.id.to_s.should match(/\A\d+\Z/)
|
127
|
+
end
|
128
|
+
|
122
129
|
it 'should have 1 entry' do
|
123
130
|
@album.entries.length.should == 1
|
124
131
|
end
|
@@ -137,9 +144,24 @@ describe Album do
|
|
137
144
|
it 'should request photos if needed' do
|
138
145
|
@album.entries = []
|
139
146
|
new_album = mock('album', :entries => [:photo])
|
140
|
-
@album.session.expects(:
|
147
|
+
@album.session.expects(:get_url).with(@album.link(/feed/).href, {}).returns(new_album)
|
141
148
|
@album.photos.should == [:photo]
|
142
149
|
end
|
150
|
+
|
151
|
+
it 'should not request photos twice if there are none' do
|
152
|
+
@album.entries = []
|
153
|
+
new_album = mock('album', :entries => [])
|
154
|
+
@album.session.expects(:get_url).with(@album.link(/feed/).href, {}).times(1).returns(new_album)
|
155
|
+
@album.photos.should == []
|
156
|
+
# note that mocks are set to accept only one get_url request
|
157
|
+
@album.photos.should == []
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should not request photos if there is no session' do
|
161
|
+
@album.entries = []
|
162
|
+
@album.expects(:session).returns(nil)
|
163
|
+
@album.photos.should == []
|
164
|
+
end
|
143
165
|
end
|
144
166
|
|
145
167
|
it 'should be public' do
|
@@ -175,16 +197,64 @@ describe Album do
|
|
175
197
|
end
|
176
198
|
end
|
177
199
|
|
200
|
+
it 'should have a numeric id' do
|
201
|
+
@object.id.should_not be_nil
|
202
|
+
@object.id.to_s.should match(/\A\d+\Z/)
|
203
|
+
end
|
204
|
+
|
178
205
|
it 'should have a default url' do
|
179
206
|
@photo.url.should == 'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/invisible_bike.jpg'
|
180
207
|
end
|
181
208
|
|
182
209
|
it 'should have thumbnail urls' do
|
183
|
-
@photo.url('
|
210
|
+
@photo.url('72').should == 'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/s72/invisible_bike.jpg'
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should have a default url with options true' do
|
214
|
+
@photo.url(nil, true).should == [
|
215
|
+
'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/invisible_bike.jpg',
|
216
|
+
{ :width => 410, :height => 295 }
|
217
|
+
]
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'should have a default url with options' do
|
221
|
+
@photo.url(nil, :id => 'p').should == [
|
222
|
+
'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/invisible_bike.jpg',
|
223
|
+
{ :width => 410, :height => 295, :id => 'p' }
|
224
|
+
]
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should have a default url with options first' do
|
228
|
+
@photo.url(:id => 'p').should == [
|
229
|
+
'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/invisible_bike.jpg',
|
230
|
+
{ :width => 410, :height => 295, :id => 'p' }
|
231
|
+
]
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should have thumbnail urls with options' do
|
235
|
+
@photo.url('72', {:class => 'x'}).should == [
|
236
|
+
'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/s72/invisible_bike.jpg',
|
237
|
+
{ :width => 72, :height => 52, :class => 'x' }
|
238
|
+
]
|
184
239
|
end
|
185
240
|
|
186
241
|
it 'should have thumbnail info' do
|
187
|
-
@photo.thumbnail('
|
242
|
+
@photo.thumbnail('72').width.should == 72
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'should retrieve valid thumbnail info' do
|
246
|
+
photo = mock('photo')
|
247
|
+
thumb = mock('thumb')
|
248
|
+
photo.expects(:thumbnails).returns([thumb])
|
249
|
+
@photo.session.expects(:get_url).with('http://picasaweb.google.com/data/feed/api/user/liz/albumid/5228155363249705041/photoid/5234820919508560306',
|
250
|
+
{:thumbsize => '32c'}).returns(photo)
|
251
|
+
@photo.thumbnail('32c').should == thumb
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'should retrieve valid thumbnail info and handle not found' do
|
255
|
+
@photo.session.expects(:get_url).with('http://picasaweb.google.com/data/feed/api/user/liz/albumid/5228155363249705041/photoid/5234820919508560306',
|
256
|
+
{:thumbsize => '32c'}).returns(nil)
|
257
|
+
@photo.thumbnail('32c').should be_nil
|
188
258
|
end
|
189
259
|
end
|
190
260
|
end
|
@@ -208,6 +278,10 @@ describe Search do
|
|
208
278
|
@search.entries.first.should be_an_instance_of(Photo)
|
209
279
|
end
|
210
280
|
|
281
|
+
it 'should alias entries to photos' do
|
282
|
+
@search.photos.should == @search.entries
|
283
|
+
end
|
284
|
+
|
211
285
|
it 'should request next' do
|
212
286
|
@search.session.expects(:get_url).with('http://picasaweb.google.com/data/feed/api/all?q=puppy&start-index=3&max-results=1').returns(:result)
|
213
287
|
@search.next.should == :result
|
data/spec/ruby_picasa_spec.rb
CHANGED
@@ -15,6 +15,18 @@ describe 'Picasa class methods' do
|
|
15
15
|
url.should match(/session=1/)
|
16
16
|
end
|
17
17
|
|
18
|
+
describe 'token_in_request?' do
|
19
|
+
it 'should be nil if no token' do
|
20
|
+
request = mock('request', :params => { })
|
21
|
+
Picasa.token_in_request?(request).should be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should not be nil if there is a token' do
|
25
|
+
request = mock('request', :params => { 'token' => 'abc' })
|
26
|
+
Picasa.token_in_request?(request).should_not be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
describe 'token_from_request' do
|
19
31
|
it 'should pluck the token from the request' do
|
20
32
|
request = mock('request', :params => { 'token' => 'abc' })
|
@@ -42,10 +54,9 @@ describe 'Picasa class methods' do
|
|
42
54
|
Picasa.is_url?('12323412341').should_not be_true
|
43
55
|
end
|
44
56
|
|
45
|
-
it 'should
|
46
|
-
|
47
|
-
Picasa.
|
48
|
-
Picasa.is_url?('/else').should be_true
|
57
|
+
it 'should allow host change' do
|
58
|
+
Picasa.host = 'abc'
|
59
|
+
Picasa.host.should == 'abc'
|
49
60
|
end
|
50
61
|
|
51
62
|
describe 'path' do
|
@@ -67,12 +78,25 @@ describe 'Picasa class methods' do
|
|
67
78
|
"/data/feed/api/all"
|
68
79
|
end
|
69
80
|
[ :max_results, :start_index, :tag, :q, :kind,
|
70
|
-
:access, :
|
81
|
+
:access, :bbox, :l].each do |arg|
|
71
82
|
it "should add #{ arg } to options" do
|
72
83
|
Picasa.path(:url => 'url', arg => '!value').should ==
|
73
84
|
"url?#{ arg.to_s.dasherize }=%21value"
|
74
85
|
end
|
75
86
|
end
|
87
|
+
[ :imgmax, :thumbsize ].each do |arg|
|
88
|
+
it "should raise PicasaError with invalid #{ arg } option" do
|
89
|
+
lambda do
|
90
|
+
Picasa.path(:url => 'url', arg => 'invalid')
|
91
|
+
end.should raise_error(RubyPicasa::PicasaError)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
[ :imgmax, :thumbsize ].each do |arg|
|
95
|
+
it "should add #{ arg } to options" do
|
96
|
+
Picasa.path(:url => 'url', arg => '72').should ==
|
97
|
+
"url?#{ arg.to_s.dasherize }=72"
|
98
|
+
end
|
99
|
+
end
|
76
100
|
it 'should ignore unknown options' do
|
77
101
|
Picasa.path(:url => 'place', :eggs => 'over_easy').should == 'place'
|
78
102
|
end
|
@@ -151,7 +175,7 @@ describe Picasa do
|
|
151
175
|
end
|
152
176
|
|
153
177
|
it 'should get the user' do
|
154
|
-
@p.expects(:get).with(:user_id =>
|
178
|
+
@p.expects(:get).with(:user_id => nil)
|
155
179
|
@p.user
|
156
180
|
end
|
157
181
|
|
@@ -177,7 +201,7 @@ describe Picasa do
|
|
177
201
|
end
|
178
202
|
|
179
203
|
it 'should get recent photos' do
|
180
|
-
@p.expects(:get).with(:
|
204
|
+
@p.expects(:get).with(:recent_photos => true, :max_results => 10)
|
181
205
|
@p.recent_photos :max_results => 10
|
182
206
|
end
|
183
207
|
|
@@ -223,13 +247,13 @@ describe Picasa do
|
|
223
247
|
it 'should call class_from_xml if with_cache yields' do
|
224
248
|
@p.expects(:with_cache).with({}).yields(:xml).returns(:result)
|
225
249
|
@p.expects(:class_from_xml).with(:xml)
|
226
|
-
@p.get.should == :result
|
250
|
+
@p.send(:get).should == :result
|
227
251
|
end
|
228
252
|
|
229
253
|
it 'should do nothing if with_cache does not yield' do
|
230
254
|
@p.expects(:with_cache).with({}) # doesn't yield
|
231
255
|
@p.expects(:class_from_xml).never
|
232
|
-
@p.get.should be_nil
|
256
|
+
@p.send(:get).should be_nil
|
233
257
|
end
|
234
258
|
end
|
235
259
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-picasa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pangloss
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-05 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: 1.9.0
|
34
34
|
version:
|
35
35
|
description: Provides a super easy to use object layer for authenticating and accessing Picasa through their API.
|
36
36
|
email:
|