koala 1.1.0rc2 → 1.1.0rc3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ module Koala
2
+ module Facebook
3
+ module GraphBatchAPIMethods
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method :graph_call_outside_batch, :graph_call
8
+ alias_method :graph_call, :graph_call_in_batch
9
+
10
+ alias_method :check_graph_api_response, :check_response
11
+ alias_method :check_response, :check_graph_batch_api_response
12
+ end
13
+ end
14
+
15
+ def batch_calls
16
+ @batch_calls ||= []
17
+ end
18
+
19
+ def graph_call_in_batch(path, args = {}, verb = "get", options = {}, &post_processing)
20
+ # for batch APIs, we queue up the call details (incl. post-processing)
21
+ batch_calls << BatchOperation.new(
22
+ :url => path,
23
+ :args => args,
24
+ :method => verb,
25
+ :access_token => options['access_token'] || access_token,
26
+ :http_options => options,
27
+ :post_processing => post_processing
28
+ )
29
+ nil # batch operations return nothing immediately
30
+ end
31
+
32
+ def check_graph_batch_api_response(response)
33
+ if response.is_a?(Hash) && response["error"] && !response["error"].is_a?(Hash)
34
+ APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"])
35
+ else
36
+ check_graph_api_response(response)
37
+ end
38
+ end
39
+
40
+ def execute(http_options = {})
41
+ return [] unless batch_calls.length > 0
42
+ # Turn the call args collected into what facebook expects
43
+ args = {}
44
+ args["batch"] = MultiJson.encode(batch_calls.map { |batch_op|
45
+ args.merge!(batch_op.files) if batch_op.files
46
+ batch_op.to_batch_params(access_token)
47
+ })
48
+
49
+ graph_call_outside_batch('/', args, 'post', http_options) do |response|
50
+ # map the results with post-processing included
51
+ index = 0 # keep compat with ruby 1.8 - no with_index for map
52
+ response.map do |call_result|
53
+ # Get the options hash
54
+ batch_op = batch_calls[index]
55
+ index += 1
56
+
57
+ if call_result
58
+ # (see note in regular api method about JSON parsing)
59
+ body = MultiJson.decode("[#{call_result['body'].to_s}]")[0]
60
+
61
+ unless call_result["code"].to_i >= 500 || error = check_response(body)
62
+ # Get the HTTP component they want
63
+ data = case batch_op.http_options[:http_component]
64
+ when :status
65
+ call_result["code"].to_i
66
+ when :headers
67
+ # facebook returns the headers as an array of k/v pairs, but we want a regular hash
68
+ call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
69
+ else
70
+ body
71
+ end
72
+
73
+ # process it if we are given a block to process with
74
+ batch_op.post_processing ? batch_op.post_processing.call(data) : data
75
+ else
76
+ error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
77
+ end
78
+ else
79
+ nil
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,54 @@
1
+ module Koala
2
+ module Facebook
3
+ class GraphCollection < Array
4
+ # This class is a light wrapper for collections returned
5
+ # from the Graph API.
6
+ #
7
+ # It extends Array to allow direct access to the data colleciton
8
+ # which should allow it to drop in seamlessly.
9
+ #
10
+ # It also allows access to paging information and the
11
+ # ability to get the next/previous page in the collection
12
+ # by calling next_page or previous_page.
13
+ attr_reader :paging
14
+ attr_reader :api
15
+
16
+ def initialize(response, api)
17
+ super response["data"]
18
+ @paging = response["paging"]
19
+ @api = api
20
+ end
21
+
22
+ # defines methods for NEXT and PREVIOUS pages
23
+ %w{next previous}.each do |this|
24
+
25
+ # def next_page
26
+ # def previous_page
27
+ define_method "#{this.to_sym}_page" do
28
+ base, args = send("#{this}_page_params")
29
+ base ? @api.get_page([base, args]) : nil
30
+ end
31
+
32
+ # def next_page_params
33
+ # def previous_page_params
34
+ define_method "#{this.to_sym}_page_params" do
35
+ return nil unless @paging and @paging[this]
36
+ parse_page_url(@paging[this])
37
+ end
38
+ end
39
+
40
+ def parse_page_url(url)
41
+ match = url.match(/.com\/(.*)\?(.*)/)
42
+ base = match[1]
43
+ args = match[2]
44
+ params = CGI.parse(args)
45
+ new_params = {}
46
+ params.each_pair do |key,value|
47
+ new_params[key] = value.join ","
48
+ end
49
+ [base,new_params]
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -13,18 +13,20 @@ module Koala
13
13
  def self.included(base)
