koala 1.3.0rc1 → 1.3.0rc2
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 +3 -1
- data/.travis.yml +4 -0
- data/.yardopts +3 -0
- data/CHANGELOG +7 -0
- data/Gemfile +14 -0
- data/Guardfile +6 -0
- data/lib/koala.rb +16 -97
- data/lib/koala/api.rb +93 -0
- data/lib/koala/api/batch_operation.rb +83 -0
- data/lib/koala/api/graph_api.rb +476 -0
- data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +20 -16
- data/lib/koala/api/graph_collection.rb +107 -0
- data/lib/koala/api/legacy.rb +26 -0
- data/lib/koala/{rest_api.rb → api/rest_api.rb} +33 -3
- data/lib/koala/http_service.rb +69 -19
- data/lib/koala/http_service/multipart_request.rb +41 -0
- data/lib/koala/http_service/response.rb +18 -0
- data/lib/koala/http_service/uploadable_io.rb +187 -0
- data/lib/koala/oauth.rb +117 -14
- data/lib/koala/realtime_updates.rb +89 -51
- data/lib/koala/test_users.rb +109 -33
- data/lib/koala/utils.rb +4 -0
- data/lib/koala/version.rb +1 -1
- data/spec/cases/api_spec.rb +19 -12
- data/spec/cases/graph_api_batch_spec.rb +41 -41
- data/spec/cases/http_service_spec.rb +1 -22
- data/spec/cases/legacy_spec.rb +107 -0
- data/spec/cases/multipart_request_spec.rb +5 -5
- data/spec/cases/oauth_spec.rb +9 -9
- data/spec/cases/realtime_updates_spec.rb +154 -47
- data/spec/cases/test_users_spec.rb +268 -219
- data/spec/fixtures/mock_facebook_responses.yml +10 -6
- data/spec/support/graph_api_shared_examples.rb +17 -12
- data/spec/support/koala_test.rb +1 -1
- data/spec/support/mock_http_service.rb +2 -2
- data/spec/support/rest_api_shared_examples.rb +1 -1
- metadata +82 -104
- data/lib/koala/batch_operation.rb +0 -74
- data/lib/koala/graph_api.rb +0 -289
- data/lib/koala/graph_collection.rb +0 -63
- data/lib/koala/multipart_request.rb +0 -35
- data/lib/koala/uploadable_io.rb +0 -181
- data/spec/cases/graph_and_rest_api_spec.rb +0 -22
- data/spec/cases/graph_api_spec.rb +0 -22
- data/spec/cases/rest_api_spec.rb +0 -22
data/lib/koala/graph_api.rb
DELETED
@@ -1,289 +0,0 @@
|
|
1
|
-
module Koala
|
2
|
-
module Facebook
|
3
|
-
GRAPH_SERVER = "graph.facebook.com"
|
4
|
-
|
5
|
-
module GraphAPIMethods
|
6
|
-
# A client for the Facebook Graph API.
|
7
|
-
#
|
8
|
-
# See http://github.com/arsduo/koala for Ruby/Koala documentation
|
9
|
-
# and http://developers.facebook.com/docs/api for Facebook API documentation
|
10
|
-
#
|
11
|
-
# The Graph API is made up of the objects in Facebook (e.g., people, pages,
|
12
|
-
# events, photos) and the connections between them (e.g., friends,
|
13
|
-
# photo tags, and event RSVPs). This client provides access to those
|
14
|
-
# primitive types in a generic way. For example, given an OAuth access
|
15
|
-
# token, this will fetch the profile of the active user and the list
|
16
|
-
# of the user's friends:
|
17
|
-
#
|
18
|
-
# graph = Koala::Facebook::API.new(access_token)
|
19
|
-
# user = graph.get_object("me")
|
20
|
-
# friends = graph.get_connections(user["id"], "friends")
|
21
|
-
#
|
22
|
-
# You can see a list of all of the objects and connections supported
|
23
|
-
# by the API at http://developers.facebook.com/docs/reference/api/.
|
24
|
-
#
|
25
|
-
# You can obtain an access token via OAuth or by using the Facebook
|
26
|
-
# JavaScript SDK. See the Koala and Facebook documentation for more information.
|
27
|
-
#
|
28
|
-
# If you are using the JavaScript SDK, you can use the
|
29
|
-
# Koala::Facebook::OAuth.get_user_from_cookie() method below to get the OAuth access token
|
30
|
-
# for the active user from the cookie saved by the SDK.
|
31
|
-
|
32
|
-
# Objects
|
33
|
-
|
34
|
-
def get_object(id, args = {}, options = {})
|
35
|
-
# Fetchs the given object from the graph.
|
36
|
-
graph_call(id, args, "get", options)
|
37
|
-
end
|
38
|
-
|
39
|
-
def get_objects(ids, args = {}, options = {})
|
40
|
-
# Fetchs all of the given objects from the graph.
|
41
|
-
# If any of the IDs are invalid, they'll raise an exception.
|
42
|
-
return [] if ids.empty?
|
43
|
-
graph_call("", args.merge("ids" => ids.respond_to?(:join) ? ids.join(",") : ids), "get", options)
|
44
|
-
end
|
45
|
-
|
46
|
-
def put_object(parent_object, connection_name, args = {}, options = {})
|
47
|
-
# Writes the given object to the graph, connected to the given parent.
|
48
|
-
# See http://developers.facebook.com/docs/api#publishing for all of
|
49
|
-
# the supported writeable objects.
|
50
|
-
#
|
51
|
-
# For example,
|
52
|
-
# graph.put_object("me", "feed", :message => "Hello, world")
|
53
|
-
# writes "Hello, world" to the active user's wall.
|
54
|
-
#
|
55
|
-
# Most write operations require extended permissions. For example,
|
56
|
-
# publishing wall posts requires the "publish_stream" permission. See
|
57
|
-
# http://developers.facebook.com/docs/authentication/ for details about
|
58
|
-
# extended permissions.
|
59
|
-
|
60
|
-
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
61
|
-
graph_call("#{parent_object}/#{connection_name}", args, "post", options)
|
62
|
-
end
|
63
|
-
|
64
|
-
def delete_object(id, options = {})
|
65
|
-
# Deletes the object with the given ID from the graph.
|
66
|
-
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
|
67
|
-
graph_call(id, {}, "delete", options)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Connections
|
71
|
-
|
72
|
-
def get_connections(id, connection_name, args = {}, options = {})
|
73
|
-
# Fetchs the connections for given object.
|
74
|
-
graph_call("#{id}/#{connection_name}", args, "get", options)
|
75
|
-
end
|
76
|
-
|
77
|
-
def put_connections(id, connection_name, args = {}, options = {})
|
78
|
-
# Posts a certain connection
|
79
|
-
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Write operations require an access token"}) unless @access_token
|
80
|
-
graph_call("#{id}/#{connection_name}", args, "post", options)
|
81
|
-
end
|
82
|
-
|
83
|
-
def delete_connections(id, connection_name, args = {}, options = {})
|
84
|
-
# Deletes a given connection
|
85
|
-
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Delete requires an access token"}) unless @access_token
|
86
|
-
graph_call("#{id}/#{connection_name}", args, "delete", options)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Media (photos and videos)
|
90
|
-
# to delete photos or videos, use delete_object(object_id)
|
91
|
-
# note: you'll need the user_photos or user_videos permissions to actually access media after upload
|
92
|
-
|
93
|
-
def get_picture(object, args = {}, options = {})
|
94
|
-
# Gets a picture object, returning the URL (which Facebook sends as a header)
|
95
|
-
graph_call("#{object}/picture", args, "get", options.merge(:http_component => :headers)) do |result|
|
96
|
-
result["Location"]
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Can be called in multiple ways:
|
101
|
-
#
|
102
|
-
# put_picture(file, [content_type], ...)
|
103
|
-
# put_picture(path_to_file, [content_type], ...)
|
104
|
-
# put_picture(picture_url, ...)
|
105
|
-
#
|
106
|
-
# You can pass in uploaded files directly from Rails or Sinatra.
|
107
|
-
# (See lib/koala/uploadable_io.rb for supported frameworks)
|
108
|
-
#
|
109
|
-
# Optional parameters can be added to the end of the argument list:
|
110
|
-
# - args: a hash of request parameters (default: {})
|
111
|
-
# - target_id: ID of the target where to post the picture (default: "me")
|
112
|
-
# - options: a hash of http options passed to the HTTPService module
|
113
|
-
#
|
114
|
-
# put_picture(file, content_type, {:message => "Message"}, 01234560)
|
115
|
-
# put_picture(params[:file], {:message => "Message"})
|
116
|
-
#
|
117
|
-
# (Note that with URLs, there's no optional content type field)
|
118
|
-
# put_picture(picture_url, {:message => "Message"}, my_page_id)
|
119
|
-
|
120
|
-
def put_picture(*picture_args)
|
121
|
-
put_object(*parse_media_args(picture_args, "photos"))
|
122
|
-
end
|
123
|
-
|
124
|
-
def put_video(*video_args)
|
125
|
-
args = parse_media_args(video_args, "videos")
|
126
|
-
args.last[:video] = true
|
127
|
-
put_object(*args)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Wall posts
|
131
|
-
# To get wall posts, use get_connections(user, "feed")
|
132
|
-
# To delete a wall post, just use delete_object(post_id)
|
133
|
-
|
134
|
-
def put_wall_post(message, attachment = {}, profile_id = "me", options = {})
|
135
|
-
# attachment is a hash describing the wall post
|
136
|
-
# (see X for more details)
|
137
|
-
# For instance,
|
138
|
-
#
|
139
|
-
# {"name" => "Link name"
|
140
|
-
# "link" => "http://www.example.com/",
|
141
|
-
# "caption" => "{*actor*} posted a new review",
|
142
|
-
# "description" => "This is a longer description of the attachment",
|
143
|
-
# "picture" => "http://www.example.com/thumbnail.jpg"}
|
144
|
-
|
145
|
-
self.put_object(profile_id, "feed", attachment.merge({:message => message}), options)
|
146
|
-
end
|
147
|
-
|
148
|
-
# Comments
|
149
|
-
# to delete comments, use delete_object(comment_id)
|
150
|
-
# to get comments, use get_connections(object, "likes")
|
151
|
-
|
152
|
-
def put_comment(object_id, message, options = {})
|
153
|
-
# Writes the given comment on the given post.
|
154
|
-
self.put_object(object_id, "comments", {:message => message}, options)
|
155
|
-
end
|
156
|
-
|
157
|
-
# Likes
|
158
|
-
# to get likes, use get_connections(user, "likes")
|
159
|
-
|
160
|
-
def put_like(object_id, options = {})
|
161
|
-
# Likes the given post.
|
162
|
-
self.put_object(object_id, "likes", {}, options)
|
163
|
-
end
|
164
|
-
|
165
|
-
def delete_like(object_id, options = {})
|
166
|
-
# Unlikes a given object for the logged-in user
|
167
|
-
raise APIError.new({"type" => "KoalaMissingAccessToken", "message" => "Unliking requires an access token"}) unless @access_token
|
168
|
-
graph_call("#{object_id}/likes", {}, "delete", options)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Search
|
172
|
-
|
173
|
-
def search(search_terms, args = {}, options = {})
|
174
|
-
args.merge!({:q => search_terms}) unless search_terms.nil?
|
175
|
-
graph_call("search", args, "get", options)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Convenience Methods
|
179
|
-
#
|
180
|
-
# in general, we're trying to avoid adding convenience methods to Koala
|
181
|
-
# except to support cases where the Facebook API requires non-standard input
|
182
|
-
# such as JSON-encoding arguments, posts directly to objects, etc.
|
183
|
-
|
184
|
-
def fql_query(query, args = {}, options = {})
|
185
|
-
get_object("fql", args.merge(:q => query), options)
|
186
|
-
end
|
187
|
-
|
188
|
-
def fql_multiquery(queries = {}, args = {}, options = {})
|
189
|
-
if results = get_object("fql", args.merge(:q => MultiJson.encode(queries)), options)
|
190
|
-
# simplify the multiquery result format
|
191
|
-
results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def get_page_access_token(object_id, args = {}, options = {})
|
196
|
-
result = get_object(object_id, args.merge(:fields => "access_token"), options) do
|
197
|
-
result ? result["access_token"] : nil
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def get_comments_for_urls(urls = [], args = {}, options = {})
|
202
|
-
# Fetchs the comments for given URLs (array or comma-separated string)
|
203
|
-
# see https://developers.facebook.com/blog/post/490
|
204
|
-
return [] if urls.empty?
|
205
|
-
args.merge!(:ids => urls.respond_to?(:join) ? urls.join(",") : urls)
|
206
|
-
get_object("comments", args, options)
|
207
|
-
end
|
208
|
-
|
209
|
-
def set_app_restrictions(app_id, restrictions_hash, args = {}, options = {})
|
210
|
-
graph_call(app_id, args.merge(:restrictions => MultiJson.encode(restrictions_hash)), "post", options)
|
211
|
-
end
|
212
|
-
|
213
|
-
# GraphCollection support
|
214
|
-
def get_page(params)
|
215
|
-
# Pages through a set of results stored in a GraphCollection
|
216
|
-
# Used for connections and search results
|
217
|
-
graph_call(*params)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Batch API
|
221
|
-
def batch(http_options = {}, &block)
|
222
|
-
batch_client = GraphBatchAPI.new(access_token, self)
|
223
|
-
if block
|
224
|
-
yield batch_client
|
225
|
-
batch_client.execute(http_options)
|
226
|
-
else
|
227
|
-
batch_client
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# Direct access to the Facebook API
|
232
|
-
# see any of the above methods for example invocations
|
233
|
-
def graph_call(path, args = {}, verb = "get", options = {}, &post_processing)
|
234
|
-
result = api(path, args, verb, options) do |response|
|
235
|
-
error = check_response(response)
|
236
|
-
raise error if error
|
237
|
-
end
|
238
|
-
|
239
|
-
# turn this into a GraphCollection if it's pageable
|
240
|
-
result = GraphCollection.evaluate(result, self)
|
241
|
-
|
242
|
-
# now process as appropriate for the given call (get picture header, etc.)
|
243
|
-
post_processing ? post_processing.call(result) : result
|
244
|
-
end
|
245
|
-
|
246
|
-
private
|
247
|
-
|
248
|
-
def check_response(response)
|
249
|
-
# check for Graph API-specific errors
|
250
|
-
# this returns an error, which is immediately raised (non-batch)
|
251
|
-
# or added to the list of batch results (batch)
|
252
|
-
if response.is_a?(Hash) && error_details = response["error"]
|
253
|
-
APIError.new(error_details)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def parse_media_args(media_args, method)
|
258
|
-
# photo and video uploads can accept different types of arguments (see above)
|
259
|
-
# so here, we parse the arguments into a form directly usable in put_object
|
260
|
-
raise KoalaError.new("Wrong number of arguments for put_#{method == "photos" ? "picture" : "video"}") unless media_args.size.between?(1, 5)
|
261
|
-
|
262
|
-
args_offset = media_args[1].kind_of?(Hash) || media_args.size == 1 ? 0 : 1
|
263
|
-
|
264
|
-
args = media_args[1 + args_offset] || {}
|
265
|
-
target_id = media_args[2 + args_offset] || "me"
|
266
|
-
options = media_args[3 + args_offset] || {}
|
267
|
-
|
268
|
-
if url?(media_args.first)
|
269
|
-
# If media_args is a URL, we can upload without UploadableIO
|
270
|
-
args.merge!(:url => media_args.first)
|
271
|
-
else
|
272
|
-
args["source"] = Koala::UploadableIO.new(*media_args.slice(0, 1 + args_offset))
|
273
|
-
end
|
274
|
-
|
275
|
-
[target_id, method, args, options]
|
276
|
-
end
|
277
|
-
|
278
|
-
def url?(data)
|
279
|
-
return false unless data.is_a? String
|
280
|
-
begin
|
281
|
-
uri = URI.parse(data)
|
282
|
-
%w( http https ).include?(uri.scheme)
|
283
|
-
rescue URI::BadURIError
|
284
|
-
false
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
end
|
@@ -1,63 +0,0 @@
|
|
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, :api, :raw_response
|
14
|
-
|
15
|
-
def self.evaluate(response, api)
|
16
|
-
# turn the response into a GraphCollection if it's pageable; if not, return the original response
|
17
|
-
response.is_a?(Hash) && response["data"].is_a?(Array) ? self.new(response, api) : response
|
18
|
-
end
|
19
|
-
|
20
|
-
def initialize(response, api)
|
21
|
-
super response["data"]
|
22
|
-
@paging = response["paging"]
|
23
|
-
@raw_response = response
|
24
|
-
@api = api
|
25
|
-
end
|
26
|
-
|
27
|
-
# defines methods for NEXT and PREVIOUS pages
|
28
|
-
%w{next previous}.each do |this|
|
29
|
-
|
30
|
-
# def next_page
|
31
|
-
# def previous_page
|
32
|
-
define_method "#{this.to_sym}_page" do
|
33
|
-
base, args = send("#{this}_page_params")
|
34
|
-
base ? @api.get_page([base, args]) : nil
|
35
|
-
end
|
36
|
-
|
37
|
-
# def next_page_params
|
38
|
-
# def previous_page_params
|
39
|
-
define_method "#{this.to_sym}_page_params" do
|
40
|
-
return nil unless @paging and @paging[this]
|
41
|
-
parse_page_url(@paging[this])
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def parse_page_url(url)
|
46
|
-
GraphCollection.parse_page_url(url)
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.parse_page_url(url)
|
50
|
-
match = url.match(/.com\/(.*)\?(.*)/)
|
51
|
-
base = match[1]
|
52
|
-
args = match[2]
|
53
|
-
params = CGI.parse(args)
|
54
|
-
new_params = {}
|
55
|
-
params.each_pair do |key,value|
|
56
|
-
new_params[key] = value.join ","
|
57
|
-
end
|
58
|
-
[base,new_params]
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
|
3
|
-
module Koala
|
4
|
-
class MultipartRequest < Faraday::Request::Multipart
|
5
|
-
# Facebook expects nested parameters to be passed in a certain way
|
6
|
-
# Based on our testing (https://github.com/arsduo/koala/issues/125),
|
7
|
-
# Faraday needs two changes to make that work:
|
8
|
-
# 1) [] need to be escaped (e.g. params[foo]=bar ==> params%5Bfoo%5D=bar)
|
9
|
-
# 2) such messages need to be multipart-encoded
|
10
|
-
|
11
|
-
self.mime_type = 'multipart/form-data'.freeze
|
12
|
-
|
13
|
-
def process_request?(env)
|
14
|
-
# if the request values contain any hashes or arrays, multipart it
|
15
|
-
super || !!(env[:body].respond_to?(:values) && env[:body].values.find {|v| v.is_a?(Hash) || v.is_a?(Array)})
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
def process_params(params, prefix = nil, pieces = nil, &block)
|
20
|
-
params.inject(pieces || []) do |all, (key, value)|
|
21
|
-
key = "#{prefix}%5B#{key}%5D" if prefix
|
22
|
-
|
23
|
-
case value
|
24
|
-
when Array
|
25
|
-
values = value.inject([]) { |a,v| a << [nil, v] }
|
26
|
-
process_params(values, key, all, &block)
|
27
|
-
when Hash
|
28
|
-
process_params(value, key, all, &block)
|
29
|
-
else
|
30
|
-
all << block.call(key, value)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
data/lib/koala/uploadable_io.rb
DELETED
@@ -1,181 +0,0 @@
|
|
1
|
-
require "net/http/post/multipart"
|
2
|
-
|
3
|
-
module Koala
|
4
|
-
class UploadableIO
|
5
|
-
attr_reader :io_or_path, :content_type, :filename
|
6
|
-
|
7
|
-
def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)
|
8
|
-
# see if we got the right inputs
|
9
|
-
parse_init_mixed_param io_or_path_or_mixed, content_type
|
10
|
-
|
11
|
-
# filename is used in the Ads API
|
12
|
-
# if it's provided, take precedence over the detected filename
|
13
|
-
# otherwise, fall back to a dummy name
|
14
|
-
@filename = filename || @filename || "koala-io-file.dum"
|
15
|
-
|
16
|
-
raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
|
17
|
-
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_upload_io
|
21
|
-
UploadIO.new(@io_or_path, @content_type, @filename)
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_file
|
25
|
-
@io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.binary_content?(content)
|
29
|
-
content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
DETECTION_STRATEGIES = [
|
34
|
-
:sinatra_param?,
|
35
|
-
:rails_3_param?,
|
36
|
-
:file_param?
|
37
|
-
]
|
38
|
-
|
39
|
-
PARSE_STRATEGIES = [
|
40
|
-
:parse_rails_3_param,
|
41
|
-
:parse_sinatra_param,
|
42
|
-
:parse_file_object,
|
43
|
-
:parse_string_path,
|
44
|
-
:parse_io
|
45
|
-
]
|
46
|
-
|
47
|
-
def parse_init_mixed_param(mixed, content_type = nil)
|
48
|
-
PARSE_STRATEGIES.each do |method|
|
49
|
-
send(method, mixed, content_type)
|
50
|
-
return if @io_or_path && @content_type
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Expects a parameter of type ActionDispatch::Http::UploadedFile
|
55
|
-
def self.rails_3_param?(uploaded_file)
|
56
|
-
uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
|
57
|
-
end
|
58
|
-
|
59
|
-
def parse_rails_3_param(uploaded_file, content_type = nil)
|
60
|
-
if UploadableIO.rails_3_param?(uploaded_file)
|
61
|
-
@io_or_path = uploaded_file.tempfile.path
|
62
|
-
@content_type = content_type || uploaded_file.content_type
|
63
|
-
@filename = uploaded_file.original_filename
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Expects a Sinatra hash of file info
|
68
|
-
def self.sinatra_param?(file_hash)
|
69
|
-
file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
|
70
|
-
end
|
71
|
-
|
72
|
-
def parse_sinatra_param(file_hash, content_type = nil)
|
73
|
-
if UploadableIO.sinatra_param?(file_hash)
|
74
|
-
@io_or_path = file_hash[:tempfile]
|
75
|
-
@content_type = content_type || file_hash[:type] || detect_mime_type(tempfile)
|
76
|
-
@filename = file_hash[:filename]
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# takes a file object
|
81
|
-
def self.file_param?(file)
|
82
|
-
file.kind_of?(File)
|
83
|
-
end
|
84
|
-
|
85
|
-
def parse_file_object(file, content_type = nil)
|
86
|
-
if UploadableIO.file_param?(file)
|
87
|
-
@io_or_path = file
|
88
|
-
@content_type = content_type || detect_mime_type(file.path)
|
89
|
-
@filename = File.basename(file.path)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def parse_string_path(path, content_type = nil)
|
94
|
-
if path.kind_of?(String)
|
95
|
-
@io_or_path = path
|
96
|
-
@content_type = content_type || detect_mime_type(path)
|
97
|
-
@filename = File.basename(path)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def parse_io(io, content_type = nil)
|
102
|
-
if io.respond_to?(:read)
|
103
|
-
@io_or_path = io
|
104
|
-
@content_type = content_type
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
MIME_TYPE_STRATEGIES = [
|
109
|
-
:use_mime_module,
|
110
|
-
:use_simple_detection
|
111
|
-
]
|
112
|
-
|
113
|
-
def detect_mime_type(filename)
|
114
|
-
if filename
|
115
|
-
MIME_TYPE_STRATEGIES.each do |method|
|
116
|
-
result = send(method, filename)
|
117
|
-
return result if result
|
118
|
-
end
|
119
|
-
end
|
120
|
-
nil # if we can't find anything
|
121
|
-
end
|
122
|
-
|
123
|
-
def use_mime_module(filename)
|
124
|
-
# if the user has installed mime/types, we can use that
|
125
|
-
# if not, rescue and return nil
|
126
|
-
begin
|
127
|
-
type = MIME::Types.type_for(filename).first
|
128
|
-
type ? type.to_s : nil
|
129
|
-
rescue
|
130
|
-
nil
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def use_simple_detection(filename)
|
135
|
-
# very rudimentary extension analysis for images
|
136
|
-
# first, get the downcased extension, or an empty string if it doesn't exist
|
137
|
-
extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
|
138
|
-
case extension
|
139
|
-
when ""
|
140
|
-
nil
|
141
|
-
# images
|
142
|
-
when "jpg", "jpeg"
|
143
|
-
"image/jpeg"
|
144
|
-
when "png"
|
145
|
-
"image/png"
|
146
|
-
when "gif"
|
147
|
-
"image/gif"
|
148
|
-
|
149
|
-
# video
|
150
|
-
when "3g2"
|
151
|
-
"video/3gpp2"
|
152
|
-
when "3gp", "3gpp"
|
153
|
-
"video/3gpp"
|
154
|
-
when "asf"
|
155
|
-
"video/x-ms-asf"
|
156
|
-
when "avi"
|
157
|
-
"video/x-msvideo"
|
158
|
-
when "flv"
|
159
|
-
"video/x-flv"
|
160
|
-
when "m4v"
|
161
|
-
"video/x-m4v"
|
162
|
-
when "mkv"
|
163
|
-
"video/x-matroska"
|
164
|
-
when "mod"
|
165
|
-
"video/mod"
|
166
|
-
when "mov", "qt"
|
167
|
-
"video/quicktime"
|
168
|
-
when "mp4", "mpeg4"
|
169
|
-
"video/mp4"
|
170
|
-
when "mpe", "mpeg", "mpg", "tod", "vob"
|
171
|
-
"video/mpeg"
|
172
|
-
when "nsv"
|
173
|
-
"application/x-winamp"
|
174
|
-
when "ogm", "ogv"
|
175
|
-
"video/ogg"
|
176
|
-
when "wmv"
|
177
|
-
"video/x-ms-wmv"
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|