koala 1.1.0rc2 → 1.1.0rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +11 -6
- data/koala.gemspec +5 -5
- data/lib/koala.rb +10 -5
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +78 -117
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_services.rb +5 -3
- data/lib/koala/http_services/net_http_service.rb +31 -26
- data/lib/koala/http_services/typhoeus_service.rb +4 -4
- data/lib/koala/oauth.rb +3 -3
- data/lib/koala/rest_api.rb +1 -1
- data/lib/koala/uploadable_io.rb +122 -90
- data/readme.md +6 -6
- data/spec/cases/api_base_spec.rb +2 -2
- data/spec/cases/graph_api_batch_spec.rb +171 -162
- data/spec/cases/http_services/http_service_spec.rb +27 -27
- data/spec/cases/http_services/net_http_service_spec.rb +169 -103
- data/spec/cases/oauth_spec.rb +1 -1
- data/spec/cases/realtime_updates_spec.rb +3 -3
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/mock_facebook_responses.yml +33 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/graph_api_shared_examples.rb +79 -35
- data/spec/support/mock_http_service.rb +3 -0
- data/spec/support/rest_api_shared_examples.rb +2 -2
- data/spec/support/setup_mocks_or_live.rb +1 -2
- metadata +10 -5
- data/lib/koala/graph_api_batch.rb +0 -151
@@ -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
|
data/lib/koala/http_services.rb
CHANGED
@@ -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
|
16
|
+
attr_accessor :always_use_ssl, :proxy, :timeout
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.server(options = {})
|
20
|
-
|
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]
|
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
|
-
|
28
|
-
|
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]
|
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(
|
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
|
65
|
+
http = Net::HTTP.new(server, private_request ? 443 : nil)
|
65
66
|
end
|
66
|
-
|
67
|
-
if options[:timeout]
|
68
|
-
http.open_timeout =
|
69
|
-
http.read_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]
|
78
|
-
|
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]
|
81
|
-
|
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 =
|
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
|
-
|
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((
|
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)
|
data/lib/koala/rest_api.rb
CHANGED
@@ -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
|
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
|
data/lib/koala/uploadable_io.rb
CHANGED
@@ -36,108 +36,140 @@ module Koala
|
|
36
36
|
end
|
37
37
|
|
38
38
|
private
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
135
|
+
# images
|
136
|
+
when "jpg", "jpeg"
|
135
137
|
"image/jpeg"
|
136
|
-
|
138
|
+
when "png"
|
137
139
|
"image/png"
|
138
|
-
|
140
|
+
when "gif"
|
139
141
|
"image/gif"
|
140
|
-
|
141
|
-
|
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
|