14
14
  base.class_eval do
15
15
  class << self
16
- attr_accessor :always_use_ssl, :proxy, :timeout, :ca_path, :ca_file
16
+ attr_accessor :always_use_ssl, :proxy, :timeout
17
17
  end
18
18
 
19
19
  def self.server(options = {})
20
- "#{options[:beta] ? "beta." : ""}#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
20
+ server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
21
+ server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
22
+ "#{options[:beta] ? "beta." : ""}#{server}"
21
23
  end
22
24
 
23
25
  def self.encode_params(param_hash)
24
26
  # unfortunately, we can't use to_query because that's Rails, not Ruby
25
27
  # if no hash (e.g. no auth token) return empty string
26
28
  ((param_hash || {}).collect do |key_and_value|
27
- key_and_value[1] = key_and_value[1].to_json unless key_and_value[1].is_a? String
29
+ key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
28
30
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
29
31
  end).join("&")
30
32
  end
@@ -7,6 +7,11 @@ module Koala
7
7
  # this service uses Net::HTTP to send requests to the graph
8
8
  include Koala::HTTPService
9
9
 
10
+ # Net::HTTP-specific values
11
+ class << self
12
+ attr_accessor :ca_file, :ca_path, :verify_mode
13
+ end
14
+
10
15
  def self.make_request(path, args, verb, options = {})
11
16
  # We translate args to a valid query string. If post is specified,
12
17
  # we send a POST request to the given path with the given arguments.
@@ -15,17 +20,13 @@ module Koala
15
20
  # this makes public requests faster
16
21
  private_request = args["access_token"] || @always_use_ssl || options[:use_ssl]
17
22
 
18
- # if proxy/timeout options aren't passed, check if defaults are set
19
- options[:proxy] ||= proxy
20
- options[:timeout] ||= timeout
21
-
22
23
  # if the verb isn't get or post, send it as a post argument
23
24
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
24
25
 
25
26
  http = create_http(server(options), private_request, options)
26
27
 
27
- result = http.start do |http|
28
- response, body = if verb == "post"
28
+ response = http.start do |http|
29
+ if verb == "post"
29
30
  if params_require_multipart? args
30
31
  http.request Net::HTTP::Post::Multipart.new path, encode_multipart_params(args)
31
32
  else
@@ -34,9 +35,9 @@ module Koala
34
35
  else
35
36
  http.get("#{path}?#{encode_params(args)}")
36
37
  end
37
-
38
- Koala::Response.new(response.code.to_i, body, response)
39
38
  end
39
+
40
+ Koala::Response.new(response.code.to_i, response.body, response)
40
41
  end
41
42
 
42
43
  protected
@@ -44,44 +45,48 @@ module Koala
44
45
  # unfortunately, we can't use to_query because that's Rails, not Ruby
45
46
  # if no hash (e.g. no auth token) return empty string
46
47
  ((param_hash || {}).collect do |key_and_value|
47
- key_and_value[1] = key_and_value[1].to_json if key_and_value[1].class != String
48
+ key_and_value[1] = MultiJson.encode(key_and_value[1]) if key_and_value[1].class != String
48
49
  "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
49
50
  end).join("&")
50
51
  end
51
-
52
+
52
53
  def self.encode_multipart_params(param_hash)
