reagent-fleakr 0.3.0 → 0.4.0

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.
Files changed (51) hide show
  1. data/README.rdoc +115 -20
  2. data/Rakefile +1 -1
  3. data/lib/fleakr.rb +66 -7
  4. data/lib/fleakr/api.rb +7 -0
  5. data/lib/fleakr/api/file_parameter.rb +47 -0
  6. data/lib/fleakr/api/method_request.rb +57 -0
  7. data/lib/fleakr/api/parameter.rb +35 -0
  8. data/lib/fleakr/api/parameter_list.rb +96 -0
  9. data/lib/fleakr/api/response.rb +2 -2
  10. data/lib/fleakr/api/upload_request.rb +64 -0
  11. data/lib/fleakr/api/value_parameter.rb +36 -0
  12. data/lib/fleakr/core_ext.rb +1 -0
  13. data/lib/fleakr/core_ext/hash.rb +22 -0
  14. data/lib/fleakr/objects.rb +9 -0
  15. data/lib/fleakr/objects/authentication_token.rb +43 -0
  16. data/lib/fleakr/objects/contact.rb +5 -5
  17. data/lib/fleakr/objects/error.rb +2 -2
  18. data/lib/fleakr/objects/group.rb +2 -2
  19. data/lib/fleakr/objects/image.rb +7 -7
  20. data/lib/fleakr/objects/photo.rb +69 -5
  21. data/lib/fleakr/objects/search.rb +3 -6
  22. data/lib/fleakr/objects/set.rb +11 -5
  23. data/lib/fleakr/objects/user.rb +14 -26
  24. data/lib/fleakr/support.rb +2 -0
  25. data/lib/fleakr/support/attribute.rb +30 -12
  26. data/lib/fleakr/support/object.rb +20 -4
  27. data/lib/fleakr/version.rb +1 -1
  28. data/test/fixtures/auth.checkToken.xml +8 -0
  29. data/test/fixtures/auth.getFullToken.xml +8 -0
  30. data/test/fixtures/people.getInfo.xml +1 -1
  31. data/test/fixtures/photos.getInfo.xml +20 -0
  32. data/test/test_helper.rb +18 -3
  33. data/test/unit/fleakr/api/file_parameter_test.rb +63 -0
  34. data/test/unit/fleakr/api/method_request_test.rb +103 -0
  35. data/test/unit/fleakr/api/parameter_list_test.rb +161 -0
  36. data/test/unit/fleakr/api/parameter_test.rb +34 -0
  37. data/test/unit/fleakr/api/upload_request_test.rb +133 -0
  38. data/test/unit/fleakr/api/value_parameter_test.rb +41 -0
  39. data/test/unit/fleakr/core_ext/hash_test.rb +32 -0
  40. data/test/unit/fleakr/objects/authentication_token_test.rb +47 -0
  41. data/test/unit/fleakr/objects/image_test.rb +10 -5
  42. data/test/unit/fleakr/objects/photo_test.rb +96 -36
  43. data/test/unit/fleakr/objects/search_test.rb +1 -1
  44. data/test/unit/fleakr/objects/set_test.rb +12 -1
  45. data/test/unit/fleakr/objects/user_test.rb +2 -16
  46. data/test/unit/fleakr/support/attribute_test.rb +82 -24
  47. data/test/unit/fleakr/support/object_test.rb +26 -3
  48. data/test/unit/fleakr_test.rb +65 -6
  49. metadata +27 -4
  50. data/lib/fleakr/api/request.rb +0 -58
  51. data/test/unit/fleakr/api/request_test.rb +0 -93
