<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff></diff>
      <filename>.DS_Store</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,15 @@
+== 1.0.7 2008-11-26
+* 1 major enhancement
+ * When a collection of photos is fetched (e.g. by Flickr#search), a PhotoCollection object is now returned, a type of array, which allows easy access to the pagination information returned by Flickr (e.g. total items, pages, etc). Previously this info was lost
+* Minor enhancements:
+ * Refactored parsing of response when getting info about photo (public API is not changed). 
+ * All returned info is now parsed and stored (previously only some attributes were)
+ * A record is kept of whether info has been previously fetched from Flickr (avoids unnecessary extra calls)
+ * Improved User#getInfo. No longer makes API call for pretty_url (which was in addition to main getInfo call) or photos_url
+* Bugfixes
+ * Fixed Photo#source not to make unnecessary API call
+ * Fixed User#url not to make unnecessary API call
+
 == 1.0.6 2008-07-30
 * Bugfixes:
   * fixed Flickr#photos when used for searching
@@ -40,4 +52,3 @@
   * Removed call to flickr.people.getOnlineList in Flickr#users as that call is no longer in the Flickr API
 * Performance improvements:
   * Url and image source uri now generated without a call to the Flickr API, as per the Flickr API docs
-  
\ No newline at end of file</diff>
      <filename>History.txt</filename>
    </modified>
    <modified>
      <diff>@@ -192,12 +192,10 @@ class Flickr
     response
   end
 
-  # acts like request but returns a list of Photo objects
+  # acts like request but returns a PhotoCollection (a list of Photo objects)
   def photos_request(method, params={})
     photos = request(method, params)
-    collection = photos['photos']['photo'] || []
-    collection = [collection] if collection.is_a? Hash
-    collection.collect { |photo| Photo.new(photo.delete('id'), @api_key, photo) }
+    PhotoCollection.new(photos, @api_key)
   end
   
   # Builds url for Flickr API REST request from given the flickr method name 
@@ -218,6 +216,22 @@ class Flickr
     return Digest::MD5.hexdigest(&quot;#{@shared_secret}#{request_str}&quot;)
   end
   
+  # A collection of photos is returned as a PhotoCollection, a subclass of Array.
+  # This allows us to retain the pagination info returned by Flickr and make it
+  # accessible in a friendly way
+  class PhotoCollection &lt; Array
+    attr_reader :page, :pages, :perpage, :total
+    
+    # builds a PhotoCollection from given params, such as those returned from 
+    # photos.search API call
+    def initialize(photos_api_response={}, api_key=nil)
+      [ &quot;page&quot;, &quot;pages&quot;, &quot;perpage&quot;, &quot;total&quot; ].each { |i| instance_variable_set(&quot;@#{i}&quot;, photos_api_response[&quot;photos&quot;][i])} 
+      collection = photos_api_response['photos']['photo'] || []
+      collection = [collection] if collection.is_a? Hash
+      collection.each { |photo| self &lt;&lt; Photo.new(photo.delete('id'), api_key, photo) }
+    end
+  end
+  
   # Todo:
   # logged_in?
   # if logged in:
@@ -281,13 +295,23 @@ class Flickr
     def firstdatetaken
       @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
     end
+    
+    # Builds url for user's photos page as per 
+    # http://www.flickr.com/services/api/misc.urls.html
     def photos_url
-      @photos_url.nil? ? getInfo.photos_url : @photos_url
+      &quot;http://www.flickr.com/photos/#{id}/&quot;
     end
+        
+    # Builds url for user's profile page as per 
+    # http://www.flickr.com/services/api/misc.urls.html
     def url
-      @url.nil? ? getInfo.url : @url
+      &quot;http://www.flickr.com/people/#{id}/&quot;
     end
-        
+    
+    def pretty_url
+      @pretty_url ||= @client.urls_getUserProfile('user_id'=&gt;@id)['user']['url']
+    end
+    
     # Implements flickr.people.getPublicGroups
     def groups
       collection = @client.people_getPublicGroups('user_id'=&gt;@id)['groups']['group']
@@ -352,8 +376,6 @@ class Flickr
         @count = info['photos']['count']
         @firstdate = info['photos']['firstdate']
         @firstdatetaken = info['photos']['firstdatetaken']
-        @photos_url = @client.urls_getUserPhotos('user_id'=&gt;@id)['user']['url']
-        @url = @client.urls_getUserProfile('user_id'=&gt;@id)['user']['url']
         self
       end
 
@@ -381,7 +403,7 @@ class Flickr
     end
     
     def title
-      @title.nil? ? getInfo.title : @title
+      @title.nil? ? getInfo(&quot;title&quot;) : @title
     end
     
     # Returns the owner of the photo as a Flickr::User. If we have no info 
@@ -395,32 +417,32 @@ class Flickr
       when String
         @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
       else
-        getInfo.owner
+        getInfo(&quot;owner&quot;)
       end
     end
 
     def server
-      @server.nil? ? getInfo.server : @server
+      @server.nil? ? getInfo(&quot;server&quot;) : @server
     end
 
     def isfavorite
-      @isfavorite.nil? ? getInfo.isfavorite : @isfavorite
+      @isfavorite.nil? ? getInfo(&quot;isfavorite&quot;) : @isfavorite
     end
 
     def license
-      @license.nil? ? getInfo.license : @license
+      @license.nil? ? getInfo(&quot;license&quot;) : @license
     end
 
     def rotation
-      @rotation.nil? ? getInfo.rotation : @rotation
+      @rotation.nil? ? getInfo(&quot;rotation&quot;) : @rotation
     end
 
     def description
-      @description.nil? ? getInfo.description : @description
+      @description || getInfo(&quot;description&quot;)
     end
 
     def notes
-      @notes.nil? ? getInfo.notes : @notes
+      @notes.nil? ? getInfo(&quot;notes&quot;) : @notes
     end
 
     # Returns the URL for the photo size page
@@ -454,7 +476,7 @@ class Flickr
     # eg, http://flickr.com/photos/granth/2584402507/ instead of
     #     http://flickr.com/photos/23386158@N00/2584402507/
     def pretty_url
-      @url || getInfo.pretty_url
+      @url || getInfo(&quot;pretty_url&quot;)
     end
 
     # Returns the URL for the image (default or any specified size)
@@ -558,18 +580,16 @@ class Flickr
     private
 
       # Implements flickr.photos.getInfo
-      def getInfo
+      def getInfo(attrib=&quot;&quot;)
+        return instance_variable_get(&quot;@#{attrib}&quot;) if @got_info
         info = @client.photos_getInfo('photo_id'=&gt;@id)['photo']
-        @title = info['title']
+        @got_info = true
+        info.each { |k,v| instance_variable_set(&quot;@#{k}&quot;, v)}
         @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
-        @server = info['server']
-        @isfavorite = info['isfavorite']
-        @license = info['license']
-        @rotation = info['rotation']
-        @description = info['description']
+        @tags = info['tags']['tag']
         @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
         @url = info['urls']['url']['content'] # assumes only one url
-        self
+        instance_variable_get(&quot;@#{attrib}&quot;)
       end
       
       # Builds source uri of image from params (often returned from other </diff>
      <filename>lib/flickr.rb</filename>
    </modified>
    <modified>
      <diff>@@ -145,8 +145,25 @@ class TestFlickr &lt; Test::Unit::TestCase
                                      &quot;some_api_key&quot;, { &quot;key3&quot; =&gt; &quot;value3&quot;})
     photos = f.photos_request('some_method')
   end
-
-  def test_should_return_list_of_photos
+  
+  def test_should_parse_photos_response_into_flickr_photo_collection
+    f = flickr_client
+    f.expects(:request).returns(dummy_photos_response)
+    assert_kind_of Flickr::PhotoCollection, f.photos_request('some_method')
+  end
+  
+  def test_should_store_pagination_info_in_photo_collection
+    f = flickr_client
+    f.expects(:request).returns(dummy_photos_response)
+    photos = f.photos_request('some_method')
+    
+    assert_equal &quot;3&quot;, photos.page
+    assert_equal &quot;5&quot;, photos.pages
+    assert_equal &quot;10&quot;, photos.perpage
+    assert_equal &quot;42&quot;, photos.total
+  end
+  
+  def test_should_return_collection_of_photos
     f = flickr_client
     f.expects(:request).returns(dummy_photos_response)
     photos = f.photos_request('some_method')
@@ -349,6 +366,32 @@ class TestFlickr &lt; Test::Unit::TestCase
     assert_nil user.client
   end
   
+  def test_should_build_url_for_users_profile_page_using_user_id
+    Flickr.any_instance.expects(:http_get).never
+    assert_equal &quot;http://www.flickr.com/people/foo123/&quot;, new_user.url
+  end
+  
+  def test_should_build_url_for_users_photos_page_using_user_id
+    Flickr.any_instance.expects(:http_get).never
+    assert_equal &quot;http://www.flickr.com/photos/foo123/&quot;, new_user.photos_url
+  end
+  
+  def test_should_get_pretty_url_for_users_profile_page
+    f = flickr_client
+    f.expects(:urls_getUserProfile).returns({&quot;user&quot; =&gt; {&quot;nsid&quot; =&gt; &quot;bar456&quot;, &quot;url&quot; =&gt; &quot;http://www.flickr.com/people/killer_bob/&quot;}})
+    
+    assert_equal &quot;http://www.flickr.com/people/killer_bob/&quot;, new_user( 'client' =&gt; f ).pretty_url
+  end
+  
+  def test_should_cache_pretty_url_for_users_profile_page
+    f = flickr_client
+    user = new_user( 'client' =&gt; f )
+    f.expects(:urls_getUserProfile).returns({&quot;user&quot; =&gt; {&quot;nsid&quot; =&gt; &quot;bar456&quot;, &quot;url&quot; =&gt; &quot;http://www.flickr.com/people/killer_bob/&quot;}}) # expects only one call
+    
+    user.pretty_url
+    user.pretty_url
+  end
+  
   def test_should_get_users_public_groups
     f = flickr_client
     f.expects(:request).with(&quot;people.getPublicGroups&quot;, anything).returns(dummy_groups_response)
@@ -458,6 +501,59 @@ class TestFlickr &lt; Test::Unit::TestCase
     assert_nil photo['key2']
   end
   
+  def test_should_get_and_store_other_info_for_photo
+    Flickr.any_instance.stubs(:http_get).returns(photo_info_xml_response)
+    photo = Flickr::Photo.new('foo123', 'some_api_key')
+    
+    assert_equal &quot;1964 120 amazon estate&quot;, photo.title # calling #title method triggers getting of info
+    assert_equal &quot;1964 120 amazon estate&quot;, photo.instance_variable_get(:@title)    
+    assert_equal &quot;3142&quot;, photo.instance_variable_get(:@server)
+    assert_equal &quot;ae75bd3111&quot;, photo.instance_variable_get(:@secret)
+    assert_equal &quot;4&quot;, photo.instance_variable_get(:@farm)
+    assert_equal &quot;1204145093&quot;, photo.instance_variable_get(:@dateuploaded)
+    assert_equal &quot;photo&quot;, photo.instance_variable_get(:@media)
+    assert_equal &quot;0&quot;, photo.instance_variable_get(:@isfavorite)
+    assert_equal &quot;0&quot;, photo.instance_variable_get(:@license)
+    assert_equal &quot;0&quot;, photo.instance_variable_get(:@rotation)
+    assert_equal &quot;1964 Volvo 120 amazon estate spotted in derbyshire.&quot;, photo.instance_variable_get(:@description)
+    assert_equal( { &quot;w&quot; =&gt; &quot;50&quot;,
+                    &quot;x&quot; =&gt; &quot;10&quot;,
+                    &quot;y&quot; =&gt; &quot;10&quot;,
+                    &quot;authorname&quot; =&gt; &quot;Bees&quot;,
+                    &quot;author&quot; =&gt; &quot;12037949754@N01&quot;,
+                    &quot;id&quot; =&gt; &quot;313&quot;,
+                    &quot;content&quot; =&gt; &quot;foo&quot;,
+                    &quot;h&quot; =&gt; &quot;50&quot; }, photo.instance_variable_get(:@notes))
+    assert_equal &quot;http://www.flickr.com/photos/rootes_arrow/2296968304/&quot;, photo.instance_variable_get(:@url)
+    assert_equal [ { &quot;id&quot; =&gt; &quot;9377979-2296968304-2228&quot;, &quot;author&quot; =&gt; &quot;9383319@N05&quot;, &quot;raw&quot; =&gt; &quot;volvo&quot;, &quot;machine_tag&quot; =&gt; &quot;0&quot;, &quot;content&quot; =&gt; &quot;volvo&quot; },
+                   { &quot;id&quot; =&gt; &quot;9377979-2296968304-2229&quot;, &quot;author&quot; =&gt; &quot;9383319@N06&quot;, &quot;raw&quot; =&gt; &quot;amazon&quot;, &quot;machine_tag&quot; =&gt; &quot;0&quot;, &quot;content&quot; =&gt; &quot;amazon&quot;
+                   } ], photo.instance_variable_get(:@tags)
+    assert_equal &quot;1&quot;, photo.instance_variable_get(:@comments)
+    assert_kind_of Flickr::User, owner = photo.instance_variable_get(:@owner)
+    assert_equal &quot;Rootes_arrow_1725&quot;, owner.username
+  end
+  
+  def test_should_get_and_other_info_for_photo_when_some_attributes_missing
+    Flickr.any_instance.stubs(:http_get).returns(sparse_photo_info_xml_response)
+    photo = Flickr::Photo.new('foo123', 'some_api_key')
+    
+    assert_equal &quot;1964 120 amazon estate&quot;, photo.title # calling #title method triggers getting of info
+    assert_equal &quot;1964 120 amazon estate&quot;, photo.instance_variable_get(:@title)
+    assert_equal( {}, photo.instance_variable_get(:@description))
+    assert_nil photo.instance_variable_get(:@notes)
+    assert_nil photo.instance_variable_get(:@tags)
+    assert_equal &quot;1&quot;, photo.instance_variable_get(:@comments)
+  end
+  
+  def test_should_not_get_info_more_than_once
+    Flickr.any_instance.expects(:http_get).returns(photo_info_xml_response) # expects only one call
+    photo = Flickr::Photo.new('foo123', 'some_api_key')
+    
+    photo.description # calling #description method triggers getting of info
+    photo.instance_variable_set(:@description, nil) # set description to nil
+    photo.description # call #description method again
+  end
+  
   # 
   # owner tests
   def test_should_return_owner_when_flickr_user
@@ -471,7 +567,7 @@ class TestFlickr &lt; Test::Unit::TestCase
     photo = new_photo(&quot;owner&quot; =&gt; nil)
     # stubbing private methods causes problems so we mock client method, which is what Photo#getInfo users to make API call
     Flickr.any_instance.expects(:photos_getInfo).returns('photo' =&gt; { 'owner'=&gt;{'nsid'=&gt;'abc123', 'username'=&gt;'SomeUserName', 'realname'=&gt;&quot;&quot;, 'location'=&gt;''}, 
-                                                                      'notes' =&gt; {}, 'urls' =&gt; {'url' =&gt; {'content' =&gt; 'http://prettyurl'}}}) 
+                                                                      'notes' =&gt; {}, 'tags' =&gt; {}, 'urls' =&gt; {'url' =&gt; {'content' =&gt; 'http://prettyurl'}}}) 
 
     owner = photo.owner
     assert_kind_of Flickr::User, owner
@@ -788,6 +884,41 @@ class TestFlickr &lt; Test::Unit::TestCase
     assert_equal f.related_tags('monkey'), %w(zoo animal)
   end
 
+  # ##### Flickr::PhotoCollection tests
+  # 
+  def test_should_subclass_array_as_photo_collection
+     assert_equal Array, Flickr::PhotoCollection.superclass
+  end
+  
+  def test_should_make_page_a_reader_method
+    assert_equal &quot;3&quot;, dummy_photo_collection.page
+  end
+  
+  def test_should_make_pages_a_reader_method
+    assert_equal &quot;5&quot;, dummy_photo_collection.pages
+  end
+  
+  def test_should_make_perpage_a_reader_method
+    assert_equal &quot;10&quot;, dummy_photo_collection.perpage
+  end
+  
+  def test_should_make_total_a_reader_method
+    assert_equal &quot;42&quot;, dummy_photo_collection.total
+  end
+  
+  def test_should_instantiate_photo_collection_from_photos_hash
+    pc = Flickr::PhotoCollection.new(dummy_photos_response)
+    assert_kind_of Flickr::PhotoCollection, pc
+    assert_equal 2, pc.size
+    assert_kind_of Flickr::Photo, pc.first
+    assert_equal &quot;foo123&quot;, pc.first[&quot;id&quot;]
+  end
+  
+  def test_should_instantiate_photo_collection_using_given_api_key
+    photo = Flickr::PhotoCollection.new(dummy_photos_response, &quot;some_api_key&quot;).first
+    assert_equal &quot;some_api_key&quot;, photo.instance_variable_get(:@api_key)
+  end
+  
   private
   def flickr_client
     Flickr.new(&quot;some_api_key&quot;)
@@ -816,6 +947,10 @@ class TestFlickr &lt; Test::Unit::TestCase
                         &quot;owner&quot; =&gt; Flickr::User.new(&quot;abc123&quot;, &quot;some_user&quot;, nil, nil, &quot;some_api_key&quot;) }.merge(options))
   end
   
+  def dummy_photo_collection
+    Flickr::PhotoCollection.new(dummy_photos_response)
+  end
+  
   def dummy_photos_response
     { &quot;photos&quot; =&gt; 
       { &quot;photo&quot; =&gt; 
@@ -823,7 +958,11 @@ class TestFlickr &lt; Test::Unit::TestCase
            &quot;key1&quot; =&gt; &quot;value1&quot;, 
            &quot;key2&quot; =&gt; &quot;value2&quot; },
          { &quot;id&quot; =&gt; &quot;bar456&quot;, 
-           &quot;key3&quot; =&gt; &quot;value3&quot;}] } }
+           &quot;key3&quot; =&gt; &quot;value3&quot;}],
+        &quot;page&quot;=&gt;&quot;3&quot;, 
+        &quot;pages&quot;=&gt;&quot;5&quot;, 
+        &quot;perpage&quot;=&gt;&quot;10&quot;,
+        &quot;total&quot;=&gt;&quot;42&quot; } }
   end
   
   def dummy_single_photo_response
@@ -889,4 +1028,55 @@ class TestFlickr &lt; Test::Unit::TestCase
     EOF
   end
 
+  def photo_info_xml_response
+    &lt;&lt;-EOF
+    &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
+    &lt;rsp stat=&quot;ok&quot;&gt;
+      &lt;photo id=&quot;22527834&quot; secret=&quot;ae75bd3111&quot; server=&quot;3142&quot; farm=&quot;4&quot; dateuploaded=&quot;1204145093&quot; isfavorite=&quot;0&quot; license=&quot;0&quot; rotation=&quot;0&quot; media=&quot;photo&quot;&gt;
+      	&lt;owner nsid=&quot;9383319@N05&quot; username=&quot;Rootes_arrow_1725&quot; realname=&quot;John&quot; location=&quot;U.K&quot; /&gt;
+      	&lt;title&gt;1964 120 amazon estate&lt;/title&gt;
+      	&lt;description&gt;1964 Volvo 120 amazon estate spotted in derbyshire.&lt;/description&gt;
+      	&lt;visibility ispublic=&quot;1&quot; isfriend=&quot;0&quot; isfamily=&quot;0&quot; /&gt;
+      	&lt;dates posted=&quot;1204145093&quot; taken=&quot;2007-06-10 13:18:27&quot; takengranularity=&quot;0&quot; lastupdate=&quot;1204166772&quot; /&gt;
+      	&lt;editability cancomment=&quot;0&quot; canaddmeta=&quot;0&quot; /&gt;
+      	&lt;usage candownload=&quot;0&quot; canblog=&quot;0&quot; canprint=&quot;0&quot; /&gt;
+      	&lt;comments&gt;1&lt;/comments&gt;
+      	&lt;notes&gt;
+        	&lt;note id=&quot;313&quot; author=&quot;12037949754@N01&quot; authorname=&quot;Bees&quot; x=&quot;10&quot; y=&quot;10&quot; w=&quot;50&quot; h=&quot;50&quot;&gt;foo&lt;/note&gt;
+        &lt;/notes&gt;
+      	&lt;tags&gt;
+      		&lt;tag id=&quot;9377979-2296968304-2228&quot; author=&quot;9383319@N05&quot; raw=&quot;volvo&quot; machine_tag=&quot;0&quot;&gt;volvo&lt;/tag&gt;
+      		&lt;tag id=&quot;9377979-2296968304-2229&quot; author=&quot;9383319@N06&quot; raw=&quot;amazon&quot; machine_tag=&quot;0&quot;&gt;amazon&lt;/tag&gt;
+      	&lt;/tags&gt;
+      	&lt;urls&gt;
+      		&lt;url type=&quot;photopage&quot;&gt;http://www.flickr.com/photos/rootes_arrow/2296968304/&lt;/url&gt;
+      	&lt;/urls&gt;
+      &lt;/photo&gt;
+    &lt;/rsp&gt;
+    EOF
+  end
+
+  def sparse_photo_info_xml_response
+    &lt;&lt;-EOF
+    &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
+    &lt;rsp stat=&quot;ok&quot;&gt;
+      &lt;photo id=&quot;22527834&quot; secret=&quot;ae75bd3111&quot; server=&quot;3142&quot; farm=&quot;4&quot; dateuploaded=&quot;1204145093&quot; isfavorite=&quot;0&quot; license=&quot;0&quot; rotation=&quot;0&quot; media=&quot;photo&quot;&gt;
+      	&lt;owner nsid=&quot;9383319@N05&quot; username=&quot;Rootes_arrow_1725&quot; realname=&quot;John&quot; location=&quot;U.K&quot; /&gt;
+      	&lt;title&gt;1964 120 amazon estate&lt;/title&gt;
+      	&lt;description/&gt;
+      	&lt;visibility ispublic=&quot;1&quot; isfriend=&quot;0&quot; isfamily=&quot;0&quot; /&gt;
+      	&lt;dates posted=&quot;1204145093&quot; taken=&quot;2007-06-10 13:18:27&quot; takengranularity=&quot;0&quot; lastupdate=&quot;1204166772&quot; /&gt;
+      	&lt;editability cancomment=&quot;0&quot; canaddmeta=&quot;0&quot; /&gt;
+      	&lt;usage candownload=&quot;0&quot; canblog=&quot;0&quot; canprint=&quot;0&quot; /&gt;
+      	&lt;comments&gt;1&lt;/comments&gt;
+      	&lt;notes/&gt;
+      	&lt;tags/&gt;
+      	&lt;urls&gt;
+      		&lt;url type=&quot;photopage&quot;&gt;http://www.flickr.com/photos/rootes_arrow/2296968304/&lt;/url&gt;
+      	&lt;/urls&gt;
+      &lt;/photo&gt;
+    &lt;/rsp&gt;
+    EOF
+  end
+
 end
\ No newline at end of file</diff>
      <filename>test/test_flickr.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>6746ae9ca11c28675505fe7e8f17562db5d8cd71</id>
    </parent>
  </parents>
  <author>
    <name>Chris Taggart</name>
    <email>chris@windowsxponmac.home</email>
  </author>
  <url>http://github.com/ctagg/flickr/commit/3ffc1586176e2e473949ffc231abfac202266a66</url>
  <id>3ffc1586176e2e473949ffc231abfac202266a66</id>
  <committed-date>2008-11-28T05:14:07-08:00</committed-date>
  <authored-date>2008-11-28T05:14:07-08:00</authored-date>
  <message>* Collection of photos returned by Flickr is now a PhotoCollection rather than array, allowing us to keep other info returned by Flickr (e.g. pagination)
* Numerous minor enhancements, performance improvements and bugfixes. See HIstory.txt for details</message>
  <tree>7dc3848779ba39a78019d2bb1d8d1da5e7cd5b20</tree>
  <committer>
    <name>Chris Taggart</name>
    <email>chris@windowsxponmac.home</email>
  </committer>
</commit>