53
- Hash[*param_hash.collect do |key, value|
54
+ Hash[*param_hash.collect do |key, value|
54
55
  [key, value.kind_of?(Koala::UploadableIO) ? value.to_upload_io : value]
55
56
  end.flatten]
56
57
  end
57
58
 
58
59
  def self.create_http(server, private_request, options)
59
- if options[:proxy]
60
- proxy = URI.parse(options[:proxy])
60
+ if proxy_server = options[:proxy] || proxy
61
+ proxy = URI.parse(proxy_server)
61
62
  http = Net::HTTP.new(server, private_request ? 443 : nil,
62
63
  proxy.host, proxy.port, proxy.user, proxy.password)
63
64
  else
64
- http = Net::HTTP.new(server, private_request ? 443 : nil)
65
+ http = Net::HTTP.new(server, private_request ? 443 : nil)
65
66
  end
66
-
67
- if options[:timeout]
68
- http.open_timeout = options[:timeout]
69
- http.read_timeout = options[:timeout]
67
+
68
+ if timeout_value = options[:timeout] || timeout
69
+ http.open_timeout = timeout_value
70
+ http.read_timeout = timeout_value
70
71
  end
71
-
72
+
72
73
  # For HTTPS requests, set the proper CA certificates
73
74
  if private_request
74
75
  http.use_ssl = true
75
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
76
+ http.verify_mode = options[:verify_mode] || verify_mode || OpenSSL::SSL::VERIFY_PEER
76
77
 
77
- options[:ca_file] ||= ca_file
78
- http.ca_file = options[:ca_file] if options[:ca_file] && File.exists?(options[:ca_file])
78
+ if cert_file = options[:ca_file] || ca_file
79
+ raise Errno::ENOENT, "Certificate file #{cert_file.inspect} does not exist!" unless File.exists?(cert_file)
80
+ http.ca_file = cert_file
81
+ end
79
82
 
80
- options[:ca_path] ||= ca_path
81
- http.ca_path = options[:ca_path] if options[:ca_path] && Dir.exists?(options[:ca_path])
83
+ if cert_path = options[:ca_path] || ca_path
84
+ raise Errno::ENOENT, "Certificate path #{cert_path.inspect} does not exist!" unless File.directory?(cert_path)
85
+ http.ca_path = cert_path
86
+ end
82
87
  end
83
-
88
+
84
89
  http
85
90
  end
86
91
  end
87
- end
92
+ end
@@ -3,14 +3,14 @@ require "typhoeus" unless defined?(Typhoeus)
3
3
  module Koala
4
4
  module TyphoeusService
5
5
  # this service uses Typhoeus to send requests to the graph
6
- include Typhoeus
6
+ include Typhoeus
7
7
  include Koala::HTTPService
8
8
 
9
9
  def self.make_request(path, args, verb, options = {})
10
10
  # if the verb isn't get or post, send it as a post argument
11
11
  args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
12
12
 
13
- # switch any UploadableIOs to the files Typhoeus expects
13
+ # switch any UploadableIOs to the files Typhoeus expects
14
14
  args.each_pair {|key, value| args[key] = value.to_file if value.is_a?(UploadableIO)}
15
15
 
16
16
  # you can pass arguments directly to Typhoeus using the :typhoeus_options key
@@ -27,9 +27,9 @@ module Koala
27
27
  response = Typhoeus::Request.send(verb, "#{prefix}://#{server(options)}#{path}", typhoeus_options)
28
28
  Koala::Response.new(response.code, response.body, response.headers_hash)
29
29
  end
30
-
30
+
31
31
  protected
32
-
32
+
33
33
  def self.multipart_requires_content_type?
34
34
  false # Typhoeus handles multipart file types, we don't have to require it
35
35
  end
data/lib/koala/oauth.rb CHANGED
@@ -99,7 +99,7 @@ module Koala
99
99
  def parse_signed_request(input)
100
100
  encoded_sig, encoded_envelope = input.split('.', 2)
101
101
  signature = base64_url_decode(encoded_sig).unpack("H*").first
102
- envelope = JSON.parse(base64_url_decode(encoded_envelope))
102
+ envelope = MultiJson.decode(base64_url_decode(encoded_envelope))
103
103
 
104
104
  raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
105
105
 
@@ -126,7 +126,7 @@ module Koala
126
126
  })
127
127
  end
128
128
 
129
- JSON.parse(response)
129
+ MultiJson.decode(response)
130
130
  end
131
131
 
132
132
  def get_tokens_from_session_keys(sessions, options = {})
