koala 1.1.0rc2 → 1.1.0rc3
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.
- 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
|