dropbox-sdk 1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ 1.0 (2011-8-16)
2
+ * Backwards compatibility broken
3
+ - Changed interface
4
+ - Change 'sandbox' references to 'app_folder'
5
+ * Updated SDK to Dropbox API Version 1, supporting all calls
6
+ - Added 'rev' parameter to metadata and get_file
7
+ - Added 'parent_rev' parameter to put_file
8
+ - Added search, share, media, revisions, and restore
9
+ - put_file uses /files_put instead of multipart POST
10
+ - Removed methods for calls that were removed from v1 of the REST API
11
+ * Changed return format for calls
12
+ - On error (non-200 response), an exception is raised
13
+ - On success, the JSON is parsed and a Hash is returned
14
+ * Updated examples
15
+ - Improved CLI example
16
+ - Added a Ruby on Rails 3 controller example
17
+ - Added a web based file browser/uploader that uses Sinatra
18
+ * put_file no longer takes a "name" arugment, only takes a full path
19
+ * Removed reliance on config files
20
+ * Assorted bugfixes and improvements
21
+ * All calls are now made over SSL
22
+ * Fully documented code for RDoc generation
23
+ * Added a CHANGELOG
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2011 Dropbox Inc., http://www.dropbox.com/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,7 @@
1
+ Getting started with the Dropbox Ruby SDK:
2
+ 1. Install json and oauth ruby gems with:
3
+ gem install json oauth
4
+ 2. Edit cli_example.rb to to include your APP_KEY and APP_SECRET
5
+ 3. Try running the example app: 'ruby clie_example.rb'.
6
+ 4. See dropbox_controller.rb for an example Ruby on Rails controller. It needs an APP_KEY and APP_SECRET set too.
7
+ 5. Check out the dropbox website for reference and help! http://dropbox.com/developers_beta
@@ -0,0 +1,197 @@
1
+ require './lib/dropbox_sdk'
2
+ require 'pp'
3
+
4
+ ####
5
+ # An example app using the Dropbox API Ruby Client
6
+ # This ruby script sets up a basic command line interface (CLI)
7
+ # that prompts a user to authenticate on the web, then
8
+ # allows them to type commands to manipulate their dropbox.
9
+ ####
10
+
11
+ # You must use your Dropbox App key and secret to use the API.
12
+ # Find this at https://www.dropbox.com/developers
13
+ APP_KEY = ""
14
+ APP_SECRET = ""
15
+ ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
16
+ #The default is :app_folder, but your application might be
17
+ #set to have full :dropbox access. Check your app at
18
+ #https://www.dropbox.com/developers/apps
19
+
20
+ class DropboxCLI
21
+ LOGIN_REQUIRED = %w{put get cp mv rm ls mkdir info logout search}
22
+
23
+ def initialize
24
+ if APP_KEY == '' or APP_SECRET == ''
25
+ puts "You must set your APP_KEY and APP_SECRET in cli_example.rb!"
26
+ puts "Find this in your apps page at https://www.dropbox.com/developers/"
27
+ exit
28
+ end
29
+
30
+ @session = DropboxSession.new(APP_KEY, APP_SECRET)
31
+ @client = nil
32
+ end
33
+
34
+ def login
35
+ ########
36
+ # Instead of going to a authorize URL, you can set a access token key and secret
37
+ # from a previous session
38
+ ########
39
+ # @session.set_access_token('key', 'secret')
40
+
41
+ if @session.authorized?
42
+ puts "already logged in!"
43
+ else
44
+
45
+ # grab the request token for session
46
+ @session.get_request_token
47
+
48
+ authorize_url = @session.get_authorize_url
49
+ puts "Got a request token. Your request token key is #{@session.token} and your token secret is #{@session.secret}"
50
+
51
+ # make the user log in and authorize this token
52
+ puts "AUTHORIZING", authorize_url, "Please visit that web page and hit 'Allow', then hit Enter here."
53
+ gets
54
+
55
+ # get the access token from the server. Its then stored in the session.
56
+ @session.get_access_token
57
+
58
+ end
59
+ puts "You are logged in. Your access token key is #{@session.token} your secret is #{@session.secret}"
60
+ @client = DropboxClient.new(@session, ACCESS_TYPE)
61
+ end
62
+
63
+ def command_loop
64
+ puts "Enter a command or 'help' or 'exit'"
65
+ command_line = ''
66
+ while command_line.strip != 'exit'
67
+ begin
68
+ execute_dropbox_command(command_line)
69
+ rescue RuntimeError => e
70
+ puts "Command Line Error! #{e.class}: #{e}"
71
+ puts e.backtrace
72
+ end
73
+ print '> '
74
+ command_line = gets.strip
75
+ end
76
+ puts 'goodbye'
77
+ exit(0)
78
+ end
79
+
80
+ def execute_dropbox_command(cmd_line)
81
+ command = cmd_line.split
82
+ method = command.first
83
+ if LOGIN_REQUIRED.include? method
84
+ if @client
85
+ send(method.to_sym, command)
86
+ else
87
+ puts 'must be logged in; type \'login\' to get started.'
88
+ end
89
+ elsif ['login', 'help'].include? method
90
+ send(method.to_sym)
91
+ else
92
+ if command.first && !command.first.strip.empty?
93
+ puts 'invalid command. type \'help\' to see commands.'
94
+ end
95
+ end
96
+ end
97
+
98
+ def logout
99
+ @session.clear_access_token
100
+ puts "You are logged out."
101
+ @client = nil
102
+ end
103
+
104
+ def put(command)
105
+ fname = command[1]
106
+
107
+ #If the user didn't specifiy the file name, just use the name of the file on disk
108
+ if command[2]
109
+ new_name = command[2]
110
+ else
111
+ new_name = File.basename(fname)
112
+ end
113
+
114
+ if fname && !fname.empty? && File.exists?(fname) && (File.ftype(fname) == 'file') && File.stat(fname).readable?
115
+ #This is where we call the the Dropbox Client
116
+ pp @client.put_file(new_name, open(fname))
117
+ else
118
+ puts "couldn't find the file #{ fname }"
119
+ end
120
+ end
121
+
122
+ def get(command)
123
+ dest = command[2]
124
+ if !command[1] || command[1].empty?
125
+ puts "please specify item to get"
126
+ elsif !dest || dest.empty?
127
+ puts "please specify full local path to dest, i.e. the file to write to"
128
+ elsif File.exists?(dest)
129
+ puts "error: File #{dest} already exists."
130
+ else
131
+ src = clean_up(command[1])
132
+ out = @client.get_file('/' + src)
133
+ open(dest, 'w'){|f| f.puts out }
134
+ puts "wrote file #{dest}."
135
+ end
136
+ end
137
+
138
+ def mkdir(command)
139
+ pp @client.file_create_folder(command[1])
140
+ end
141
+
142
+ def thumbnail(command)
143
+ command[2] ||= 'small'
144
+ pp @client.thumbnail(command[1], command[2])
145
+ end
146
+
147
+ def cp(command)
148
+ src = clean_up(command[1])
149
+ dest = clean_up(command[2])
150
+ pp @client.file_copy(src, dest)
151
+ end
152
+
153
+ def mv(command)
154
+ src = clean_up(command[1])
155
+ dest = clean_up(command[2])
156
+ pp @client.file_move(src, dest)
157
+ end
158
+
159
+ def rm(command)
160
+ pp @client.file_delete(clean_up(command[1]))
161
+ end
162
+
163
+ def search(command)
164
+ resp = @client.search('/',clean_up(command[1]))
165
+
166
+ for item in resp
167
+ puts item['path']
168
+ end
169
+ end
170
+
171
+ def info(command)
172
+ pp @client.account_info
173
+ end
174
+
175
+ def ls(command)
176
+ command[1] = '/' + clean_up(command[1] || '')
177
+ resp = @client.metadata(command[1])
178
+
179
+ if resp['contents'].length > 0
180
+ for item in resp['contents']
181
+ puts item['path']
182
+ end
183
+ end
184
+ end
185
+
186
+ def help
187
+ puts "commands are: login #{LOGIN_REQUIRED.join(' ')} help exit"
188
+ end
189
+
190
+ def clean_up(str)
191
+ return str.gsub(/^\/+/, '') if str
192
+ str
193
+ end
194
+ end
195
+
196
+ cli = DropboxCLI.new
197
+ cli.command_loop
@@ -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,607 @@
1
+ require 'rubygems'
2
+ require 'oauth'
3
+ require 'json'
4
+ require 'uri'
5
+ require 'yaml'
6
+
7
+ DROPBOX_API_SERVER = "api.dropbox.com"
8
+ DROPBOX_API_CONTENT_SERVER = "api-content.dropbox.com"
9
+
10
+ API_VERSION = 1
11
+
12
+ # DropboxSession is responsible for holding OAuth information. It knows how to take your consumer key and secret
13
+ # and request an access token, an authorize url, and get an access token. You just need to pass it to
14
+ # DropboxClient after its been authorized.
15
+ class DropboxSession
16
+ def initialize(key, secret)
17
+ @consumer_key = key
18
+ @consumer_secret = secret
19
+ @oauth_conf = {
20
+ :site => "https://" + DROPBOX_API_SERVER,
21
+ :scheme => :header,
22
+ :http_method => :post,
23
+ :request_token_url => "/#{API_VERSION}/oauth/request_token",
24
+ :access_token_url => "/#{API_VERSION}/oauth/access_token",
25
+ :authorize_url => "/#{API_VERSION}/oauth/authorize",
26
+ }
27
+
28
+ @consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, @oauth_conf)
29
+ @access_token = nil
30
+ @request_token = nil
31
+ end
32
+
33
+ # This gets a request token. Callbacks are excluded, and any arguments provided are passed on
34
+ # to the oauth gem's get_request_token call.
35
+ def get_request_token(*args)
36
+ begin
37
+ @request_token ||= @consumer.get_request_token({:exclude_callback => true}, *args)
38
+ rescue OAuth::Unauthorized => e
39
+ raise DropboxAuthError.new("Could not get request token, unauthorized. Is your app key and secret correct? #{e}")
40
+ end
41
+ end
42
+
43
+ # This returns a URL that your user must visit to grant
44
+ # permissions to this application.
45
+ def get_authorize_url(callback=nil, *args)
46
+ get_request_token(*args)
47
+
48
+ url = @request_token.authorize_url
49
+ if callback
50
+ url += "&oauth_callback=" + URI.escape(callback)
51
+ end
52
+
53
+ "https://www.dropbox.com" + url
54
+ end
55
+
56
+ # Clears the access_token
57
+ def clear_access_token
58
+ @access_token = nil
59
+ end
60
+
61
+ # Given a saved request token and secret, set this location's token and secret
62
+ # * token - this is the request token
63
+ # * secret - this is the request token secret
64
+ def set_request_token(key, secret)
65
+ @request_token = OAuth::RequestToken.new(@consumer, key, secret)
66
+ end
67
+
68
+ # Given a saved access token and secret, you set this Session to use that token and secret
69
+ # * token - this is the access token
70
+ # * secret - this is the access token secret
71
+ def set_access_token(key, secret)
72
+ @access_token = OAuth::AccessToken.from_hash(@consumer, {:oauth_token => key, :oauth_token_secret => secret})
73
+ end
74
+
75
+ def check_authorized
76
+ ##this check is applied before the token and secret methods
77
+ raise DropboxError.new('Session does not yet have a request token') unless authorized?
78
+ end
79
+
80
+ # Returns the current oauth access or request token.
81
+ def token
82
+ (@access_token || @request_token).token
83
+ end
84
+
85
+ # Returns the current oauth access or request token secret.
86
+ def secret
87
+ (@access_token || @request_token).secret
88
+ end
89
+
90
+ # Returns the access token. If this DropboxSession doesn't yet have an access_token, it requests one
91
+ # using the request_token generate from your app's token and secret. This request will fail unless
92
+ # your user has got to the authorize_url and approved your request
93
+ def get_access_token
94
+ if @access_token.nil?
95
+ if @request_token.nil?
96
+ raise DropboxAuthError.new("No request token. You must set this or get an authorize url first.")
97
+ end
98
+
99
+ begin
100
+ @access_token = @request_token.get_access_token
101
+ rescue OAuth::Unauthorized => e
102
+ raise DropboxAuthError.new("Could not get access token, unauthorized. Did you go to authorize_url? #{e}")
103
+ end
104
+ end
105
+ @access_token
106
+ end
107
+
108
+ # Returns true if this Session has been authorized and has an access_token.
109
+ def authorized?
110
+ !!@access_token
111
+ end
112
+
113
+ # serialize the DropboxSession.
114
+ # At DropboxSession's state is capture in three key/secret pairs. Consumer, request, and access.
115
+ # This takes the form of an array that is then converted to yaml
116
+ # [consumer_key, consumer_secret, request_token.token, request_token.secret, access_token.token, access_token.secret]
117
+ # access_token is only included if it already exists in the DropboxSesssion
118
+ def serialize
119
+ toreturn = []
120
+ if @access_token
121
+ toreturn.push @access_token.secret, @access_token.token
122
+ end
123
+
124
+ get_request_token unless @request_token
125
+
126
+ toreturn.push @request_token.secret, @request_token.token
127
+ toreturn.push @consumer_secret, @consumer_key
128
+
129
+ toreturn.to_yaml
130
+ end
131
+
132
+ # Takes a serialized DropboxSession and returns a new DropboxSession object
133
+ def self.deserialize(ser)
134
+ ser = YAML::load(ser)
135
+ session = DropboxSession.new(ser.pop, ser.pop)
136
+ session.set_request_token(ser.pop, ser.pop)
137
+
138
+ if ser.length > 0
139
+ session.set_access_token(ser.pop, ser.pop)
140
+ end
141
+ session
142
+ end
143
+ end
144
+
145
+
146
+
147
+ # This is the usual error raised on any Dropbox related Errors
148
+ class DropboxError < RuntimeError
149
+ attr_accessor :http_response, :error, :user_error
150
+ def initialize(error, http_response=nil, user_error=nil)
151
+ @error = error
152
+ @http_response = http_response
153
+ @user_error = user_error
154
+ end
155
+
156
+ def to_s
157
+ return "#{user_error} (#{error})" if user_error
158
+ "#{error}"
159
+ end
160
+ end
161
+
162
+ # This is the error raised on Authentication failures. Usually this means
163
+ # one of three things
164
+ # * Your user failed to go to the authorize url and approve your application
165
+ # * You set an invalid or expired token and secret on your Session
166
+ # * Your user deauthorized the application after you stored a valid token and secret
167
+ class DropboxAuthError < DropboxError
168
+ end
169
+
170
+ # This is raised when you call metadata with a hash and that hash matches
171
+ # See documentation in metadata function
172
+ class DropboxNotModified < DropboxError
173
+ end
174
+
175
+ # This is the Dropbox Client API you'll be working with most often. You need to give it
176
+ # a DropboxSession which has already been authorize, or which it can authorize.
177
+ class DropboxClient
178
+
179
+ # Initialize a new DropboxClient. You need to get it a session which either has been authorized. See
180
+ # documentation on DropboxSession for how to authorize it.
181
+ def initialize(session, root="app_folder", locale=nil)
182
+ if not session.authorized?
183
+ begin
184
+ ## attempt to get an access token and authorize the session
185
+ session.get_access_token
186
+ rescue OAuth::Unauthorized => e
187
+ raise DropboxAuthError.new("Could not initialize. Failed to get access token from Session. Error was: #{ e.message }")
188
+ # If this was raised, the user probably didn't go to auth.get_authorize_url
189
+ end
190
+ end
191
+
192
+ @root = root.to_s # If they passed in a symbol, make it a string
193
+
194
+ if not ["dropbox","app_folder"].include?(@root)
195
+ raise DropboxError.new("root must be :dropbox or :app_folder")
196
+ end
197
+ if @root == "app_folder"
198
+ #App Folder is the name of the access type, but for historical reasons
199
+ #sandbox is the URL root compontent that indicates this
200
+ @root = "sandbox"
201
+ end
202
+
203
+ @locale = locale
204
+ @session = session
205
+ @token = session.get_access_token
206
+
207
+ #There's no gurantee that @token is still valid, so be sure to handle any DropboxAuthErrors that can be raised
208
+ end
209
+
210
+ # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
211
+ # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
212
+ def parse_response(response, raw=false) # :nodoc:
213
+ if response.kind_of?(Net::HTTPServerError)
214
+ raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
215
+ elsif response.kind_of?(Net::HTTPUnauthorized)
216
+ raise DropboxAuthError.new(response, "User is not authenticated.")
217
+ elsif not response.kind_of?(Net::HTTPSuccess)
218
+ begin
219
+ d = JSON.parse(response.body)
220
+ rescue
221
+ raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
222
+ end
223
+ if d['user_error'] and d['error']
224
+ raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
225
+ elsif d['error']
226
+ raise DropboxError.new(d['error'], response)
227
+ else
228
+ raise DropboxError.new(response.body, response)
229
+ end
230
+ end
231
+
232
+ return response.body if raw
233
+
234
+ begin
235
+ return JSON.parse(response.body)
236
+ rescue JSON::ParserError
237
+ raise DropboxError.new("Unable to parse JSON response", response)
238
+ end
239
+
240
+ end
241
+
242
+
243
+ # Returns account info in a Hash object
244
+ #
245
+ # For a detailed description of what this call returns, visit:
246
+ # https://www.dropbox.com/developers/docs#account-info
247
+ def account_info()
248
+ response = @token.get build_url("/account/info")
249
+ parse_response(response)
250
+ end
251
+
252
+ # Uploads a file to a server. This uses the HTTP PUT upload method for simplicity
253
+ #
254
+ # Arguments:
255
+ # * to_path: The directory path to upload the file to. If the destination
256
+ # directory does not yet exist, it will be created.
257
+ # * file_obj: A file-like object to upload. If you would like, you can
258
+ # pass a string as file_obj.
259
+ # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
260
+ # If overwrite is False and a file already exists there, Dropbox
261
+ # will rename the upload to make sure it doesn't overwrite anything.
262
+ # You must check the returned metadata to know what this new name is.
263
+ # This field should only be True if your intent is to potentially
264
+ # clobber changes to a file that you don't know about.
265
+ # * parent_rev: The rev field from the 'parent' of this upload. [optional]
266
+ # If your intent is to update the file at the given path, you should
267
+ # pass the parent_rev parameter set to the rev value from the most recent
268
+ # metadata you have of the existing file at that path. If the server
269
+ # has a more recent version of the file at the specified path, it will
270
+ # automatically rename your uploaded file, spinning off a conflict.
271
+ # Using this parameter effectively causes the overwrite parameter to be ignored.
272
+ # The file will always be overwritten if you send the most-recent parent_rev,
273
+ # and it will never be overwritten you send a less-recent one.
274
+ # Returns:
275
+ # * a Hash containing the metadata of the newly uploaded file. The file may have a different name if it conflicted.
276
+ #
277
+ # Simple Example
278
+ # client = DropboxClient(session, "app_folder")
279
+ # #session is a DropboxSession I've already authorized
280
+ # client.put_file('/test_file_on_dropbox', open('/tmp/test_file'))
281
+ # This will upload the "/tmp/test_file" from my computer into the root of my App's app folder
282
+ # and call it "test_file_on_dropbox".
283
+ # The file will not overwrite any pre-existing file.
284
+ def put_file(to_path, file_obj, overwrite=false, parent_rev=nil)
285
+
286
+ path = "/files_put/#{@root}#{format_path(to_path)}"
287
+
288
+ params = {
289
+ 'overwrite' => overwrite.to_s
290
+ }
291
+
292
+ params['parent_rev'] = parent_rev unless parent_rev.nil?
293
+
294
+ response = @token.put(build_url(path, params, content_server=true),
295
+ file_obj,
296
+ "Content-Type" => "application/octet-stream")
297
+
298
+ parse_response(response)
299
+ end
300
+
301
+ # Download a file
302
+ #
303
+ # Args:
304
+ # * from_path: The path to the file to be downloaded
305
+ # * rev: A previous revision value of the file to be downloaded
306
+ #
307
+ # Returns:
308
+ # * The file contents.
309
+ def get_file(from_path, rev=nil)
310
+ params = {}
311
+ params['rev'] = rev.to_s if rev
312
+
313
+ path = "/files/#{@root}#{format_path(from_path)}"
314
+ response = @token.get(build_url(path, params, content_server=true))
315
+
316
+ parse_response(response, raw=true)
317
+ end
318
+
319
+ # Copy a file or folder to a new location.
320
+ #
321
+ # Arguments:
322
+ # * from_path: The path to the file or folder to be copied.
323
+ # * to_path: The destination path of the file or folder to be copied.
324
+ # This parameter should include the destination filename (e.g.
325
+ # from_path: '/test.txt', to_path: '/dir/test.txt'). If there's
326
+ # already a file at the to_path, this copy will be renamed to
327
+ # be unique.
328
+ #
329
+ # Returns:
330
+ # * A hash with the metadata of the new copy of the file or folder.
331
+ # For a detailed description of what this call returns, visit:
332
+ # https://www.dropbox.com/developers/docs#fileops-copy
333
+ def file_copy(from_path, to_path)
334
+ params = {
335
+ "root" => @root,
336
+ "from_path" => format_path(from_path, false),
337
+ "to_path" => format_path(to_path, false),
338
+ }
339
+ response = @token.get(build_url("/fileops/copy", params))
340
+ parse_response(response)
341
+ end
342
+
343
+ # Create a folder.
344
+ #
345
+ # Arguments:
346
+ # * path: The path of the new folder.
347
+ #
348
+ # Returns:
349
+ # * A hash with the metadata of the newly created folder.
350
+ # For a detailed description of what this call returns, visit:
351
+ # https://www.dropbox.com/developers/docs#fileops-create-folder
352
+ def file_create_folder(path)
353
+ params = {
354
+ "root" => @root,
355
+ "path" => format_path(from_path, false),
356
+ }
357
+ response = @token.get(build_url("/fileops/create_folder", params))
358
+
359
+ parse_response(response)
360
+ end
361
+
362
+ # Deletes a file
363
+ #
364
+ # Arguments:
365
+ # * path: The path of the file to delete
366
+ #
367
+ # Returns:
368
+ # * A Hash with the metadata of file just deleted.
369
+ # For a detailed description of what this call returns, visit:
370
+ # https://www.dropbox.com/developers/docs#fileops-delete
371
+ def file_delete(path)
372
+ params = {
373
+ "root" => @root,
374
+ "path" => format_path(from_path, false),
375
+ }
376
+ response = @token.get(build_url("/fileops/delete", params))
377
+ parse_response(response)
378
+ end
379
+
380
+ # Moves a file
381
+ #
382
+ # Arguments:
383
+ # * from_path: The path of the file to be moved
384
+ # * to_path: The destination path of the file or folder to be moved
385
+ # If the file or folder already exists, it will be renamed to be unique.
386
+ #
387
+ # Returns:
388
+ # * A Hash with the metadata of file or folder just moved.
389
+ # For a detailed description of what this call returns, visit:
390
+ # https://www.dropbox.com/developers/docs#fileops-delete
391
+ def file_move(from_path, to_path)
392
+ params = {
393
+ "root" => @root,
394
+ "from_path" => format_path(from_path, false),
395
+ "to_path" => format_path(to_path, false),
396
+ }
397
+ response = @token.post(build_url("/fileops/move", params))
398
+ parse_response(response)
399
+ end
400
+
401
+ # Retrives metadata for a file or folder
402
+ #
403
+ # Arguments:
404
+ # * path: The path to the file or folder.
405
+ # * list: Whether to list all contained files (only applies when
406
+ # path refers to a folder).
407
+ # * file_limit: The maximum number of file entries to return within
408
+ # a folder. If the number of files in the directory exceeds this
409
+ # limit, an exception is raised. The server will return at max
410
+ # 10,000 files within a folder.
411
+ # * hash: Every directory listing has a hash parameter attached that
412
+ # can then be passed back into this function later to save on
413
+ # bandwidth. Rather than returning an unchanged folder's contents, if
414
+ # the hash matches a DropboxNotModified exception is raised.
415
+ #
416
+ # Returns:
417
+ # * A Hash object with the metadata of the file or folder (and contained files if
418
+ # appropriate). For a detailed description of what this call returns, visit:
419
+ # https://www.dropbox.com/developers/docs#metadata
420
+ def metadata(path, file_limit=10000, list=true, hash=nil)
421
+ params = {
422
+ "file_limit" => file_limit.to_s,
423
+ "list" => list.to_s
424
+ }
425
+
426
+ params["hash"] = hash if hash
427
+
428
+ response = @token.get build_url("/metadata/#{@root}#{format_path(path)}", params=params)
429
+ if response.kind_of? Net::HTTPRedirection
430
+ raise DropboxNotModified.new("metadata not modified")
431
+ end
432
+ parse_response(response)
433
+ end
434
+
435
+ # Search directory for filenames matching query
436
+ #
437
+ # Arguments:
438
+ # * path: The directory to search within
439
+ # * query: The query to search on (3 character minimum)
440
+ # * file_limit: The maximum number of file entries to return/
441
+ # If the number of files exceeds this
442
+ # limit, an exception is raised. The server will return at max 10,000
443
+ # * include_deleted: Whether to include deleted files in search results
444
+ #
445
+ # Returns:
446
+ # * A Hash object with a list the metadata of the file or folders matching query
447
+ # inside path. For a detailed description of what this call returns, visit:
448
+ # https://www.dropbox.com/developers/docs#search
449
+ def search(path, query, file_limit=10000, include_deleted=false)
450
+
451
+ params = {
452
+ 'query' => query,
453
+ 'file_limit' => file_limit.to_s,
454
+ 'include_deleted' => include_deleted.to_s
455
+ }
456
+
457
+ response = @token.get(build_url("/search/#{@root}#{format_path(path)}", params))
458
+ parse_response(response)
459
+
460
+ end
461
+
462
+ # Retrive revisions of a file
463
+ #
464
+ # Arguments:
465
+ # * path: The file to fetch revisions for. Note that revisions
466
+ # are not available for folders.
467
+ # * rev_limit: The maximum number of file entries to return within
468
+ # a folder. The server will return at max 1,000 revisions.
469
+ #
470
+ # Returns:
471
+ # * A Hash object with a list of the metadata of the all the revisions of
472
+ # all matches files (up to rev_limit entries)
473
+ # For a detailed description of what this call returns, visit:
474
+ # https://www.dropbox.com/developers/docs#revisions
475
+ def revisions(path, rev_limit=1000)
476
+
477
+ params = {
478
+ 'rev_limit' => rev_limit.to_s
479
+ }
480
+
481
+ response = @token.get(build_url("/revisions/#{@root}#{format_path(path)}", params))
482
+ parse_response(response)
483
+
484
+ end
485
+
486
+ # Restore a file to a previous revision.
487
+ #
488
+ # Arguments:
489
+ # * path: The file to restore. Note that folders can't be restored.
490
+ # * rev: A previous rev value of the file to be restored to.
491
+ #
492
+ # Returns:
493
+ # * A Hash object with a list the metadata of the file or folders restored
494
+ # For a detailed description of what this call returns, visit:
495
+ # https://www.dropbox.com/developers/docs#search
496
+ def restore(path, rev)
497
+ params = {
498
+ 'rev' => rev.to_s
499
+ }
500
+
501
+ response = @token.get(build_url("/restore/#{@root}#{format_path(path)}", params))
502
+ parse_response(response)
503
+ end
504
+
505
+ # Returns a direct link to a media file
506
+ # All of Dropbox's API methods require OAuth, which may cause problems in
507
+ # situations where an application expects to be able to hit a URL multiple times
508
+ # (for example, a media player seeking around a video file). This method
509
+ # creates a time-limited URL that can be accessed without any authentication.
510
+ #
511
+ # Arguments:
512
+ # * path: The file to stream.
513
+ #
514
+ # Returns:
515
+ # * A Hash object that looks like the following:
516
+ # {'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
517
+ def media(path)
518
+ response = @token.get(build_url("/media/#{@root}#{format_path(path)}"))
519
+ parse_response(response)
520
+ end
521
+
522
+ # Get a URL to share a media file
523
+ # Shareable links created on Dropbox are time-limited, but don't require any
524
+ # authentication, so they can be given out freely. The time limit should allow
525
+ # at least a day of shareability, though users have the ability to disable
526
+ # a link from their account if they like.
527
+ #
528
+ # Arguments:
529
+ # * path: The file to share.
530
+ #
531
+ # Returns:
532
+ # * A Hash object that looks like the following example:
533
+ # {'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
534
+ # For a detailed description of what this call returns, visit:
535
+ # https://www.dropbox.com/developers/docs#share
536
+ def shares(path)
537
+ response = @token.get(build_url("/shares/#{@root}#{format_path(path)}"))
538
+ parse_response(response)
539
+ end
540
+
541
+ # Download a thumbnail for an image.
542
+ #
543
+ # Arguments:
544
+ # * from_path: The path to the file to be thumbnailed.
545
+ # * size: A string describing the desired thumbnail size. At this time,
546
+ # 'small', 'medium', and 'large' are officially supported sizes
547
+ # (32x32, 64x64, and 128x128 respectively), though others may
548
+ # be available. Check https://www.dropbox.com/developers/docs#thumbnails
549
+ # for more details. [defaults to large]
550
+ # Returns:
551
+ # * The thumbnail data
552
+ def thumbnail(from_path, size='large')
553
+ from_path = format_path(from_path, false)
554
+
555
+ raise DropboxError.new("size must be small medium or large. (not '#{size})") unless ['small','medium','large'].include?(size)
556
+
557
+ params = {
558
+ "size" => size
559
+ }
560
+
561
+ url = build_url("/thumbnails/#{@root}#{from_path}", params, content_server=true)
562
+
563
+ response = @token.get(url)
564
+ parse_response(response, raw=true)
565
+ end
566
+
567
+ def build_url(url, params=nil, content_server=false) # :nodoc:
568
+ port = 443
569
+ host = content_server ? DROPBOX_API_CONTENT_SERVER : DROPBOX_API_SERVER
570
+ versioned_url = "/#{API_VERSION}#{url}"
571
+
572
+ target = URI::Generic.new("https", nil, host, port, nil, versioned_url, nil, nil, nil)
573
+
574
+ #add a locale param if we have one
575
+ #initialize a params object is we don't have one
576
+ if @locale
577
+ (params ||= {})['locale']=@locale
578
+ end
579
+
580
+ if params
581
+ target.query = params.collect {|k,v|
582
+ URI.escape(k) + "=" + URI.escape(v)
583
+ }.join("&")
584
+ end
585
+
586
+ target.to_s
587
+ end
588
+ end
589
+
590
+
591
+ #From the oauth spec plus "/". Slash should not be ecsaped
592
+ RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/
593
+
594
+ def format_path(path, escape=true) # :nodoc:
595
+ path = path.gsub(/\/+/,"/")
596
+ # replace multiple slashes with a single one
597
+
598
+ path = path.gsub(/^\/?/,"/")
599
+ # ensure the path starts with a slash
600
+
601
+ path.gsub(/\/?$/,"")
602
+ # ensure the path doesn't end with a slash
603
+
604
+ return URI.escape(path, RESERVED_CHARACTERS) if escape
605
+ path
606
+ end
607
+
@@ -0,0 +1,184 @@
1
+ # -------------------------------------------------------------------
2
+ # An example webapp that lets you browse and upload files to Dropbox.
3
+ # Demonstrates:
4
+ # - The webapp OAuth process.
5
+ # - The metadata() and put_file() calls.
6
+ #
7
+ # To run:
8
+ # 1. Install Sinatra $ gem install sinatra
9
+ # 2. Launch server $ ruby web_file_browser.rb
10
+ # 3. Browse to http://localhost:4567/
11
+ # -------------------------------------------------------------------
12
+
13
+ require 'rubygems'
14
+ require 'sinatra'
15
+ require 'pp'
16
+ require './lib/dropbox_sdk'
17
+
18
+ # Get your app's key and secret from https://www.dropbox.com/developers/
19
+ APP_KEY = ''
20
+ APP_SECRET = ''
21
+ ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
22
+ #The default is :app_folder, but your application might be
23
+ #set to have full :dropbox access. Check your app at
24
+ #https://www.dropbox.com/developers/apps
25
+
26
+ # -------------------------------------------------------------------
27
+ # OAuth stuff
28
+
29
+ get '/oauth-start' do
30
+ # OAuth Step 1: Get a request token from Dropbox.
31
+ db_session = DropboxSession.new(APP_KEY, APP_SECRET)
32
+ begin
33
+ db_session.get_request_token
34
+ rescue DropboxError => e
35
+ return html_page "Exception in OAuth step 1", "<p>#{h e}</p>"
36
+ end
37
+
38
+ session[:request_db_session] = db_session.serialize
39
+
40
+ # OAuth Step 2: Send the user to the Dropbox website so they can authorize
41
+ # our app. After the user authorizes our app, Dropbox will redirect them
42
+ # to our '/oauth-callback' endpoint.
43
+ auth_url = db_session.get_authorize_url url('/oauth-callback')
44
+ redirect auth_url
45
+ end
46
+
47
+ get '/oauth-callback' do
48
+ # Finish OAuth Step 2
49
+ ser = session[:request_db_session]
50
+ unless ser
51
+ return html_page "Error in OAuth step 2", "<p>Couldn't find OAuth state in session.</p>"
52
+ end
53
+ db_session = DropboxSession.deserialize(ser)
54
+
55
+ # OAuth Step 3: Get an access token from Dropbox.
56
+ begin
57
+ db_session.get_access_token
58
+ rescue DropboxError => e
59
+ return html_page "Exception in OAuth step 3", "<p>#{h e}</p>"
60
+ end
61
+ session.delete(:request_db_session)
62
+ session[:authorized_db_session] = db_session.serialize
63
+ redirect url('/')
64
+ # In this simple example, we store the authorized DropboxSession in the web
65
+ # session hash. A "real" webapp might store it somewhere more persistent.
66
+ end
67
+
68
+ # If we already have an authorized DropboxSession, returns a DropboxClient.
69
+ def get_db_client
70
+ if session[:authorized_db_session]
71
+ db_session = DropboxSession.deserialize(session[:authorized_db_session])
72
+ begin
73
+ return DropboxClient.new(db_session, ACCESS_TYPE)
74
+ rescue DropboxAuthError => e
75
+ # The stored session didn't work. Fall through and start OAuth.
76
+ session[:authorized_db_session].delete
77
+ end
78
+ end
79
+ end
80
+
81
+ # -------------------------------------------------------------------
82
+ # File/folder display stuff
83
+
84
+ get '/' do
85
+ # Get the DropboxClient object. Redirect to OAuth flow if necessary.
86
+ db_client = get_db_client
87
+ unless db_client
88
+ redirect url("/oauth-start")
89
+ end
90
+
91
+ # Call DropboxClient.metadata
92
+ path = params[:path] || '/'
93
+ begin
94
+ entry = db_client.metadata(path)
95
+ rescue DropboxAuthError => e
96
+ session.delete(:authorized_db_session) # An auth error means the db_session is probably bad
97
+ return html_page "Dropbox auth error", "<p>#{h e}</p>"
98
+ rescue DropboxError => e
99
+ if e.http_response.code == '404'
100
+ return html_page "Path not found: #{h path}", ""
101
+ else
102
+ return html_page "Dropbox API error", "<pre>#{h e.http_response}</pre>"
103
+ end
104
+ end
105
+
106
+ if entry['is_dir']
107
+ render_folder(db_client, entry)
108
+ else
109
+ render_file(db_client, entry)
110
+ end
111
+ end
112
+
113
+ def render_folder(db_client, entry)
114
+ # Provide an upload form (so the user can add files to this folder)
115
+ out = "<form action='/upload' method='post' enctype='multipart/form-data'>"
116
+ out += "<label for='file'>Upload file:</label> <input name='file' type='file'/>"
117
+ out += "<input type='submit' value='Upload'/>"
118
+ out += "<input name='folder' type='hidden' value='#{h entry['path']}'/>"
119
+ out += "</form>" # TODO: Add a token to counter CSRF attacks.
120
+ # List of folder contents
121
+ entry['contents'].each do |child|
122
+ cp = child['path'] # child path
123
+ cn = File.basename(cp) # child name
124
+ if (child['is_dir']) then cn += '/' end
125
+ out += "<div><a style='text-decoration: none' href='/?path=#{h cp}'>#{h cn}</a></div>"
126
+ end
127
+
128
+ html_page "Folder: #{entry['path']}", out
129
+ end
130
+
131
+ def render_file(db_client, entry)
132
+ # Just dump out metadata hash
133
+ html_page "File: #{entry['path']}", "<pre>#{h entry.pretty_inspect}</pre>"
134
+ end
135
+
136
+ # -------------------------------------------------------------------
137
+ # File upload handler
138
+
139
+ post '/upload' do
140
+ # Check POST parameter.
141
+ file = params[:file]
142
+ unless file && (temp_file = file[:tempfile]) && (name = file[:filename])
143
+ return html_page "Upload error", "<p>No file selected.</p>"
144
+ end
145
+
146
+ # Get the DropboxClient object.
147
+ db_client = get_db_client
148
+ unless db_client
149
+ return html_page "Upload error", "<p>Not linked with a Dropbox account.</p>"
150
+ end
151
+
152
+ # Call DropboxClient.put_file
153
+ begin
154
+ entry = db_client.put_file("#{params[:folder]}/#{name}", temp_file.read)
155
+ rescue DropboxAuthError => e
156
+ session.delete(:authorized_db_session) # An auth error means the db_session is probably bad
157
+ return html_page "Dropbox auth error", "<p>#{h e}</p>"
158
+ rescue DropboxError => e
159
+ return html_page "Dropbox API error", "<p>#{h e}</p>"
160
+ end
161
+
162
+ html_page "Upload complete", "<pre>#{h entry.pretty_inspect}</pre>"
163
+ end
164
+
165
+ # -------------------------------------------------------------------
166
+
167
+ def html_page(title, body)
168
+ "<html>" +
169
+ "<head><title>#{h title}</title></head>" +
170
+ "<body><h1>#{h title}</h1>#{body}</body>" +
171
+ "</html>"
172
+ end
173
+
174
+ enable :sessions
175
+
176
+ helpers do
177
+ include Rack::Utils
178
+ alias_method :h, :escape_html
179
+ end
180
+
181
+ if APP_KEY == '' or APP_SECRET == ''
182
+ puts "You must set APP_KEY and APP_SECRET at the top of \"#{__FILE__}\"!"
183
+ exit 1
184
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dropbox-sdk
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31098137
5
+ prerelease: 4
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - beta
10
+ version: 1.0.beta
11
+ platform: ruby
12
+ authors:
13
+ - Dropbox, Inc.
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: oauth
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: json
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ description: " A library that provides a plain function-call interface to the\n Dropbox API web endpoints.\n"
49
+ email:
50
+ - support-api@dropbox.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - CHANGELOG
59
+ - LICENSE
60
+ - README
61
+ - cli_example.rb
62
+ - dropbox_controller.rb
63
+ - web_file_browser.rb
64
+ - lib/dropbox_sdk.rb
65
+ homepage: http://www.dropbox.com/developers/
66
+ licenses:
67
+ - MIT
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">"
86
+ - !ruby/object:Gem::Version
87
+ hash: 25
88
+ segments:
89
+ - 1
90
+ - 3
91
+ - 1
92
+ version: 1.3.1
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.6
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Dropbox REST API Client.
100
+ test_files: []
101
+