@@ -0,0 +1,96 @@
1
+ module Fleakr
2
+ module Api # :nodoc:
3
+
4
+ # = ParameterList
5
+ #
6
+ # Represents a list of parameters that get passed as part of a
7
+ # MethodRequest or UploadRequest. These can be transformed as necessary
8
+ # into query strings (using #to_query) or form data (using #to_form)
9
+ #
10
+ class ParameterList
11
+
12
+ # Create a new parameter list with optional parameters:
13
+ # [:sign?] Will these parameters be used to sign the request?
14
+ # [:authenticate?] Will the request need to be authenticated?
15
+ #
16
+ # Any additional name / value pairs will be created as individual
17
+ # ValueParameters as part of the list. Example:
18
+ #
19
+ # >> list = Fleakr::Api::ParameterList.new(:foo => 'bar')
20
+ # => #<Fleakr::Api::ParameterList:0x1656e6c @list=... >
21
+ # >> list[:foo]
22
+ # => #<Fleakr::Api::ValueParameter:0x1656da4 @include_in_signature=true, @name="foo", @value="bar">
23
+ #
24
+ def initialize(options = {})
25
+ @api_options = options.extract!(:sign?, :authenticate?)
26
+
27
+ @list = Hash.new
28
+
29
+ options.each {|k,v| self << ValueParameter.new(k.to_s, v) }
30
+
31
+ self << ValueParameter.new('api_key', Fleakr.api_key)
32
+ self << ValueParameter.new('auth_token', Fleakr.token.value) if authenticate?
33
+ end
34
+
35
+ # Add a new parameter (ValueParameter / FileParameter) to the list
36
+ #
37
+ def <<(parameter)
38
+ @list.merge!(parameter.name => parameter)
39
+ end
40
+
41
+ # Should this parameter list be signed?
42
+ #
43
+ def sign?
44
+ (@api_options[:sign?] == true || authenticate?) ? true : false
45
+ end
46
+
47
+ # Should we send the auth_token with the request?
48
+ #
49
+ def authenticate?
50
+ (@api_options[:authenticate?] == true) ? true : false
51
+ end
52
+
53
+ # Access an individual parameter by key (symbol or string)
54
+ #
55
+ def [](key)
56
+ list[key.to_s]
57
+ end
58
+
59
+ def boundary # :nodoc:
60
+ @boundary ||= Digest::MD5.hexdigest(rand.to_s)
61
+ end
62
+
63
+ # Generate the query string representation of this parameter
64
+ # list - e.g. <tt>foo=bar&blee=baz</tt>
65
+ #
66
+ def to_query
67
+ list.values.map(&:to_query).join('&')
68
+ end
69
+
70
+ # Generate the form representation of this parameter list including the
71
+ # boundary
72
+ #
73
+ def to_form
74
+ form = list.values.map {|p| "--#{self.boundary}\r\n#{p.to_form}" }.join
75
+ form << "--#{self.boundary}--"
76
+
77
+ form
78
+ end
79
+
80
+ def signature # :nodoc:
81
+ parameters_to_sign = @list.values.reject {|p| !p.include_in_signature? }
82
+ signature_text = parameters_to_sign.sort.map {|p| "#{p.name}#{p.value}" }.join
83
+
84
+ Digest::MD5.hexdigest("#{Fleakr.shared_secret}#{signature_text}")
85
+ end
86
+
87
+ private
88
+ def list
89
+ list = @list
90
+ list.merge!('api_sig' => ValueParameter.new('api_sig', signature, false)) if self.sign?
91
+
92
+ list
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,11 +1,11 @@
1
1
  module Fleakr
2
- module Api
2
+ module Api # :nodoc:
3
3
 
4
4
  # = Response
5
5
  #
6
6
  # Response objects contain Hpricot documents that are traversed and parsed by
7
7
  # the model objects. This class is never called directly but is instantiated
8
- # during the request cycle (see: Fleakr::Api::Request.with_response!)
8
+ # during the request cycle (see: Fleakr::Api::MethodRequest.with_response!)
9
9
  #
10
10
  class Response
11
11
 
