reagent-fleakr 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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