@@ -149,7 +149,7 @@ module Koala
149
149
  result = fetch_token_string(args, post, "access_token", options)
150
150
 
151
151
  # if we have an error, parse the error JSON and raise an error
152
- raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
152
+ raise APIError.new((MultiJson.decode(result)["error"] rescue nil) || {}) if result =~ /error/
153
153
 
154
154
  # otherwise, parse the access token
155
155
  parse_access_token(result)
@@ -8,7 +8,7 @@ module Koala
8
8
  end
9
9
 
10
10
  def fql_multiquery(queries = {}, args = {}, options = {})
11
- if results = rest_call('fql.multiquery', args.merge(:queries => queries.to_json), options)
11
+ if results = rest_call('fql.multiquery', args.merge(:queries => MultiJson.encode(queries)), options)
12
12
  # simplify the multiquery result format
13
13
  results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
14
14
  end
@@ -36,108 +36,140 @@ module Koala
36
36
  end
37
37
 
38
38
  private
39
- DETECTION_STRATEGIES = [
40
- :sinatra_param?,
41
- :rails_3_param?,
42
- :file_param?
43
- ]
39
+ DETECTION_STRATEGIES = [
40
+ :sinatra_param?,
41
+ :rails_3_param?,
42
+ :file_param?
43
+ ]
44
+
45
+ PARSE_STRATEGIES = [
46
+ :parse_rails_3_param,
47
+ :parse_sinatra_param,
48
+ :parse_file_object,
49
+ :parse_string_path
50
+ ]
44
51
 
45
- PARSE_STRATEGIES = [
46
- :parse_rails_3_param,
47
- :parse_sinatra_param,
48
- :parse_file_object,
49
- :parse_string_path
50
- ]
51
-
52
- def parse_init_mixed_param(mixed)
53
- PARSE_STRATEGIES.each do |method|
54
- send(method, mixed)
55
- return if @io_or_path && @content_type
56
- end
57
- end
58
-
59
- # Expects a parameter of type ActionDispatch::Http::UploadedFile
60
- def self.rails_3_param?(uploaded_file)
61
- uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
62
- end
63
-
64
- def parse_rails_3_param(uploaded_file)
65
- if UploadableIO.rails_3_param?(uploaded_file)
66
- @io_or_path = uploaded_file.tempfile.path
67
- @content_type = uploaded_file.content_type
68
- end
69
- end
70
-
71
- # Expects a Sinatra hash of file info
72
- def self.sinatra_param?(file_hash)
73
- file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
52
+ def parse_init_mixed_param(mixed)
53
+ PARSE_STRATEGIES.each do |method|
54
+ send(method, mixed)
55
+ return if @io_or_path && @content_type
74
56
  end
75
-
76
- def parse_sinatra_param(file_hash)
77
- if UploadableIO.sinatra_param?(file_hash)
78
- @io_or_path = file_hash[:tempfile]
79
- @content_type = file_hash[:type] || detect_mime_type(tempfile)
80
- end
57
+ end
58
+
59
+ # Expects a parameter of type ActionDispatch::Http::UploadedFile
60
+ def self.rails_3_param?(uploaded_file)
61
+ uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
62
+ end
63
+
64
+ def parse_rails_3_param(uploaded_file)
65
+ if UploadableIO.rails_3_param?(uploaded_file)
66
+ @io_or_path = uploaded_file.tempfile.path
67
+ @content_type = uploaded_file.content_type
81
68
  end
82
-
83
- # takes a file object
84
- def self.file_param?(file)
85
- file.kind_of?(File)
69
+ end
70
+
71
+ # Expects a Sinatra hash of file info
72
+ def self.sinatra_param?(file_hash)
73
+ file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
74
+ end
75
+
76
+ def parse_sinatra_param(file_hash)
77
+ if UploadableIO.sinatra_param?(file_hash)
78
+ @io_or_path = file_hash[:tempfile]
79
+ @content_type = file_hash[:type] || detect_mime_type(tempfile)
86
80
  end