@@ -0,0 +1,64 @@
1
+ module Fleakr
2
+ module Api # :nodoc:
3
+
4
+ # = UploadRequest
5
+ #
6
+ # This implements the upload functionality of the Flickr API which is needed
7
+ # to create new photos and replace the photo content of existing photos
8
+ #
9
+ class UploadRequest
10
+
11
+ ENDPOINT_URIS = {
12
+ :create => 'http://api.flickr.com/services/upload/',
13
+ :update => 'http://api.flickr.com/services/replace/'
14
+ }
15
+
16
+ attr_reader :parameters, :type
17
+
18
+ # Send a request and return a Response object. If an API error occurs, this raises
19
+ # a Fleakr::ApiError with the reason for the error. See UploadRequest#new for more
20
+ # details.
21
+ #
22
+ def self.with_response!(filename, options = {})
23
+ request = self.new(filename, options)
24
+ response = request.send
25
+
26
+ raise(Fleakr::ApiError, "Code: #{response.error.code} - #{response.error.message}") if response.error?
27
+
28
+ response
29
+ end
30
+
31
+ # Create a new UploadRequest with the specified filename and options:
32
+ #
33
+ # [:type] Valid values are :create and :update and are used when uploading new
34
+ # photos or replacing existing ones
35
+ #
36
+ def initialize(filename, options = {})
37
+ type_options = options.extract!(:type)
38
+ options.merge!(:authenticate? => true)
39
+
40
+ @type = type_options[:type] || :create
41
+
42
+ @parameters = ParameterList.new(options)
43
+ @parameters << FileParameter.new('photo', filename)
44
+ end
45
+
46
+ def headers # :nodoc:
47
+ {'Content-Type' => "multipart/form-data; boundary=#{self.parameters.boundary}"}
48
+ end
49
+
50
+ def send # :nodoc:
51
+ response = Net::HTTP.start(endpoint_uri.host, endpoint_uri.port) do |http|
52
+ http.post(endpoint_uri.path, self.parameters.to_form, self.headers)
53
+ end
54
+ Response.new(response.body)
55
+ end
56
+
57
+ private
58
+ def endpoint_uri
59
+ @endpoint_uri ||= URI.parse(ENDPOINT_URIS[self.type])
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ module Fleakr
2
+ module Api # :nodoc:
3
+
4
+ # = ValueParameter
5
+ #
6
+ # A simple name / value parameter for use in API calls
7
+ #
8
+ class ValueParameter < Parameter
9
+
10
+ attr_reader :value
11
+
12
+ # Create a new parameter with the specified name / value pair.
13
+ #
14
+ def initialize(name, value, include_in_signature = true)
15
+ @value = value
16
+ super(name, include_in_signature)
17
+ end
18
+
19
+ # Generate the query string representation of this parameter.
20
+ #
21
+ def to_query
22
+ "#{self.name}=#{CGI.escape(self.value.to_s)}"
23
+ end
24
+
25
+ # Generate the form representation of this parameter.
26
+ #
27
+ def to_form
28
+ "Content-Disposition: form-data; name=\"#{self.name}\"\r\n" +
29
+ "\r\n" +
30
+ "#{self.value}\r\n"
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ require 'fleakr/core_ext/hash'
@@ -0,0 +1,22 @@
1
+ class Hash
2
+
3
+ # Extract the matching keys from the source hash and return
4
+ # a new hash with those keys:
5
+ #
6
+ # >> h = {:a => 'b', :c => 'd'}
7
+ # => {:a=>"b", :c=>"d"}
8
+ # >> h.extract!(:a)
9
+ # => {:a=>"b"}
10
+ # >> h
11
+ # => {:c=>"d"}
12
+ #
13
+ def extract!(*keys)
14
+ value = {}
15
+
16
+ keys.each {|k| value.merge!({k => self[k]}) if self.has_key?(k) }
17
+ keys.each {|k| delete(k) }
18
+
19
+ value
20
+ end
21
+
22
+ end
@@ -0,0 +1,9 @@
1
+ require 'fleakr/objects/authentication_token'
2
+ require 'fleakr/objects/contact'
3
+ require 'fleakr/objects/error'
4
+ require 'fleakr/objects/group'
5
+ require 'fleakr/objects/image'
6
+ require 'fleakr/objects/photo'
7
+ require 'fleakr/objects/search'
8
+ require 'fleakr/objects/set'
9
+ require 'fleakr/objects/user'
@@ -0,0 +1,43 @@
1
+ module Fleakr
2
+ module Objects # :nodoc:
3
+
4
+ # = AuthenticationToken
5
+ #
6
+ # This class represents an authentication token used for API calls that
7
+ # require authentication before they can be used
8
+ #
9
+ # == Attributes
10
+ #
11
+ # [value] The token value that is used in subsequent API calls
12
+ # [permissions] The permissions granted to this application (read / write / delete)
13
+ #
14
+ class AuthenticationToken
15
+
16
+ include Fleakr::Support::Object
17
+
18
+ flickr_attribute :value, :from => 'auth/token'
19
+ flickr_attribute :permissions, :from => 'auth/perms'
20
+
21
+ # Retrieve a full authentication token from the supplied mini-token (e.g. 123-456-789)
22
+ #
23
+ def self.from_mini_token(token)
24
+ parameters = {:mini_token => token, :sign? => true}
25
+ response = Fleakr::Api::MethodRequest.with_response!('auth.getFullToken', parameters)
26
+
27
+ self.new(response.body)
28
+ end
29
+
30
+ # Retrieve a full authentication token from the supplied auth_token string
31
+ # (e.g. 45-76598454353455)
32
+ #
33
+ def self.from_auth_token(token)
34
+ parameters = {:auth_token => token, :sign? => true}
35
+ response = Fleakr::Api::MethodRequest.with_response!('auth.checkToken', parameters)
36
+
37
+ self.new(response.body)
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -4,15 +4,15 @@ module Fleakr
4
4
 
