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 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
@@ -20,4 +20,7 @@ Spec::Rake::SpecTask.new(:spec) do |t|
20
20
  t.spec_opts = ['--colour', '--format', 'specdoc']
21
21
  end
22
22
 
23
+ Rake::Task[:default].clear
24
+ task :default => [:spec]
25
+
23
26
  # vim: syntax=Ruby
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.0'
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%2Fpicasaweb.google.com%2Fdata%2F&session=#{ session }&secure=#{ secure }&next=#{ return_to_url }"
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
- def user(user_id_or_url = 'default', options = {})
157
- get(options.merge(:user_id => user_id_or_url))
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
- get(options.merge(:album_id => album_id_or_url))
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
- get(options.merge(:url => url))
243
+ options = make_options(:url, url, options)
244
+ get(options)
176
245
  end
177
246
 
178
- def recent_photos(user_id_or_url = 'default', options = {})
179
- if user_id_or_url.is_a?(Hash)
180
- options = user_id_or_url
181
- user_id_or_url = 'default'
182
- end
183
- h = {}
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
- private
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
@@ -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
- # Plural attribute names will be treated as arrays unless the element name
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| l.rel == rel }
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
- def load(options = {})
57
- session.get_url(id, options)
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
- self.session ||= parent.session
124
- self.entries = session.album(id, options).entries if self.session
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 an id' do
7
- @object.id.should_not be_nil
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 load' do
29
- @object.session.expects(:get_url).with(@object.id, {})
30
- @object.load
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(:album).with(@album.id, {}).returns(new_album)
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('s72').should == 'http://lh5.ggpht.com/liz/SKXR5BoXabI/AAAAAAAAAzs/tJQefyM4mFw/s72/invisible_bike.jpg'
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('s72').width.should == 72
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
@@ -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 recognize relative urls?' do
46
- pending 'not currently needed'
47
- Picasa.is_url?('something.com/else').should be_true
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, :thumbsize, :imgmax, :bbox, :l].each do |arg|
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 => 'default')
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(:user_id => 'default', :recent_photos => true, :max_results => 10)
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.0
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-02 00:00:00 -05:00
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.8.3
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: