dbox 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ require 'dropbox_sdk'
2
+
3
+ # This is an example of a Rails 3 controller that authorizes an application
4
+ # and then uploads a file to the user's Dropbox.
5
+
6
+ # You must set these
7
+ APP_KEY = ""
8
+ APP_SECRET = ""
9
+ ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
10
+ #The default is :app_folder, but your application might be
11
+ #set to have full :dropbox access. Check your app at
12
+ #https://www.dropbox.com/developers/apps
13
+
14
+
15
+ # Examples routes for config/routes.rb (Rails 3)
16
+ #match 'db/authorize', :controller => 'db', :action => 'authorize'
17
+ #match 'db/upload', :controller => 'db', :action => 'upload'
18
+
19
+ class DbController < ApplicationController
20
+ def authorize
21
+ if not params[:oauth_token] then
22
+ dbsession = DropboxSession.new(APP_KEY, APP_SECRET)
23
+
24
+ session[:dropbox_session] = dbsession.serialize #serialize and save this DropboxSession
25
+
26
+ #pass to get_authorize_url a callback url that will return the user here
27
+ redirect_to dbsession.get_authorize_url url_for(:action => 'authorize')
28
+ else
29
+ # the user has returned from Dropbox
30
+ dbsession = DropboxSession.deserialize(session[:dropbox_session])
31
+ dbsession.get_access_token #we've been authorized, so now request an access_token
32
+ session[:dropbox_session] = dbsession.serialize
33
+
34
+ redirect_to :action => 'upload'
35
+ end
36
+ end
37
+
38
+ def upload
39
+ # Check if user has no dropbox session...re-direct them to authorize
40
+ return redirect_to(:action => 'authorize') unless session[:dropbox_session]
41
+
42
+ dbsession = DropboxSession.deserialize(session[:dropbox_session])
43
+ client = DropboxClient.new(dbsession, ACCESS_TYPE) #raise an exception if session not authorized
44
+ info = client.account_info # look up account information
45
+
46
+ if request.method != "POST"
47
+ # show a file upload page
48
+ render :inline =>
49
+ "#{info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag %><% end %>"
50
+ return
51
+ else
52
+ # upload the posted file to dropbox keeping the same name
53
+ resp = client.put_file(params[:file].original_filename, params[:file].read)
54
+ render :text => "Upload successful! File now at #{resp['path']}"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ # Build with: gem build gemspec.rb
2
+ Gem::Specification.new do |s|
3
+ s.name = "dropbox-sdk"
4
+
5
+ s.version = "1.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",
24
+ ]
25
+ end
@@ -0,0 +1,690 @@
1
+ require 'rubygems'
2
+ require 'uri'
3
+ require 'net/https'
4
+ require 'cgi'
5
+ require 'json'
6
+ require 'yaml'
7
+
8
+ module Dropbox
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.1"
15
+ end
16
+
17
+ # DropboxSession is responsible for holding OAuth information. It knows how to take your consumer key and secret
18
+ # and request an access token, an authorize url, and get an access token. You just need to pass it to
19
+ # DropboxClient after its been authorized.
20
+ class DropboxSession
21
+
22
+ def initialize(consumer_key, consumer_secret)
23
+ @consumer_key = consumer_key
24
+ @consumer_secret = consumer_secret
25
+ @request_token = nil
26
+ @access_token = nil
27
+ end
28
+
29
+ private
30
+
31
+ def do_http(uri, auth_token, request) # :nodoc:
32
+ http = Net::HTTP.new(uri.host, uri.port)
33
+ http.use_ssl = true
34
+
35
+ request.add_field('Authorization', build_auth_header(auth_token))
36
+
37
+ #We use this to better understand how developers are using our SDKs.
38
+ request['User-Agent'] = "OfficialDropboxRubySDK/#{Dropbox::SDK_VERSION}"
39
+
40
+ http.request(request)
41
+ end
42
+
43
+ def build_auth_header(token) # :nodoc:
44
+ header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
45
+ "oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
46
+ if token
47
+ key = URI.escape(token.key)
48
+ secret = URI.escape(token.secret)
49
+ header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
50
+ else
51
+ header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
52
+ end
53
+ header
54
+ end
55
+
56
+ def do_get_with_token(url, token, headers=nil) # :nodoc:
57
+ uri = URI.parse(url)
58
+ do_http(uri, token, Net::HTTP::Get.new(uri.request_uri))
59
+ end
60
+
61
+ public
62
+
63
+ def do_get(url, headers=nil) # :nodoc:
64
+ assert_authorized
65
+ do_get_with_token(url, @access_token)
66
+ end
67
+
68
+ def do_http_with_body(uri, request, body)
69
+ if body != nil
70
+ if body.is_a?(Hash)
71
+ form_data = {}
72
+ body.each {|k,v| form_data[k.to_s] = v if !v.nil?}
73
+ request.set_form_data(form_data)
74
+ elsif body.respond_to?(:read)
75
+ if body.respond_to?(:length)
76
+ request["Content-Length"] = body.length.to_s
77
+ elsif body.respond_to?(:stat) && body.stat.respond_to?(:size)
78
+ request["Content-Length"] = body.stat.size.to_s
79
+ else
80
+ raise ArgumentError, "Don't know how to handle 'body' (responds to 'read' but not to 'length' or 'stat.size')."
81
+ end
82
+ request.body_stream = body
83
+ else
84
+ s = body.to_s
85
+ request["Content-Length"] = s.length
86
+ request.body = s
87
+ end
88
+ end
89
+ do_http(uri, @access_token, request)
90
+ end
91
+
92
+ def do_post(url, headers=nil, body=nil) # :nodoc:
93
+ assert_authorized
94
+ uri = URI.parse(url)
95
+ do_http_with_body(uri, Net::HTTP::Post.new(uri.request_uri, headers), body)
96
+ end
97
+
98
+ def do_put(url, headers=nil, body=nil) # :nodoc:
99
+ assert_authorized
100
+ uri = URI.parse(url)
101
+ do_http_with_body(uri, Net::HTTP::Put.new(uri.request_uri, headers), body)
102
+ end
103
+
104
+
105
+ def get_token(url_end, input_token, error_message_prefix) #: nodoc:
106
+ response = do_get_with_token("https://#{Dropbox::API_SERVER}:443/#{Dropbox::API_VERSION}/oauth#{url_end}", input_token)
107
+ if not response.kind_of?(Net::HTTPSuccess) # it must be a 200
108
+ raise DropboxAuthError.new("#{error_message_prefix} Server returned #{response.code}: #{response.message}.", response)
109
+ end
110
+ parts = CGI.parse(response.body)
111
+ if !parts.has_key? "oauth_token" and parts["oauth_token"].length != 1
112
+ raise DropboxAuthError.new("Invalid response from #{url_end}: missing \"oauth_token\" parameter: #{response.body}", response)
113
+ end
114
+ if !parts.has_key? "oauth_token_secret" and parts["oauth_token_secret"].length != 1
115
+ raise DropboxAuthError.new("Invalid response from #{url_end}: missing \"oauth_token\" parameter: #{response.body}", response)
116
+ end
117
+
118
+ OAuthToken.new(parts["oauth_token"][0], parts["oauth_token_secret"][0])
119
+ end
120
+
121
+ # This returns a request token. Requests one from the dropbox server using the provided application key and secret if nessecary.
122
+ def get_request_token()
123
+ @request_token ||= get_token("/request_token", nil, "Error getting request token. Is your app key and secret correctly set?")
124
+ end
125
+
126
+ # This returns a URL that your user must visit to grant
127
+ # permissions to this application.
128
+ def get_authorize_url(callback=nil)
129
+ get_request_token()
130
+
131
+ url = "/#{Dropbox::API_VERSION}/oauth/authorize?oauth_token=#{URI.escape(@request_token.key)}"
132
+ if callback
133
+ url += "&oauth_callback=#{URI.escape(callback)}"
134
+ end
135
+ if @locale
136
+ url += "&locale=#{URI.escape(@locale)}"
137
+ end
138
+
139
+ "https://#{Dropbox::WEB_SERVER}#{url}"
140
+ end
141
+
142
+ # Clears the access_token
143
+ def clear_access_token
144
+ @access_token = nil
145
+ end
146
+
147
+ # Returns the request token, or nil if one hasn't been acquired yet.
148
+ def request_token
149
+ @request_token
150
+ end
151
+
152
+ # Returns the access token, or nil if one hasn't been acquired yet.
153
+ def access_token
154
+ @access_token
155
+ end
156
+
157
+ # Given a saved request token and secret, set this location's token and secret
158
+ # * token - this is the request token
159
+ # * secret - this is the request token secret
160
+ def set_request_token(key, secret)
161
+ @request_token = OAuthToken.new(key, secret)
162
+ end
163
+
164
+ # Given a saved access token and secret, you set this Session to use that token and secret
165
+ # * token - this is the access token
166
+ # * secret - this is the access token secret
167
+ def set_access_token(key, secret)
168
+ @access_token = OAuthToken.new(key, secret)
169
+ end
170
+
171
+ # Returns the access token. If this DropboxSession doesn't yet have an access_token, it requests one
172
+ # using the request_token generate from your app's token and secret. This request will fail unless
173
+ # your user has gone to the authorize_url and approved your request
174
+ def get_access_token
175
+ return @access_token if authorized?
176
+
177
+ if @request_token.nil?
178
+ raise DropboxAuthError.new("No request token. You must set this or get an authorize url first.")
179
+ end
180
+
181
+ @access_token = get_token("/access_token", @request_token, "Couldn't get access token.")
182
+ end
183
+
184
+ # If we have an access token, then do nothing. If not, throw a RuntimeError.
185
+ def assert_authorized
186
+ unless authorized?
187
+ raise RuntimeError.new('Session does not yet have a request token')
188
+ end
189
+ end
190
+
191
+ # Returns true if this Session has been authorized and has an access_token.
192
+ def authorized?
193
+ !!@access_token
194
+ end
195
+
196
+ # serialize the DropboxSession.
197
+ # At DropboxSession's state is capture in three key/secret pairs. Consumer, request, and access.
198
+ # Serialize returns these in a YAML string, generated from a converted array of the form:
199
+ # [consumer_key, consumer_secret, request_token.token, request_token.secret, access_token.token, access_token.secret]
200
+ # access_token is only included if it already exists in the DropboxSesssion
201
+ def serialize
202
+ toreturn = []
203
+ if @access_token
204
+ toreturn.push @access_token.secret, @access_token.key
205
+ end
206
+
207
+ get_request_token
208
+
209
+ toreturn.push @request_token.secret, @request_token.key
210
+ toreturn.push @consumer_secret, @consumer_key
211
+
212
+ toreturn.to_yaml
213
+ end
214
+
215
+ # Takes a serialized DropboxSession YAML String and returns a new DropboxSession object
216
+ def self.deserialize(ser)
217
+ ser = YAML::load(ser)
218
+ session = DropboxSession.new(ser.pop, ser.pop)
219
+ session.set_request_token(ser.pop, ser.pop)
220
+
221
+ if ser.length > 0
222
+ session.set_access_token(ser.pop, ser.pop)
223
+ end
224
+ session
225
+ end
226
+ end
227
+
228
+
229
+ # A class that represents either an OAuth request token or an OAuth access token.
230
+ class OAuthToken
231
+ def initialize(key, secret)
232
+ @key = key
233
+ @secret = secret
234
+ end
235
+
236
+ def key
237
+ @key
238
+ end
239
+
240
+ def secret
241
+ @secret
242
+ end
243
+ end
244
+
245
+
246
+ # This is the usual error raised on any Dropbox related Errors
247
+ class DropboxError < RuntimeError
248
+ attr_accessor :http_response, :error, :user_error
249
+ def initialize(error, http_response=nil, user_error=nil)
250
+ @error = error
251
+ @http_response = http_response
252
+ @user_error = user_error
253
+ end
254
+
255
+ def to_s
256
+ return "#{user_error} (#{error})" if user_error
257
+ "#{error}"
258
+ end
259
+ end
260
+
261
+ # This is the error raised on Authentication failures. Usually this means
262
+ # one of three things
263
+ # * Your user failed to go to the authorize url and approve your application
264
+ # * You set an invalid or expired token and secret on your Session
265
+ # * Your user deauthorized the application after you stored a valid token and secret
266
+ class DropboxAuthError < DropboxError
267
+ end
268
+
269
+ # This is raised when you call metadata with a hash and that hash matches
270
+ # See documentation in metadata function
271
+ class DropboxNotModified < DropboxError
272
+ end
273
+
274
+ # This is the Dropbox Client API you'll be working with most often. You need to give it
275
+ # a DropboxSession which has already been authorize, or which it can authorize.
276
+ class DropboxClient
277
+
278
+ # Initialize a new DropboxClient. You need to get it a session which either has been authorized. See
279
+ # documentation on DropboxSession for how to authorize it.
280
+ def initialize(session, root="app_folder", locale=nil)
281
+ session.get_access_token
282
+
283
+ @root = root.to_s # If they passed in a symbol, make it a string
284
+
285
+ if not ["dropbox","app_folder"].include?(@root)
286
+ raise DropboxError.new("root must be :dropbox or :app_folder")
287
+ end
288
+ if @root == "app_folder"
289
+ #App Folder is the name of the access type, but for historical reasons
290
+ #sandbox is the URL root component that indicates this
291
+ @root = "sandbox"
292
+ end
293
+
294
+ @locale = locale
295
+ @session = session
296
+ end
297
+
298
+ # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
299
+ # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
300
+ def parse_response(response, raw=false) # :nodoc:
301
+ if response.kind_of?(Net::HTTPServerError)
302
+ raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
303
+ elsif response.kind_of?(Net::HTTPUnauthorized)
304
+ raise DropboxAuthError.new(response, "User is not authenticated.")
305
+ elsif not response.kind_of?(Net::HTTPSuccess)
306
+ begin
307
+ d = JSON.parse(response.body)
308
+ rescue
309
+ raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
310
+ end
311
+ if d['user_error'] and d['error']
312
+ raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
313
+ elsif d['error']
314
+ raise DropboxError.new(d['error'], response)
315
+ else
316
+ raise DropboxError.new(response.body, response)
317
+ end
318
+ end
319
+
320
+ return response.body if raw
321
+
322
+ begin
323
+ return JSON.parse(response.body)
324
+ rescue JSON::ParserError
325
+ raise DropboxError.new("Unable to parse JSON response", response)
326
+ end
327
+ end
328
+
329
+
330
+ # Returns account info in a Hash object
331
+ #
332
+ # For a detailed description of what this call returns, visit:
333
+ # https://www.dropbox.com/developers/docs#account-info
334
+ def account_info()
335
+ response = @session.do_get build_url("/account/info")
336
+ parse_response(response)
337
+ end
338
+
339
+ # Uploads a file to a server. This uses the HTTP PUT upload method for simplicity
340
+ #
341
+ # Arguments:
342
+ # * to_path: The directory path to upload the file to. If the destination
343
+ # directory does not yet exist, it will be created.
344
+ # * file_obj: A file-like object to upload. If you would like, you can
345
+ # pass a string as file_obj.
346
+ # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
347
+ # If overwrite is False and a file already exists there, Dropbox
348
+ # will rename the upload to make sure it doesn't overwrite anything.
349
+ # You must check the returned metadata to know what this new name is.
350
+ # This field should only be True if your intent is to potentially
351
+ # clobber changes to a file that you don't know about.
352
+ # * parent_rev: The rev field from the 'parent' of this upload. [optional]
353
+ # If your intent is to update the file at the given path, you should
354
+ # pass the parent_rev parameter set to the rev value from the most recent
355
+ # metadata you have of the existing file at that path. If the server
356
+ # has a more recent version of the file at the specified path, it will
357
+ # automatically rename your uploaded file, spinning off a conflict.
358
+ # Using this parameter effectively causes the overwrite parameter to be ignored.
359
+ # The file will always be overwritten if you send the most-recent parent_rev,
360
+ # and it will never be overwritten you send a less-recent one.
361
+ # Returns:
362
+ # * a Hash containing the metadata of the newly uploaded file. The file may have a different name if it conflicted.
363
+ #
364
+ # Simple Example
365
+ # client = DropboxClient(session, :app_folder)
366
+ # #session is a DropboxSession I've already authorized
367
+ # client.put_file('/test_file_on_dropbox', open('/tmp/test_file'))
368
+ # This will upload the "/tmp/test_file" from my computer into the root of my App's app folder
369
+ # and call it "test_file_on_dropbox".
370
+ # The file will not overwrite any pre-existing file.
371
+ def put_file(to_path, file_obj, overwrite=false, parent_rev=nil)
372
+ path = "/files_put/#{@root}#{format_path(to_path)}"
373
+
374
+ params = {
375
+ 'overwrite' => overwrite.to_s
376
+ }
377
+
378
+ params['parent_rev'] = parent_rev unless parent_rev.nil?
379
+
380
+ response = @session.do_put(build_url(path, params, content_server=true),
381
+ {"Content-Type" => "application/octet-stream"},
382
+ file_obj)
383
+
384
+ parse_response(response)
385
+ end
386
+
387
+ # Download a file
388
+ #
389
+ # Args:
390
+ # * from_path: The path to the file to be downloaded
391
+ # * rev: A previous revision value of the file to be downloaded
392
+ #
393
+ # Returns:
394
+ # * The file contents.
395
+ def get_file(from_path, rev=nil)
396
+ params = {}
397
+ params['rev'] = rev.to_s if rev
398
+
399
+ path = "/files/#{@root}#{format_path(from_path)}"
400
+ response = @session.do_get build_url(path, params, content_server=true)
401
+
402
+ parse_response(response, raw=true)
403
+ end
404
+
405
+ # Copy a file or folder to a new location.
406
+ #
407
+ # Arguments:
408
+ # * from_path: The path to the file or folder to be copied.
409
+ # * to_path: The destination path of the file or folder to be copied.
410
+ # This parameter should include the destination filename (e.g.
411
+ # from_path: '/test.txt', to_path: '/dir/test.txt'). If there's
412
+ # already a file at the to_path, this copy will be renamed to
413
+ # be unique.
414
+ #
415
+ # Returns:
416
+ # * A hash with the metadata of the new copy of the file or folder.
417
+ # For a detailed description of what this call returns, visit:
418
+ # https://www.dropbox.com/developers/docs#fileops-copy
419
+ def file_copy(from_path, to_path)
420
+ params = {
421
+ "root" => @root,
422
+ "from_path" => format_path(from_path, false),
423
+ "to_path" => format_path(to_path, false),
424
+ }
425
+ response = @session.do_post build_url("/fileops/copy", params)
426
+ parse_response(response)
427
+ end
428
+
429
+ # Create a folder.
430
+ #
431
+ # Arguments:
432
+ # * path: The path of the new folder.
433
+ #
434
+ # Returns:
435
+ # * A hash with the metadata of the newly created folder.
436
+ # For a detailed description of what this call returns, visit:
437
+ # https://www.dropbox.com/developers/docs#fileops-create-folder
438
+ def file_create_folder(path)
439
+ params = {
440
+ "root" => @root,
441
+ "path" => format_path(path, false),
442
+ }
443
+ response = @session.do_post build_url("/fileops/create_folder", params)
444
+
445
+ parse_response(response)
446
+ end
447
+
448
+ # Deletes a file
449
+ #
450
+ # Arguments:
451
+ # * path: The path of the file to delete
452
+ #
453
+ # Returns:
454
+ # * A Hash with the metadata of file just deleted.
455
+ # For a detailed description of what this call returns, visit:
456
+ # https://www.dropbox.com/developers/docs#fileops-delete
457
+ def file_delete(path)
458
+ params = {
459
+ "root" => @root,
460
+ "path" => format_path(path, false),
461
+ }
462
+ response = @session.do_post build_url("/fileops/delete", params)
463
+ parse_response(response)
464
+ end
465
+
466
+ # Moves a file
467
+ #
468
+ # Arguments:
469
+ # * from_path: The path of the file to be moved
470
+ # * to_path: The destination path of the file or folder to be moved
471
+ # If the file or folder already exists, it will be renamed to be unique.
472
+ #
473
+ # Returns:
474
+ # * A Hash with the metadata of file or folder just moved.
475
+ # For a detailed description of what this call returns, visit:
476
+ # https://www.dropbox.com/developers/docs#fileops-delete
477
+ def file_move(from_path, to_path)
478
+ params = {
479
+ "root" => @root,
480
+ "from_path" => format_path(from_path, false),
481
+ "to_path" => format_path(to_path, false),
482
+ }
483
+ response = @session.do_post build_url("/fileops/move", params)
484
+ parse_response(response)
485
+ end
486
+
487
+ # Retrives metadata for a file or folder
488
+ #
489
+ # Arguments:
490
+ # * path: The path to the file or folder.
491
+ # * list: Whether to list all contained files (only applies when
492
+ # path refers to a folder).
493
+ # * file_limit: The maximum number of file entries to return within
494
+ # a folder. If the number of files in the directory exceeds this
495
+ # limit, an exception is raised. The server will return at max
496
+ # 10,000 files within a folder.
497
+ # * hash: Every directory listing has a hash parameter attached that
498
+ # can then be passed back into this function later to save on
499
+ # bandwidth. Rather than returning an unchanged folder's contents, if
500
+ # the hash matches a DropboxNotModified exception is raised.
501
+ #
502
+ # Returns:
503
+ # * A Hash object with the metadata of the file or folder (and contained files if
504
+ # appropriate). For a detailed description of what this call returns, visit:
505
+ # https://www.dropbox.com/developers/docs#metadata
506
+ def metadata(path, file_limit=10000, list=true, hash=nil)
507
+ params = {
508
+ "file_limit" => file_limit.to_s,
509
+ "list" => list.to_s
510
+ }
511
+
512
+ params["hash"] = hash if hash
513
+
514
+ response = @session.do_get build_url("/metadata/#{@root}#{format_path(path)}", params=params)
515
+ if response.kind_of? Net::HTTPRedirection
516
+ raise DropboxNotModified.new("metadata not modified")
517
+ end
518
+ parse_response(response)
519
+ end
520
+
521
+ # Search directory for filenames matching query
522
+ #
523
+ # Arguments:
524
+ # * path: The directory to search within
525
+ # * query: The query to search on (3 character minimum)
526
+ # * file_limit: The maximum number of file entries to return/
527
+ # If the number of files exceeds this
528
+ # limit, an exception is raised. The server will return at max 10,000
529
+ # * include_deleted: Whether to include deleted files in search results
530
+ #
531
+ # Returns:
532
+ # * A Hash object with a list the metadata of the file or folders matching query
533
+ # inside path. For a detailed description of what this call returns, visit:
534
+ # https://www.dropbox.com/developers/docs#search
535
+ def search(path, query, file_limit=10000, include_deleted=false)
536
+ params = {
537
+ 'query' => query,
538
+ 'file_limit' => file_limit.to_s,
539
+ 'include_deleted' => include_deleted.to_s
540
+ }
541
+
542
+ response = @session.do_get build_url("/search/#{@root}#{format_path(path)}", params)
543
+ parse_response(response)
544
+ end
545
+
546
+ # Retrive revisions of a file
547
+ #
548
+ # Arguments:
549
+ # * path: The file to fetch revisions for. Note that revisions
550
+ # are not available for folders.
551
+ # * rev_limit: The maximum number of file entries to return within
552
+ # a folder. The server will return at max 1,000 revisions.
553
+ #
554
+ # Returns:
555
+ # * A Hash object with a list of the metadata of the all the revisions of
556
+ # all matches files (up to rev_limit entries)
557
+ # For a detailed description of what this call returns, visit:
558
+ # https://www.dropbox.com/developers/docs#revisions
559
+ def revisions(path, rev_limit=1000)
560
+
561
+ params = {
562
+ 'rev_limit' => rev_limit.to_s
563
+ }
564
+
565
+ response = @session.do_get build_url("/revisions/#{@root}#{format_path(path)}", params)
566
+ parse_response(response)
567
+
568
+ end
569
+
570
+ # Restore a file to a previous revision.
571
+ #
572
+ # Arguments:
573
+ # * path: The file to restore. Note that folders can't be restored.
574
+ # * rev: A previous rev value of the file to be restored to.
575
+ #
576
+ # Returns:
577
+ # * A Hash object with a list the metadata of the file or folders restored
578
+ # For a detailed description of what this call returns, visit:
579
+ # https://www.dropbox.com/developers/docs#search
580
+ def restore(path, rev)
581
+ params = {
582
+ 'rev' => rev.to_s
583
+ }
584
+
585
+ response = @session.do_post build_url("/restore/#{@root}#{format_path(path)}", params)
586
+ parse_response(response)
587
+ end
588
+
589
+ # Returns a direct link to a media file
590
+ # All of Dropbox's API methods require OAuth, which may cause problems in
591
+ # situations where an application expects to be able to hit a URL multiple times
592
+ # (for example, a media player seeking around a video file). This method
593
+ # creates a time-limited URL that can be accessed without any authentication.
594
+ #
595
+ # Arguments:
596
+ # * path: The file to stream.
597
+ #
598
+ # Returns:
599
+ # * A Hash object that looks like the following:
600
+ # {'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
601
+ def media(path)
602
+ response = @session.do_get build_url("/media/#{@root}#{format_path(path)}")
603
+ parse_response(response)
604
+ end
605
+
606
+ # Get a URL to share a media file
607
+ # Shareable links created on Dropbox are time-limited, but don't require any
608
+ # authentication, so they can be given out freely. The time limit should allow
609
+ # at least a day of shareability, though users have the ability to disable
610
+ # a link from their account if they like.
611
+ #
612
+ # Arguments:
613
+ # * path: The file to share.
614
+ #
615
+ # Returns:
616
+ # * A Hash object that looks like the following example:
617
+ # {'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
618
+ # For a detailed description of what this call returns, visit:
619
+ # https://www.dropbox.com/developers/docs#share
620
+ def shares(path)
621
+ response = @session.do_get build_url("/shares/#{@root}#{format_path(path)}")
622
+ parse_response(response)
623
+ end
624
+
625
+ # Download a thumbnail for an image.
626
+ #
627
+ # Arguments:
628
+ # * from_path: The path to the file to be thumbnailed.
629
+ # * size: A string describing the desired thumbnail size. At this time,
630
+ # 'small', 'medium', and 'large' are officially supported sizes
631
+ # (32x32, 64x64, and 128x128 respectively), though others may
632
+ # be available. Check https://www.dropbox.com/developers/docs#thumbnails
633
+ # for more details. [defaults to large]
634
+ # Returns:
635
+ # * The thumbnail data
636
+ def thumbnail(from_path, size='large')
637
+ from_path = format_path(from_path, false)
638
+
639
+ raise DropboxError.new("size must be small medium or large. (not '#{size})") unless ['small','medium','large'].include?(size)
640
+
641
+ params = {
642
+ "size" => size
643
+ }
644
+
645
+ url = build_url("/thumbnails/#{@root}#{from_path}", params, content_server=true)
646
+
647
+ response = @session.do_get url
648
+ parse_response(response, raw=true)
649
+ end
650
+
651
+ def build_url(url, params=nil, content_server=false) # :nodoc:
652
+ port = 443
653
+ host = content_server ? Dropbox::API_CONTENT_SERVER : Dropbox::API_SERVER
654
+ versioned_url = "/#{Dropbox::API_VERSION}#{url}"
655
+
656
+ target = URI::Generic.new("https", nil, host, port, nil, versioned_url, nil, nil, nil)
657
+
658
+ #add a locale param if we have one
659
+ #initialize a params object is we don't have one
660
+ if @locale
661
+ (params ||= {})['locale']=@locale
662
+ end
663
+
664
+ if params
665
+ target.query = params.collect {|k,v|
666
+ URI.escape(k) + "=" + URI.escape(v)
667
+ }.join("&")
668
+ end
669
+
670
+ target.to_s
671
+ end
672
+
673
+ #From the oauth spec plus "/". Slash should not be ecsaped
674
+ RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/
675
+
676
+ def format_path(path, escape=true) # :nodoc:
677
+ path = path.gsub(/\/+/,"/")
678
+ # replace multiple slashes with a single one
679
+
680
+ path = path.gsub(/^\/?/,"/")
681
+ # ensure the path starts with a slash
682
+
683
+ path.gsub(/\/?$/,"")
684
+ # ensure the path doesn't end with a slash
685
+
686
+ return URI.escape(path, RESERVED_CHARACTERS) if escape
687
+ path
688
+ end
689
+
690
+ end