5
5
  include Fleakr::Support::Object
6
6
 
7
- flickr_attribute :id, :attribute => 'nsid'
8
- flickr_attribute :username, :attribute => 'username'
9
- flickr_attribute :icon_server, :attribute => 'iconserver'
10
- flickr_attribute :icon_farm, :attribute => 'iconfarm'
7
+ flickr_attribute :id, :from => '@nsid'
8
+ flickr_attribute :username
9
+ flickr_attribute :icon_server, :from => '@iconserver'
10
+ flickr_attribute :icon_farm, :from => '@iconfarm'
11
11
 
12
12
  # Retrieve a list of contacts for the specified user ID and return an initialized
13
13
  # collection of #User objects
14
14
  def self.find_all_by_user_id(user_id)
15
- response = Fleakr::Api::Request.with_response!('contacts.getPublicList', :user_id => user_id)
15
+ response = Fleakr::Api::MethodRequest.with_response!('contacts.getPublicList', :user_id => user_id)
16
16
  (response.body/'contacts/contact').map {|c| Contact.new(c).to_user }
17
17
  end
18
18
 
@@ -14,8 +14,8 @@ module Fleakr
14
14
 
15
15
  include Fleakr::Support::Object
16
16
 
17
- flickr_attribute :code, :xpath => 'rsp/err', :attribute => 'code'
18
- flickr_attribute :message, :xpath => 'rsp/err', :attribute => 'msg'
17
+ flickr_attribute :code, :from => 'err@code'
18
+ flickr_attribute :message, :from => 'err@msg'
19
19
 
20
20
  end
21
21
  end
@@ -12,8 +12,8 @@ module Fleakr
12
12
 
13
13
  include Fleakr::Support::Object
14
14
 
15
- flickr_attribute :id, :attribute => 'nsid'
16
- flickr_attribute :name, :attribute => 'name'
15
+ flickr_attribute :id, :from => '@nsid'
16
+ flickr_attribute :name
17
17
 
18
18
  find_all :by_user_id, :call => 'people.getPublicGroups', :path => 'groups/group'
19
19
 
@@ -24,11 +24,11 @@ module Fleakr
24
24
 
25
25
  include Fleakr::Support::Object
26
26
 
27
- flickr_attribute :size, :attribute => :label
28
- flickr_attribute :width, :attribute => :width
29
- flickr_attribute :height, :attribute => :height
30
- flickr_attribute :url, :attribute => :source
31
- flickr_attribute :page, :attribute => :url
27
+ flickr_attribute :size, :from => '@label'
28
+ flickr_attribute :width
29
+ flickr_attribute :height
30
+ flickr_attribute :url, :from => '@source'
31
+ flickr_attribute :page, :from => '@url'
32
32
 
33
33
  find_all :by_photo_id, :call => 'photos.getSizes', :path => 'sizes/size'
34
34
 
@@ -41,8 +41,8 @@ module Fleakr
41
41
  # directory, the file will be created with the original filename from Flickr.
42
42
  # If the target is a file, it will be saved with the specified name. In the
43
43
  # case that the target file already exists, this method will overwrite it.