87
-
88
- def parse_file_object(file)
89
- if UploadableIO.file_param?(file)
90
- @io_or_path = file
91
- @content_type = detect_mime_type(file.path)
92
- end
81
+ end
82
+
83
+ # takes a file object
84
+ def self.file_param?(file)
85
+ file.kind_of?(File)
86
+ end
87
+
88
+ def parse_file_object(file)
89
+ if UploadableIO.file_param?(file)
90
+ @io_or_path = file
91
+ @content_type = detect_mime_type(file.path)
93
92
  end
94
-
95
- def parse_string_path(path)
96
- if path.kind_of?(String)
97
- @io_or_path = path
98
- @content_type = detect_mime_type(path)
99
- end
93
+ end
94
+
95
+ def parse_string_path(path)
96
+ if path.kind_of?(String)
97
+ @io_or_path = path
98
+ @content_type = detect_mime_type(path)
100
99
  end
101
-
102
- MIME_TYPE_STRATEGIES = [
103
- :use_mime_module,
104
- :use_simple_detection
105
- ]
106
-
107
- def detect_mime_type(filename)
108
- if filename
109
- MIME_TYPE_STRATEGIES.each do |method|
110
- result = send(method, filename)
111
- return result if result
112
- end
100
+ end
101
+
102
+ MIME_TYPE_STRATEGIES = [
103
+ :use_mime_module,
104
+ :use_simple_detection
105
+ ]
106
+
107
+ def detect_mime_type(filename)
108
+ if filename
109
+ MIME_TYPE_STRATEGIES.each do |method|
110
+ result = send(method, filename)
111
+ return result if result
113
112
  end
114
- nil # if we can't find anything
115
113
  end
116
-
117
- def use_mime_module(filename)
118
- # if the user has installed mime/types, we can use that
119
- # if not, rescue and return nil
120
- begin
121
- type = MIME::Types.type_for(filename).first
122
- type ? type.to_s : nil
123
- rescue
124
- nil
125
- end
114
+ nil # if we can't find anything
115
+ end
116
+
117
+ def use_mime_module(filename)
118
+ # if the user has installed mime/types, we can use that
119
+ # if not, rescue and return nil
120
+ begin
121
+ type = MIME::Types.type_for(filename).first
122
+ type ? type.to_s : nil
123
+ rescue
124
+ nil
126
125
  end
127
-
128
- def use_simple_detection(filename)
129
- # very rudimentary extension analysis for images
130
- # first, get the downcased extension, or an empty string if it doesn't exist
131
- extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
132
- if extension == ""
126
+ end
127
+
128
+ def use_simple_detection(filename)
129
+ # very rudimentary extension analysis for images
130
+ # first, get the downcased extension, or an empty string if it doesn't exist
131
+ extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
132
+ case extension
133
+ when ""
133
134
  nil
134
- elsif extension == "jpg" || extension == "jpeg"
135
+ # images
136
+ when "jpg", "jpeg"
135
137
  "image/jpeg"
136
- elsif extension == "png"
138
+ when "png"
137
139
  "image/png"
138
- elsif extension == "gif"
140
+ when "gif"
139
141
  "image/gif"
140
- end
141
- end
142
+
143
+ # video
144
+ when "3g2"
145
+ "video/3gpp2"
146
+ when "3gp", "3gpp"
147
+ "video/3gpp"
148
+ when "asf"
149
+ "video/x-ms-asf"
150
+ when "avi"
151
+ "video/x-msvideo"
152
+ when "flv"
153
+ "video/x-flv"
154
+ when "m4v"
155
+ "video/x-m4v"
156
+ when "mkv"
157
+ "video/x-matroska"
158
+ when "mod"
159
+ "video/mod"
160
+ when "mov", "qt"
161
+ "video/quicktime"
162
+ when "mp4", "mpeg4"
163
+ "video/mp4"
164
+ when "mpe", "mpeg", "mpg", "tod", "vob"
165
+ "video/mpeg"
166
+ when "nsv"
167
+ "application/x-winamp"
168
+ when "ogm", "ogv"
169
+ "video/ogg"
170
+ when "wmv"
171
+ "video/x-ms-wmv"
172
+ end
173
+ end
142
174
  end
143
175
  end