dbox 0.7.6 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.md +14 -16
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/dbox.gemspec +6 -15
- data/lib/dbox/api.rb +22 -22
- metadata +85 -97
- data/vendor/dropbox-ruby-sdk/CHANGELOG +0 -42
- data/vendor/dropbox-ruby-sdk/LICENSE +0 -20
- data/vendor/dropbox-ruby-sdk/README +0 -7
- data/vendor/dropbox-ruby-sdk/cli_example.rb +0 -206
- data/vendor/dropbox-ruby-sdk/copy_between_accounts.rb +0 -155
- data/vendor/dropbox-ruby-sdk/dropbox_controller.rb +0 -57
- data/vendor/dropbox-ruby-sdk/gemspec.rb +0 -25
- data/vendor/dropbox-ruby-sdk/lib/dropbox_sdk.rb +0 -883
- data/vendor/dropbox-ruby-sdk/lib/trusted-certs.crt +0 -341
- data/vendor/dropbox-ruby-sdk/search_cache.json +0 -42830
- data/vendor/dropbox-ruby-sdk/search_cache.rb +0 -332
- data/vendor/dropbox-ruby-sdk/web_file_browser.rb +0 -184
@@ -1,25 +0,0 @@
|
|
1
|
-
# Build with: gem build gemspec.rb
|
2
|
-
Gem::Specification.new do |s|
|
3
|
-
s.name = "dropbox-sdk"
|
4
|
-
|
5
|
-
s.version = "1.3.1"
|
6
|
-
s.license = 'MIT'
|
7
|
-
|
8
|
-
s.authors = ["Dropbox, Inc."]
|
9
|
-
s.email = ["support-api@dropbox.com"]
|
10
|
-
|
11
|
-
s.add_dependency "json"
|
12
|
-
|
13
|
-
s.homepage = "http://www.dropbox.com/developers/"
|
14
|
-
s.summary = "Dropbox REST API Client."
|
15
|
-
s.description = <<-EOF
|
16
|
-
A library that provides a plain function-call interface to the
|
17
|
-
Dropbox API web endpoints.
|
18
|
-
EOF
|
19
|
-
|
20
|
-
s.files = [
|
21
|
-
"CHANGELOG", "LICENSE", "README",
|
22
|
-
"cli_example.rb", "dropbox_controller.rb", "web_file_browser.rb",
|
23
|
-
"lib/dropbox_sdk.rb", "lib/trusted-certs.crt",
|
24
|
-
]
|
25
|
-
end
|
@@ -1,883 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'uri'
|
3
|
-
require 'net/https'
|
4
|
-
require 'cgi'
|
5
|
-
require 'json'
|
6
|
-
require 'yaml'
|
7
|
-
|
8
|
-
module Dropbox # :nodoc:
|
9
|
-
API_SERVER = "api.dropbox.com"
|
10
|
-
API_CONTENT_SERVER = "api-content.dropbox.com"
|
11
|
-
WEB_SERVER = "www.dropbox.com"
|
12
|
-
|
13
|
-
API_VERSION = 1
|
14
|
-
SDK_VERSION = "1.3.1"
|
15
|
-
|
16
|
-
TRUSTED_CERT_FILE = File.join(File.dirname(__FILE__), 'trusted-certs.crt')
|
17
|
-
end
|
18
|
-
|
19
|
-
# DropboxSession is responsible for holding OAuth information. It knows how to take your consumer key and secret
|
20
|
-
# and request an access token, an authorize url, and get an access token. You just need to pass it to
|
21
|
-
# DropboxClient after its been authorized.
|
22
|
-
class DropboxSession
|
23
|
-
|
24
|
-
# * consumer_key - Your Dropbox application's "app key".
|
25
|
-
# * consumer_secret - Your Dropbox application's "app secret".
|
26
|
-
def initialize(consumer_key, consumer_secret)
|
27
|
-
@consumer_key = consumer_key
|
28
|
-
@consumer_secret = consumer_secret
|
29
|
-
@request_token = nil
|
30
|
-
@access_token = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def do_http(uri, auth_token, request) # :nodoc:
|
36
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
37
|
-
|
38
|
-
http.use_ssl = true
|
39
|
-
enable_cert_checking(http)
|
40
|
-
http.ca_file = Dropbox::TRUSTED_CERT_FILE
|
41
|
-
|
42
|
-
request.add_field('Authorization', build_auth_header(auth_token))
|
43
|
-
|
44
|
-
#We use this to better understand how developers are using our SDKs.
|
45
|
-
request['User-Agent'] = "OfficialDropboxRubySDK/#{Dropbox::SDK_VERSION}"
|
46
|
-
|
47
|
-
begin
|
48
|
-
http.request(request)
|
49
|
-
rescue OpenSSL::SSL::SSLError => e
|
50
|
-
raise DropboxError.new("SSL error connecting to Dropbox. " +
|
51
|
-
"There may be a problem with the set of certificates in \"#{Dropbox::TRUSTED_CERT_FILE}\". " +
|
52
|
-
e)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def enable_cert_checking(http)
|
57
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
58
|
-
end
|
59
|
-
|
60
|
-
def build_auth_header(token) # :nodoc:
|
61
|
-
header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
|
62
|
-
"oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
|
63
|
-
if token
|
64
|
-
key = URI.escape(token.key)
|
65
|
-
secret = URI.escape(token.secret)
|
66
|
-
header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
|
67
|
-
else
|
68
|
-
header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
|
69
|
-
end
|
70
|
-
header
|
71
|
-
end
|
72
|
-
|
73
|
-
def do_get_with_token(url, token, headers=nil) # :nodoc:
|
74
|
-
uri = URI.parse(url)
|
75
|
-
do_http(uri, token, Net::HTTP::Get.new(uri.request_uri))
|
76
|
-
end
|
77
|
-
|
78
|
-
public
|
79
|
-
|
80
|
-
def do_get(url, headers=nil) # :nodoc:
|
81
|
-
assert_authorized
|
82
|
-
do_get_with_token(url, @access_token)
|
83
|
-
end
|
84
|
-
|
85
|
-
def do_http_with_body(uri, request, body)
|
86
|
-
if body != nil
|
87
|
-
if body.is_a?(Hash)
|
88
|
-
form_data = {}
|
89
|
-
body.each {|k,v| form_data[k.to_s] = v if !v.nil?}
|
90
|
-
request.set_form_data(form_data)
|
91
|
-
elsif body.respond_to?(:read)
|
92
|
-
if body.respond_to?(:length)
|
93
|
-
request["Content-Length"] = body.length.to_s
|
94
|
-
elsif body.respond_to?(:stat) && body.stat.respond_to?(:size)
|
95
|
-
request["Content-Length"] = body.stat.size.to_s
|
96
|
-
else
|
97
|
-
raise ArgumentError, "Don't know how to handle 'body' (responds to 'read' but not to 'length' or 'stat.size')."
|
98
|
-
end
|
99
|
-
request.body_stream = body
|
100
|
-
else
|
101
|
-
s = body.to_s
|
102
|
-
request["Content-Length"] = s.length
|
103
|
-
request.body = s
|
104
|
-
end
|
105
|
-
end
|
106
|
-
do_http(uri, @access_token, request)
|
107
|
-
end
|
108
|
-
|
109
|
-
def do_post(url, headers=nil, body=nil) # :nodoc:
|
110
|
-
assert_authorized
|
111
|
-
uri = URI.parse(url)
|
112
|
-
do_http_with_body(uri, Net::HTTP::Post.new(uri.request_uri, headers), body)
|
113
|
-
end
|
114
|
-
|
115
|
-
def do_put(url, headers=nil, body=nil) # :nodoc:
|
116
|
-
assert_authorized
|
117
|
-
uri = URI.parse(url)
|
118
|
-
do_http_with_body(uri, Net::HTTP::Put.new(uri.request_uri, headers), body)
|
119
|
-
end
|
120
|
-
|
121
|
-
|
122
|
-
def get_token(url_end, input_token, error_message_prefix) #: nodoc:
|
123
|
-
response = do_get_with_token("https://#{Dropbox::API_SERVER}:443/#{Dropbox::API_VERSION}/oauth#{url_end}", input_token)
|
124
|
-
if not response.kind_of?(Net::HTTPSuccess) # it must be a 200
|
125
|
-
raise DropboxAuthError.new("#{error_message_prefix} Server returned #{response.code}: #{response.message}.", response)
|
126
|
-
end
|
127
|
-
parts = CGI.parse(response.body)
|
128
|
-
|
129
|
-
if !parts.has_key? "oauth_token" and parts["oauth_token"].length != 1
|
130
|
-
raise DropboxAuthError.new("Invalid response from #{url_end}: missing \"oauth_token\" parameter: #{response.body}", response)
|
131
|
-
end
|
132
|
-
if !parts.has_key? "oauth_token_secret" and parts["oauth_token_secret"].length != 1
|
133
|
-
raise DropboxAuthError.new("Invalid response from #{url_end}: missing \"oauth_token\" parameter: #{response.body}", response)
|
134
|
-
end
|
135
|
-
|
136
|
-
OAuthToken.new(parts["oauth_token"][0], parts["oauth_token_secret"][0])
|
137
|
-
end
|
138
|
-
|
139
|
-
# This returns a request token. Requests one from the dropbox server using the provided application key and secret if nessecary.
|
140
|
-
def get_request_token()
|
141
|
-
@request_token ||= get_token("/request_token", nil, "Error getting request token. Is your app key and secret correctly set?")
|
142
|
-
end
|
143
|
-
|
144
|
-
# This returns a URL that your user must visit to grant
|
145
|
-
# permissions to this application.
|
146
|
-
def get_authorize_url(callback=nil)
|
147
|
-
get_request_token()
|
148
|
-
|
149
|
-
url = "/#{Dropbox::API_VERSION}/oauth/authorize?oauth_token=#{URI.escape(@request_token.key)}"
|
150
|
-
if callback
|
151
|
-
url += "&oauth_callback=#{URI.escape(callback)}"
|
152
|
-
end
|
153
|
-
if @locale
|
154
|
-
url += "&locale=#{URI.escape(@locale)}"
|
155
|
-
end
|
156
|
-
|
157
|
-
"https://#{Dropbox::WEB_SERVER}#{url}"
|
158
|
-
end
|
159
|
-
|
160
|
-
# Clears the access_token
|
161
|
-
def clear_access_token
|
162
|
-
@access_token = nil
|
163
|
-
end
|
164
|
-
|
165
|
-
# Returns the request token, or nil if one hasn't been acquired yet.
|
166
|
-
def request_token
|
167
|
-
@request_token
|
168
|
-
end
|
169
|
-
|
170
|
-
# Returns the access token, or nil if one hasn't been acquired yet.
|
171
|
-
def access_token
|
172
|
-
@access_token
|
173
|
-
end
|
174
|
-
|
175
|
-
# Given a saved request token and secret, set this location's token and secret
|
176
|
-
# * token - this is the request token
|
177
|
-
# * secret - this is the request token secret
|
178
|
-
def set_request_token(key, secret)
|
179
|
-
@request_token = OAuthToken.new(key, secret)
|
180
|
-
end
|
181
|
-
|
182
|
-
# Given a saved access token and secret, you set this Session to use that token and secret
|
183
|
-
# * token - this is the access token
|
184
|
-
# * secret - this is the access token secret
|
185
|
-
def set_access_token(key, secret)
|
186
|
-
@access_token = OAuthToken.new(key, secret)
|
187
|
-
end
|
188
|
-
|
189
|
-
# Returns the access token. If this DropboxSession doesn't yet have an access_token, it requests one
|
190
|
-
# using the request_token generate from your app's token and secret. This request will fail unless
|
191
|
-
# your user has gone to the authorize_url and approved your request
|
192
|
-
def get_access_token
|
193
|
-
return @access_token if authorized?
|
194
|
-
|
195
|
-
if @request_token.nil?
|
196
|
-
raise DropboxAuthError.new("No request token. You must set this or get an authorize url first.")
|
197
|
-
end
|
198
|
-
|
199
|
-
@access_token = get_token("/access_token", @request_token, "Couldn't get access token.")
|
200
|
-
end
|
201
|
-
|
202
|
-
# If we have an access token, then do nothing. If not, throw a RuntimeError.
|
203
|
-
def assert_authorized
|
204
|
-
unless authorized?
|
205
|
-
raise RuntimeError.new('Session does not yet have a request token')
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# Returns true if this Session has been authorized and has an access_token.
|
210
|
-
def authorized?
|
211
|
-
!!@access_token
|
212
|
-
end
|
213
|
-
|
214
|
-
# serialize the DropboxSession.
|
215
|
-
# At DropboxSession's state is capture in three key/secret pairs. Consumer, request, and access.
|
216
|
-
# Serialize returns these in a YAML string, generated from a converted array of the form:
|
217
|
-
# [consumer_key, consumer_secret, request_token.token, request_token.secret, access_token.token, access_token.secret]
|
218
|
-
# access_token is only included if it already exists in the DropboxSesssion
|
219
|
-
def serialize
|
220
|
-
toreturn = []
|
221
|
-
if @access_token
|
222
|
-
toreturn.push @access_token.secret, @access_token.key
|
223
|
-
end
|
224
|
-
|
225
|
-
get_request_token
|
226
|
-
|
227
|
-
toreturn.push @request_token.secret, @request_token.key
|
228
|
-
toreturn.push @consumer_secret, @consumer_key
|
229
|
-
|
230
|
-
toreturn.to_yaml
|
231
|
-
end
|
232
|
-
|
233
|
-
# Takes a serialized DropboxSession YAML String and returns a new DropboxSession object
|
234
|
-
def self.deserialize(ser)
|
235
|
-
ser = YAML::load(ser)
|
236
|
-
session = DropboxSession.new(ser.pop, ser.pop)
|
237
|
-
session.set_request_token(ser.pop, ser.pop)
|
238
|
-
|
239
|
-
if ser.length > 0
|
240
|
-
session.set_access_token(ser.pop, ser.pop)
|
241
|
-
end
|
242
|
-
session
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
|
247
|
-
# A class that represents either an OAuth request token or an OAuth access token.
|
248
|
-
class OAuthToken # :nodoc:
|
249
|
-
def initialize(key, secret)
|
250
|
-
@key = key
|
251
|
-
@secret = secret
|
252
|
-
end
|
253
|
-
|
254
|
-
def key
|
255
|
-
@key
|
256
|
-
end
|
257
|
-
|
258
|
-
def secret
|
259
|
-
@secret
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
|
264
|
-
# This is the usual error raised on any Dropbox related Errors
|
265
|
-
class DropboxError < RuntimeError
|
266
|
-
attr_accessor :http_response, :error, :user_error
|
267
|
-
def initialize(error, http_response=nil, user_error=nil)
|
268
|
-
@error = error
|
269
|
-
@http_response = http_response
|
270
|
-
@user_error = user_error
|
271
|
-
end
|
272
|
-
|
273
|
-
def to_s
|
274
|
-
return "#{user_error} (#{error})" if user_error
|
275
|
-
"#{error}"
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# This is the error raised on Authentication failures. Usually this means
|
280
|
-
# one of three things
|
281
|
-
# * Your user failed to go to the authorize url and approve your application
|
282
|
-
# * You set an invalid or expired token and secret on your Session
|
283
|
-
# * Your user deauthorized the application after you stored a valid token and secret
|
284
|
-
class DropboxAuthError < DropboxError
|
285
|
-
end
|
286
|
-
|
287
|
-
# This is raised when you call metadata with a hash and that hash matches
|
288
|
-
# See documentation in metadata function
|
289
|
-
class DropboxNotModified < DropboxError
|
290
|
-
end
|
291
|
-
|
292
|
-
# This is the Dropbox Client API you'll be working with most often. You need to give it
|
293
|
-
# a DropboxSession which has already been authorized, or which it can authorize.
|
294
|
-
class DropboxClient
|
295
|
-
|
296
|
-
# Initialize a new DropboxClient. You need to give it a session which has been authorized. See
|
297
|
-
# documentation on DropboxSession for how to authorize it.
|
298
|
-
def initialize(session, root="app_folder", locale=nil)
|
299
|
-
session.get_access_token
|
300
|
-
|
301
|
-
@root = root.to_s # If they passed in a symbol, make it a string
|
302
|
-
|
303
|
-
if not ["dropbox","app_folder"].include?(@root)
|
304
|
-
raise DropboxError.new("root must be :dropbox or :app_folder")
|
305
|
-
end
|
306
|
-
if @root == "app_folder"
|
307
|
-
#App Folder is the name of the access type, but for historical reasons
|
308
|
-
#sandbox is the URL root component that indicates this
|
309
|
-
@root = "sandbox"
|
310
|
-
end
|
311
|
-
|
312
|
-
@locale = locale
|
313
|
-
@session = session
|
314
|
-
end
|
315
|
-
|
316
|
-
# Parse response. You probably shouldn't be calling this directly. This takes responses from the server
|
317
|
-
# and parses them. It also checks for errors and raises exceptions with the appropriate messages.
|
318
|
-
def parse_response(response, raw=false) # :nodoc:
|
319
|
-
if response.kind_of?(Net::HTTPServerError)
|
320
|
-
raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
|
321
|
-
elsif response.kind_of?(Net::HTTPUnauthorized)
|
322
|
-
raise DropboxAuthError.new(response, "User is not authenticated.")
|
323
|
-
elsif not response.kind_of?(Net::HTTPSuccess)
|
324
|
-
begin
|
325
|
-
d = JSON.parse(response.body)
|
326
|
-
rescue
|
327
|
-
raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
|
328
|
-
end
|
329
|
-
if d['user_error'] and d['error']
|
330
|
-
raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
|
331
|
-
elsif d['error']
|
332
|
-
raise DropboxError.new(d['error'], response)
|
333
|
-
else
|
334
|
-
raise DropboxError.new(response.body, response)
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
return response.body if raw
|
339
|
-
|
340
|
-
begin
|
341
|
-
return JSON.parse(response.body)
|
342
|
-
rescue JSON::ParserError
|
343
|
-
raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
# Returns account info in a Hash object
|
348
|
-
#
|
349
|
-
# For a detailed description of what this call returns, visit:
|
350
|
-
# https://www.dropbox.com/developers/reference/api#account-info
|
351
|
-
def account_info()
|
352
|
-
response = @session.do_get build_url("/account/info")
|
353
|
-
parse_response(response)
|
354
|
-
end
|
355
|
-
|
356
|
-
# Uploads a file to a server. This uses the HTTP PUT upload method for simplicity
|
357
|
-
#
|
358
|
-
# Arguments:
|
359
|
-
# * to_path: The directory path to upload the file to. If the destination
|
360
|
-
# directory does not yet exist, it will be created.
|
361
|
-
# * file_obj: A file-like object to upload. If you would like, you can
|
362
|
-
# pass a string as file_obj.
|
363
|
-
# * overwrite: Whether to overwrite an existing file at the given path. [default is False]
|
364
|
-
# If overwrite is False and a file already exists there, Dropbox
|
365
|
-
# will rename the upload to make sure it doesn't overwrite anything.
|
366
|
-
# You must check the returned metadata to know what this new name is.
|
367
|
-
# This field should only be True if your intent is to potentially
|
368
|
-
# clobber changes to a file that you don't know about.
|
369
|
-
# * parent_rev: The rev field from the 'parent' of this upload. [optional]
|
370
|
-
# If your intent is to update the file at the given path, you should
|
371
|
-
# pass the parent_rev parameter set to the rev value from the most recent
|
372
|
-
# metadata you have of the existing file at that path. If the server
|
373
|
-
# has a more recent version of the file at the specified path, it will
|
374
|
-
# automatically rename your uploaded file, spinning off a conflict.
|
375
|
-
# Using this parameter effectively causes the overwrite parameter to be ignored.
|
376
|
-
# The file will always be overwritten if you send the most-recent parent_rev,
|
377
|
-
# and it will never be overwritten you send a less-recent one.
|
378
|
-
# Returns:
|
379
|
-
# * a Hash containing the metadata of the newly uploaded file. The file may have a different name if it conflicted.
|
380
|
-
#
|
381
|
-
# Simple Example
|
382
|
-
# client = DropboxClient(session, :app_folder)
|
383
|
-
# #session is a DropboxSession I've already authorized
|
384
|
-
# client.put_file('/test_file_on_dropbox', open('/tmp/test_file'))
|
385
|
-
# This will upload the "/tmp/test_file" from my computer into the root of my App's app folder
|
386
|
-
# and call it "test_file_on_dropbox".
|
387
|
-
# The file will not overwrite any pre-existing file.
|
388
|
-
def put_file(to_path, file_obj, overwrite=false, parent_rev=nil)
|
389
|
-
path = "/files_put/#{@root}#{format_path(to_path)}"
|
390
|
-
|
391
|
-
params = {
|
392
|
-
'overwrite' => overwrite.to_s
|
393
|
-
}
|
394
|
-
|
395
|
-
params['parent_rev'] = parent_rev unless parent_rev.nil?
|
396
|
-
|
397
|
-
response = @session.do_put(build_url(path, params, content_server=true),
|
398
|
-
{"Content-Type" => "application/octet-stream"},
|
399
|
-
file_obj)
|
400
|
-
|
401
|
-
parse_response(response)
|
402
|
-
end
|
403
|
-
|
404
|
-
# Download a file
|
405
|
-
#
|
406
|
-
# Args:
|
407
|
-
# * from_path: The path to the file to be downloaded
|
408
|
-
# * rev: A previous revision value of the file to be downloaded
|
409
|
-
#
|
410
|
-
# Returns:
|
411
|
-
# * The file contents.
|
412
|
-
def get_file(from_path, rev=nil)
|
413
|
-
response = get_file_impl(from_path, rev)
|
414
|
-
parse_response(response, raw=true)
|
415
|
-
end
|
416
|
-
|
417
|
-
# Download a file and get its metadata.
|
418
|
-
#
|
419
|
-
# Args:
|
420
|
-
# * from_path: The path to the file to be downloaded
|
421
|
-
# * rev: A previous revision value of the file to be downloaded
|
422
|
-
#
|
423
|
-
# Returns:
|
424
|
-
# * The file contents.
|
425
|
-
# * The file metadata as a hash.
|
426
|
-
def get_file_and_metadata(from_path, rev=nil)
|
427
|
-
response = get_file_impl(from_path, rev)
|
428
|
-
parsed_response = parse_response(response, raw=true)
|
429
|
-
metadata = parse_metadata(response)
|
430
|
-
return parsed_response, metadata
|
431
|
-
end
|
432
|
-
|
433
|
-
# Download a file (helper method - don't call this directly).
|
434
|
-
#
|
435
|
-
# Args:
|
436
|
-
# * from_path: The path to the file to be downloaded
|
437
|
-
# * rev: A previous revision value of the file to be downloaded
|
438
|
-
#
|
439
|
-
# Returns:
|
440
|
-
# * The HTTPResponse for the file download request.
|
441
|
-
def get_file_impl(from_path, rev=nil) # :nodoc:
|
442
|
-
params = {}
|
443
|
-
params['rev'] = rev.to_s if rev
|
444
|
-
|
445
|
-
path = "/files/#{@root}#{format_path(from_path)}"
|
446
|
-
@session.do_get build_url(path, params, content_server=true)
|
447
|
-
end
|
448
|
-
private :get_file_impl
|
449
|
-
|
450
|
-
# Parses out file metadata from a raw dropbox HTTP response.
|
451
|
-
#
|
452
|
-
# Args:
|
453
|
-
# * dropbox_raw_response: The raw, unparsed HTTPResponse from Dropbox.
|
454
|
-
#
|
455
|
-
# Returns:
|
456
|
-
# * The metadata of the file as a hash.
|
457
|
-
def parse_metadata(dropbox_raw_response) # :nodoc:
|
458
|
-
begin
|
459
|
-
raw_metadata = dropbox_raw_response['x-dropbox-metadata']
|
460
|
-
metadata = JSON.parse(raw_metadata)
|
461
|
-
rescue
|
462
|
-
raise DropboxError.new("Dropbox Server Error: x-dropbox-metadata=#{raw_metadata}",
|
463
|
-
dropbox_raw_response)
|
464
|
-
end
|
465
|
-
return metadata
|
466
|
-
end
|
467
|
-
private :parse_metadata
|
468
|
-
|
469
|
-
# Copy a file or folder to a new location.
|
470
|
-
#
|
471
|
-
# Arguments:
|
472
|
-
# * from_path: The path to the file or folder to be copied.
|
473
|
-
# * to_path: The destination path of the file or folder to be copied.
|
474
|
-
# This parameter should include the destination filename (e.g.
|
475
|
-
# from_path: '/test.txt', to_path: '/dir/test.txt'). If there's
|
476
|
-
# already a file at the to_path, this copy will be renamed to
|
477
|
-
# be unique.
|
478
|
-
#
|
479
|
-
# Returns:
|
480
|
-
# * A hash with the metadata of the new copy of the file or folder.
|
481
|
-
# For a detailed description of what this call returns, visit:
|
482
|
-
# https://www.dropbox.com/developers/reference/api#fileops-copy
|
483
|
-
def file_copy(from_path, to_path)
|
484
|
-
params = {
|
485
|
-
"root" => @root,
|
486
|
-
"from_path" => format_path(from_path, false),
|
487
|
-
"to_path" => format_path(to_path, false),
|
488
|
-
}
|
489
|
-
response = @session.do_post build_url("/fileops/copy", params)
|
490
|
-
parse_response(response)
|
491
|
-
end
|
492
|
-
|
493
|
-
# Create a folder.
|
494
|
-
#
|
495
|
-
# Arguments:
|
496
|
-
# * path: The path of the new folder.
|
497
|
-
#
|
498
|
-
# Returns:
|
499
|
-
# * A hash with the metadata of the newly created folder.
|
500
|
-
# For a detailed description of what this call returns, visit:
|
501
|
-
# https://www.dropbox.com/developers/reference/api#fileops-create-folder
|
502
|
-
def file_create_folder(path)
|
503
|
-
params = {
|
504
|
-
"root" => @root,
|
505
|
-
"path" => format_path(path, false),
|
506
|
-
}
|
507
|
-
response = @session.do_post build_url("/fileops/create_folder", params)
|
508
|
-
|
509
|
-
parse_response(response)
|
510
|
-
end
|
511
|
-
|
512
|
-
# Deletes a file
|
513
|
-
#
|
514
|
-
# Arguments:
|
515
|
-
# * path: The path of the file to delete
|
516
|
-
#
|
517
|
-
# Returns:
|
518
|
-
# * A Hash with the metadata of file just deleted.
|
519
|
-
# For a detailed description of what this call returns, visit:
|
520
|
-
# https://www.dropbox.com/developers/reference/api#fileops-delete
|
521
|
-
def file_delete(path)
|
522
|
-
params = {
|
523
|
-
"root" => @root,
|
524
|
-
"path" => format_path(path, false),
|
525
|
-
}
|
526
|
-
response = @session.do_post build_url("/fileops/delete", params)
|
527
|
-
parse_response(response)
|
528
|
-
end
|
529
|
-
|
530
|
-
# Moves a file
|
531
|
-
#
|
532
|
-
# Arguments:
|
533
|
-
# * from_path: The path of the file to be moved
|
534
|
-
# * to_path: The destination path of the file or folder to be moved
|
535
|
-
# If the file or folder already exists, it will be renamed to be unique.
|
536
|
-
#
|
537
|
-
# Returns:
|
538
|
-
# * A Hash with the metadata of file or folder just moved.
|
539
|
-
# For a detailed description of what this call returns, visit:
|
540
|
-
# https://www.dropbox.com/developers/reference/api#fileops-delete
|
541
|
-
def file_move(from_path, to_path)
|
542
|
-
params = {
|
543
|
-
"root" => @root,
|
544
|
-
"from_path" => format_path(from_path, false),
|
545
|
-
"to_path" => format_path(to_path, false),
|
546
|
-
}
|
547
|
-
response = @session.do_post build_url("/fileops/move", params)
|
548
|
-
parse_response(response)
|
549
|
-
end
|
550
|
-
|
551
|
-
# Retrives metadata for a file or folder
|
552
|
-
#
|
553
|
-
# Arguments:
|
554
|
-
# * path: The path to the file or folder.
|
555
|
-
# * list: Whether to list all contained files (only applies when
|
556
|
-
# path refers to a folder).
|
557
|
-
# * file_limit: The maximum number of file entries to return within
|
558
|
-
# a folder. If the number of files in the directory exceeds this
|
559
|
-
# limit, an exception is raised. The server will return at max
|
560
|
-
# 25,000 files within a folder.
|
561
|
-
# * hash: Every directory listing has a hash parameter attached that
|
562
|
-
# can then be passed back into this function later to save on
|
563
|
-
# bandwidth. Rather than returning an unchanged folder's contents, if
|
564
|
-
# the hash matches a DropboxNotModified exception is raised.
|
565
|
-
# * rev: Optional. The revision of the file to retrieve the metadata for.
|
566
|
-
# This parameter only applies for files. If omitted, you'll receive
|
567
|
-
# the most recent revision metadata.
|
568
|
-
# * include_deleted: Specifies whether to include deleted files in metadata results.
|
569
|
-
#
|
570
|
-
# Returns:
|
571
|
-
# * A Hash object with the metadata of the file or folder (and contained files if
|
572
|
-
# appropriate). For a detailed description of what this call returns, visit:
|
573
|
-
# https://www.dropbox.com/developers/reference/api#metadata
|
574
|
-
def metadata(path, file_limit=25000, list=true, hash=nil, rev=nil, include_deleted=false)
|
575
|
-
params = {
|
576
|
-
"file_limit" => file_limit.to_s,
|
577
|
-
"list" => list.to_s,
|
578
|
-
"include_deleted" => include_deleted.to_s
|
579
|
-
}
|
580
|
-
|
581
|
-
params["hash"] = hash if hash
|
582
|
-
params["rev"] = rev if rev
|
583
|
-
|
584
|
-
response = @session.do_get build_url("/metadata/#{@root}#{format_path(path)}", params=params)
|
585
|
-
if response.kind_of? Net::HTTPRedirection
|
586
|
-
raise DropboxNotModified.new("metadata not modified")
|
587
|
-
end
|
588
|
-
parse_response(response)
|
589
|
-
end
|
590
|
-
|
591
|
-
# Search directory for filenames matching query
|
592
|
-
#
|
593
|
-
# Arguments:
|
594
|
-
# * path: The directory to search within
|
595
|
-
# * query: The query to search on (3 character minimum)
|
596
|
-
# * file_limit: The maximum number of file entries to return/
|
597
|
-
# If the number of files exceeds this
|
598
|
-
# limit, an exception is raised. The server will return at max 1,000
|
599
|
-
# * include_deleted: Whether to include deleted files in search results
|
600
|
-
#
|
601
|
-
# Returns:
|
602
|
-
# * A Hash object with a list the metadata of the file or folders matching query
|
603
|
-
# inside path. For a detailed description of what this call returns, visit:
|
604
|
-
# https://www.dropbox.com/developers/reference/api#search
|
605
|
-
def search(path, query, file_limit=1000, include_deleted=false)
|
606
|
-
params = {
|
607
|
-
'query' => query,
|
608
|
-
'file_limit' => file_limit.to_s,
|
609
|
-
'include_deleted' => include_deleted.to_s
|
610
|
-
}
|
611
|
-
|
612
|
-
response = @session.do_get build_url("/search/#{@root}#{format_path(path)}", params)
|
613
|
-
parse_response(response)
|
614
|
-
end
|
615
|
-
|
616
|
-
# Retrive revisions of a file
|
617
|
-
#
|
618
|
-
# Arguments:
|
619
|
-
# * path: The file to fetch revisions for. Note that revisions
|
620
|
-
# are not available for folders.
|
621
|
-
# * rev_limit: The maximum number of file entries to return within
|
622
|
-
# a folder. The server will return at max 1,000 revisions.
|
623
|
-
#
|
624
|
-
# Returns:
|
625
|
-
# * A Hash object with a list of the metadata of the all the revisions of
|
626
|
-
# all matches files (up to rev_limit entries)
|
627
|
-
# For a detailed description of what this call returns, visit:
|
628
|
-
# https://www.dropbox.com/developers/reference/api#revisions
|
629
|
-
def revisions(path, rev_limit=1000)
|
630
|
-
|
631
|
-
params = {
|
632
|
-
'rev_limit' => rev_limit.to_s
|
633
|
-
}
|
634
|
-
|
635
|
-
response = @session.do_get build_url("/revisions/#{@root}#{format_path(path)}", params)
|
636
|
-
parse_response(response)
|
637
|
-
|
638
|
-
end
|
639
|
-
|
640
|
-
# Restore a file to a previous revision.
|
641
|
-
#
|
642
|
-
# Arguments:
|
643
|
-
# * path: The file to restore. Note that folders can't be restored.
|
644
|
-
# * rev: A previous rev value of the file to be restored to.
|
645
|
-
#
|
646
|
-
# Returns:
|
647
|
-
# * A Hash object with a list the metadata of the file or folders restored
|
648
|
-
# For a detailed description of what this call returns, visit:
|
649
|
-
# https://www.dropbox.com/developers/reference/api#search
|
650
|
-
def restore(path, rev)
|
651
|
-
params = {
|
652
|
-
'rev' => rev.to_s
|
653
|
-
}
|
654
|
-
|
655
|
-
response = @session.do_post build_url("/restore/#{@root}#{format_path(path)}", params)
|
656
|
-
parse_response(response)
|
657
|
-
end
|
658
|
-
|
659
|
-
# Returns a direct link to a media file
|
660
|
-
# All of Dropbox's API methods require OAuth, which may cause problems in
|
661
|
-
# situations where an application expects to be able to hit a URL multiple times
|
662
|
-
# (for example, a media player seeking around a video file). This method
|
663
|
-
# creates a time-limited URL that can be accessed without any authentication.
|
664
|
-
#
|
665
|
-
# Arguments:
|
666
|
-
# * path: The file to stream.
|
667
|
-
#
|
668
|
-
# Returns:
|
669
|
-
# * A Hash object that looks like the following:
|
670
|
-
# {'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
|
671
|
-
def media(path)
|
672
|
-
response = @session.do_get build_url("/media/#{@root}#{format_path(path)}")
|
673
|
-
parse_response(response)
|
674
|
-
end
|
675
|
-
|
676
|
-
# Get a URL to share a media file
|
677
|
-
# Shareable links created on Dropbox are time-limited, but don't require any
|
678
|
-
# authentication, so they can be given out freely. The time limit should allow
|
679
|
-
# at least a day of shareability, though users have the ability to disable
|
680
|
-
# a link from their account if they like.
|
681
|
-
#
|
682
|
-
# Arguments:
|
683
|
-
# * path: The file to share.
|
684
|
-
#
|
685
|
-
# Returns:
|
686
|
-
# * A Hash object that looks like the following example:
|
687
|
-
# {'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
|
688
|
-
# For a detailed description of what this call returns, visit:
|
689
|
-
# https://www.dropbox.com/developers/reference/api#shares
|
690
|
-
def shares(path)
|
691
|
-
response = @session.do_get build_url("/shares/#{@root}#{format_path(path)}")
|
692
|
-
parse_response(response)
|
693
|
-
end
|
694
|
-
|
695
|
-
# Download a thumbnail for an image.
|
696
|
-
#
|
697
|
-
# Arguments:
|
698
|
-
# * from_path: The path to the file to be thumbnailed.
|
699
|
-
# * size: A string describing the desired thumbnail size. At this time,
|
700
|
-
# 'small', 'medium', and 'large' are officially supported sizes
|
701
|
-
# (32x32, 64x64, and 128x128 respectively), though others may
|
702
|
-
# be available. Check https://www.dropbox.com/developers/reference/api#thumbnails
|
703
|
-
# for more details. [defaults to large]
|
704
|
-
# Returns:
|
705
|
-
# * The thumbnail data
|
706
|
-
def thumbnail(from_path, size='large')
|
707
|
-
response = thumbnail_impl(from_path, size)
|
708
|
-
parse_response(response, raw=true)
|
709
|
-
end
|
710
|
-
|
711
|
-
# Download a thumbnail for an image alongwith the image's metadata.
|
712
|
-
#
|
713
|
-
# Arguments:
|
714
|
-
# * from_path: The path to the file to be thumbnailed.
|
715
|
-
# * size: A string describing the desired thumbnail size. See thumbnail()
|
716
|
-
# for details.
|
717
|
-
# Returns:
|
718
|
-
# * The thumbnail data
|
719
|
-
# * The metadata for the image as a hash
|
720
|
-
def thumbnail_and_metadata(from_path, size='large')
|
721
|
-
response = thumbnail_impl(from_path, size)
|
722
|
-
parsed_response = parse_response(response, raw=true)
|
723
|
-
metadata = parse_metadata(response)
|
724
|
-
return parsed_response, metadata
|
725
|
-
end
|
726
|
-
|
727
|
-
# A way of letting you keep a local representation of the Dropbox folder
|
728
|
-
# heirarchy. You can periodically call delta() to get a list of "delta
|
729
|
-
# entries", which are instructions on how to update your local state to
|
730
|
-
# match the server's state.
|
731
|
-
#
|
732
|
-
# Arguments:
|
733
|
-
# * +cursor+: On the first call, omit this argument (or pass in +nil+). On
|
734
|
-
# subsequent calls, pass in the +cursor+ string returned by the previous
|
735
|
-
# call.
|
736
|
-
#
|
737
|
-
# Returns: A hash with three fields.
|
738
|
-
# * +entries+: A list of "delta entries" (described below)
|
739
|
-
# * +reset+: If +true+, you should reset local state to be an empty folder
|
740
|
-
# before processing the list of delta entries. This is only +true+ only
|
741
|
-
# in rare situations.
|
742
|
-
# * +cursor+: A string that is used to keep track of your current state.
|
743
|
-
# On the next call to delta(), pass in this value to return entries
|
744
|
-
# that were recorded since the cursor was returned.
|
745
|
-
# * +has_more+: If +true+, then there are more entries available; you can
|
746
|
-
# call delta() again immediately to retrieve those entries. If +false+,
|
747
|
-
# then wait at least 5 minutes (preferably longer) before checking again.
|
748
|
-
#
|
749
|
-
# Delta Entries: Each entry is a 2-item list of one of following forms:
|
750
|
-
# * [_path_, _metadata_]: Indicates that there is a file/folder at the given
|
751
|
-
# path. You should add the entry to your local state. (The _metadata_
|
752
|
-
# value is the same as what would be returned by the #metadata() call.)
|
753
|
-
# * If the path refers to parent folders that don't yet exist in your
|
754
|
-
# local state, create those parent folders in your local state. You
|
755
|
-
# will eventually get entries for those parent folders.
|
756
|
-
# * If the new entry is a file, replace whatever your local state has at
|
757
|
-
# _path_ with the new entry.
|
758
|
-
# * If the new entry is a folder, check what your local state has at
|
759
|
-
# _path_. If it's a file, replace it with the new entry. If it's a
|
760
|
-
# folder, apply the new _metadata_ to the folder, but do not modify
|
761
|
-
# the folder's children.
|
762
|
-
# * [path, +nil+]: Indicates that there is no file/folder at the _path_ on
|
763
|
-
# Dropbox. To update your local state to match, delete whatever is at
|
764
|
-
# _path_, including any children (you will sometimes also get separate
|
765
|
-
# delta entries for each child, but this is not guaranteed). If your
|
766
|
-
# local state doesn't have anything at _path_, ignore this entry.
|
767
|
-
#
|
768
|
-
# Remember: Dropbox treats file names in a case-insensitive but case-preserving
|
769
|
-
# way. To facilitate this, the _path_ strings above are lower-cased versions of
|
770
|
-
# the actual path. The _metadata_ dicts have the original, case-preserved path.
|
771
|
-
def delta(cursor=nil)
|
772
|
-
params = {}
|
773
|
-
if cursor
|
774
|
-
params['cursor'] = cursor
|
775
|
-
end
|
776
|
-
|
777
|
-
response = @session.do_post build_url("/delta", params)
|
778
|
-
parse_response(response)
|
779
|
-
end
|
780
|
-
|
781
|
-
# Download a thumbnail (helper method - don't call this directly).
|
782
|
-
#
|
783
|
-
# Args:
|
784
|
-
# * from_path: The path to the file to be thumbnailed.
|
785
|
-
# * size: A string describing the desired thumbnail size. See thumbnail()
|
786
|
-
# for details.
|
787
|
-
#
|
788
|
-
# Returns:
|
789
|
-
# * The HTTPResponse for the thumbnail request.
|
790
|
-
def thumbnail_impl(from_path, size='large') # :nodoc:
|
791
|
-
from_path = format_path(from_path, false)
|
792
|
-
|
793
|
-
raise DropboxError.new("size must be small medium or large. (not '#{size})") unless ['small','medium','large'].include?(size)
|
794
|
-
|
795
|
-
params = {
|
796
|
-
"size" => size
|
797
|
-
}
|
798
|
-
|
799
|
-
url = build_url("/thumbnails/#{@root}#{from_path}", params, content_server=true)
|
800
|
-
|
801
|
-
@session.do_get url
|
802
|
-
end
|
803
|
-
private :thumbnail_impl
|
804
|
-
|
805
|
-
|
806
|
-
# Creates and returns a copy ref for a specific file. The copy ref can be
|
807
|
-
# used to instantly copy that file to the Dropbox of another account.
|
808
|
-
#
|
809
|
-
# Args:
|
810
|
-
# * path: The path to the file for a copy ref to be created on.
|
811
|
-
#
|
812
|
-
# Returns:
|
813
|
-
# * A Hash object that looks like the following example:
|
814
|
-
# {"expires"=>"Fri, 31 Jan 2042 21:01:05 +0000", "copy_ref"=>"z1X6ATl6aWtzOGq0c3g5Ng"}
|
815
|
-
def create_copy_ref(path)
|
816
|
-
path = "/copy_ref/#{@root}#{format_path(path)}"
|
817
|
-
|
818
|
-
response = @session.do_get(build_url(path, {}))
|
819
|
-
|
820
|
-
parse_response(response)
|
821
|
-
end
|
822
|
-
|
823
|
-
# Adds the file referenced by the copy ref to the specified path
|
824
|
-
#
|
825
|
-
# Args:
|
826
|
-
# * copy_ref: A copy ref string that was returned from a create_copy_ref call.
|
827
|
-
# The copy_ref can be created from any other Dropbox account, or from the same account.
|
828
|
-
# * to_path: The path to where the file will be created.
|
829
|
-
#
|
830
|
-
# Returns:
|
831
|
-
# * A hash with the metadata of the new file.
|
832
|
-
def add_copy_ref(to_path, copy_ref)
|
833
|
-
path = "/fileops/copy"
|
834
|
-
|
835
|
-
params = {'from_copy_ref' => copy_ref,
|
836
|
-
'to_path' => "#{format_path(to_path)}",
|
837
|
-
'root' => @root}
|
838
|
-
|
839
|
-
response = @session.do_post(build_url(path, params))
|
840
|
-
|
841
|
-
parse_response(response)
|
842
|
-
end
|
843
|
-
|
844
|
-
def build_url(url, params=nil, content_server=false) # :nodoc:
|
845
|
-
port = 443
|
846
|
-
host = content_server ? Dropbox::API_CONTENT_SERVER : Dropbox::API_SERVER
|
847
|
-
versioned_url = "/#{Dropbox::API_VERSION}#{url}"
|
848
|
-
|
849
|
-
target = URI::Generic.new("https", nil, host, port, nil, versioned_url, nil, nil, nil)
|
850
|
-
|
851
|
-
#add a locale param if we have one
|
852
|
-
#initialize a params object is we don't have one
|
853
|
-
if @locale
|
854
|
-
(params ||= {})['locale']=@locale
|
855
|
-
end
|
856
|
-
|
857
|
-
if params
|
858
|
-
target.query = params.collect {|k,v|
|
859
|
-
CGI.escape(k) + "=" + CGI.escape(v)
|
860
|
-
}.join("&")
|
861
|
-
end
|
862
|
-
|
863
|
-
target.to_s
|
864
|
-
end
|
865
|
-
|
866
|
-
#From the oauth spec plus "/". Slash should not be ecsaped
|
867
|
-
RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/
|
868
|
-
|
869
|
-
def format_path(path, escape=true) # :nodoc:
|
870
|
-
path = path.gsub(/\/+/,"/")
|
871
|
-
# replace multiple slashes with a single one
|
872
|
-
|
873
|
-
path = path.gsub(/^\/?/,"/")
|
874
|
-
# ensure the path starts with a slash
|
875
|
-
|
876
|
-
path.gsub(/\/?$/,"")
|
877
|
-
# ensure the path doesn't end with a slash
|
878
|
-
|
879
|
-
return URI.escape(path, RESERVED_CHARACTERS) if escape
|
880
|
-
path
|
881
|
-
end
|
882
|
-
|
883
|
-
end
|