44
- def save_to(target)
45
- destination = File.directory?(target) ? "#{target}/#{self.filename}" : "#{target}"
44
+ def save_to(target, prefix = nil)
45
+ destination = File.directory?(target) ? "#{target}/#{prefix}#{self.filename}" : "#{target}"
46
46
  File.open(destination, 'w') {|f| f << Net::HTTP.get(URI.parse(self.url)) }
47
47
  end
48
48
 
@@ -7,6 +7,10 @@ module Fleakr
7
7
  #
8
8
  # [id] The ID for this photo
9
9
  # [title] The title of this photo
10
+ # [description] The description of this photo
11
+ # [secret] This photo's secret (used for sharing photo without permissions checking)
12
+ # [comment_count] Count of the comments attached to this photo
13
+ # [url] This photo's page on Flickr
10
14
  # [square] The tiny square representation of this photo
11
15
  # [thumbnail] The thumbnail for this photo
12
16
  # [small] The small representation of this photo
@@ -25,18 +29,78 @@ module Fleakr
25
29
 
26
30
  include Fleakr::Support::Object
27
31
 
28
- flickr_attribute :title, :attribute => 'title'
29
- flickr_attribute :id, :attribute => 'id'
30
- flickr_attribute :farm_id, :attribute => 'farm'
31
- flickr_attribute :server_id, :attribute => 'server'
32
- flickr_attribute :secret, :attribute => 'secret'
32
+ flickr_attribute :id, :from => ['@id', 'photoid']
33
+ flickr_attribute :title
34
+ flickr_attribute :description
35
+ flickr_attribute :farm_id, :from => '@farm'
36
+ flickr_attribute :server_id, :from => '@server'
37
+ flickr_attribute :secret
38
+ flickr_attribute :posted
39
+ flickr_attribute :taken
40
+ flickr_attribute :updated, :from => '@lastupdate'
41
+ flickr_attribute :comment_count, :from => 'comments'
42
+ flickr_attribute :url
43
+
44
+ # TODO:
45
+ # * owner (user)
46
+ # * visibility
47
+ # * editability
48
+ # * usage
49
+ # * notes
50
+ # * tags
33
51
 
34
52
  find_all :by_photoset_id, :call => 'photosets.getPhotos', :path => 'photoset/photo'
35
53
  find_all :by_user_id, :call => 'people.getPublicPhotos', :path => 'photos/photo'
36
54
  find_all :by_group_id, :call => 'groups.pools.getPhotos', :path => 'photos/photo'
37
55
 
56
+ find_one :by_id, :using => :photo_id, :call => 'photos.getInfo', :authenticate? => true
57
+
58
+ lazily_load :posted, :taken, :updated, :comment_count, :url, :description, :with => :load_info
59
+
38
60
  has_many :images
39
61
 
62
+ # Upload the photo specified by <tt>filename</tt> to the user's Flickr account. This
63
+ # call requires authentication.
64
+ #
65
+ def self.upload(filename)
66
+ response = Fleakr::Api::UploadRequest.with_response!(filename)
67
+ photo = Photo.new(response.body)
68
+ Photo.find_by_id(photo.id, :authenticate? => true)
69
+ end
70
+
71
+ # Replace the current photo's image with the one specified by filename. This
72
+ # call requires authentication.
73
+ #
74
+ def replace_with(filename)
75
+ response = Fleakr::Api::UploadRequest.with_response!(filename, :photo_id => self.id, :type => :update)
76
+ self.populate_from(response.body)
77
+ self
78
+ end
79
+
80
+ # TODO: Refactor this to remove duplication w/ User#load_info - possibly in the lazily_load class method
81
+ def load_info # :nodoc:
82
+ response = Fleakr::Api::MethodRequest.with_response!('photos.getInfo', :photo_id => self.id)
83
+ self.populate_from(response.body)
84
+ end
85
+
86
+ # When was this photo posted?
87
+ #
88
+ def posted_at
89
+ Time.at(posted.to_i)
90
+ end
91
+
92
+ # When was this photo taken?
93
+ #
94
+ def taken_at
95
+ Time.parse(taken)
96
+ end
97
+
98
+ # When was this photo last updated? This includes addition of tags and other metadata.
99
+ #
100
+ def updated_at
101
+ Time.at(updated.to_i)
102
+ end
103
+
40
104
  # Create methods to access image sizes by name
41
105
  SIZES.each do |size|
42
106
  define_method(size) do