ruby-picasa 0.2.0 → 0.2.1
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/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:
|