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 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: