dropbox-sdk-sv 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ # ---------------------------------------------------------------------------------------
2
+ # A Rails 3 controller that:
3
+ # - Runs the through Dropbox's OAuth 2 flow, yielding a Dropbox API access token.
4
+ # - Makes a Dropbox API call to upload a file.
5
+ #
6
+ # To set up:
7
+ # 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
8
+ # 2. Add http://localhost:3000/dropbox/auth_finish as a Redirect URI for your Dropbox app.
9
+ # 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
10
+ #
11
+ # To run:
12
+ # 1. You need a Rails 3 project (to create one, run: rails new <folder-name>)
13
+ # 2. Copy dropbox_sdk.rb into <folder-name>/vendor
14
+ # 3. Copy this file into <folder-name>/app/controllers/
15
+ # 4. Add the following lines to <folder-name>/config/routes.rb
16
+ # get "dropbox/main"
17
+ # post "dropbox/upload"
18
+ # get "dropbox/auth_start"
19
+ # get "dropbox/auth_finish"
20
+ # 5. Run: rails server
21
+ # 6. Point your browser at: https://localhost:3000/dropbox/main
22
+
23
+ require 'dropbox_sdk'
24
+ require 'securerandom'
25
+
26
+ APP_KEY = ""
27
+ APP_SECRET = ""
28
+
29
+ class DropboxController < ApplicationController
30
+
31
+ def main
32
+ client = get_dropbox_client
33
+ unless client
34
+ redirect_to(:action => 'auth_start') and return
35
+ end
36
+
37
+ account_info = client.account_info
38
+
39
+ # Show a file upload page
40
+ render :inline =>
41
+ "#{account_info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag 'Upload' %><% end %>"
42
+ end
43
+
44
+ def upload
45
+ client = get_dropbox_client
46
+ unless client
47
+ redirect_to(:action => 'auth_start') and return
48
+ end
49
+
50
+ begin
51
+ # Upload the POST'd file to Dropbox, keeping the same name
52
+ resp = client.put_file(params[:file].original_filename, params[:file].read)
53
+ render :text => "Upload successful. File now at #{resp['path']}"
54
+ rescue DropboxAuthError => e
55
+ session.delete(:access_token) # An auth error means the access token is probably bad
56
+ logger.info "Dropbox auth error: #{e}"
57
+ render :text => "Dropbox auth error"
58
+ rescue DropboxError => e
59
+ logger.info "Dropbox API error: #{e}"
60
+ render :text => "Dropbox API error"
61
+ end
62
+ end
63
+
64
+ def get_dropbox_client
65
+ if session[:access_token]
66
+ begin
67
+ access_token = session[:access_token]
68
+ DropboxClient.new(access_token)
69
+ rescue
70
+ # Maybe something's wrong with the access token?
71
+ session.delete(:access_token)
72
+ raise
73
+ end
74
+ end
75
+ end
76
+
77
+ def get_web_auth()
78
+ redirect_uri = url_for(:action => 'auth_finish')
79
+ DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, redirect_uri, session, :dropbox_auth_csrf_token)
80
+ end
81
+
82
+ def auth_start
83
+ authorize_url = get_web_auth().start()
84
+
85
+ # Send the user to the Dropbox website so they can authorize our app. After the user
86
+ # authorizes our app, Dropbox will redirect them here with a 'code' parameter.
87
+ redirect_to authorize_url
88
+ end
89
+
90
+ def auth_finish
91
+ begin
92
+ access_token, user_id, url_state = get_web_auth.finish(params)
93
+ session[:access_token] = access_token
94
+ redirect_to :action => 'main'
95
+ rescue DropboxOAuth2Flow::BadRequestError => e
96
+ render :text => "Error in OAuth 2 flow: Bad request: #{e}"
97
+ rescue DropboxOAuth2Flow::BadStateError => e
98
+ logger.info("Error in OAuth 2 flow: No CSRF token in session: #{e}")
99
+ redirect_to(:action => 'auth_start')
100
+ rescue DropboxOAuth2Flow::CsrfError => e
101
+ logger.info("Error in OAuth 2 flow: CSRF mismatch: #{e}")
102
+ render :text => "CSRF error"
103
+ rescue DropboxOAuth2Flow::NotApprovedError => e
104
+ render :text => "Not approved? Why not, bro?"
105
+ rescue DropboxOAuth2Flow::ProviderError => e
106
+ logger.info "Error in OAuth 2 flow: Error redirect from Dropbox: #{e}"
107
+ render :text => "Strange error."
108
+ rescue DropboxError => e
109
+ logger.info "Error getting OAuth 2 access token: #{e}"
110
+ render :text => "Error communicating with Dropbox servers."
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
2
+ require 'pp'
3
+
4
+ # You must use your Dropbox App key and secret to use the API.
5
+ # Find this at https://www.dropbox.com/developers
6
+ APP_KEY = ''
7
+ APP_SECRET = ''
8
+
9
+ def main
10
+ if APP_KEY == '' or APP_SECRET == ''
11
+ warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
12
+ exit
13
+ end
14
+
15
+ prog_name = __FILE__
16
+ args = ARGV
17
+ if args.size != 2
18
+ warn "Usage: #{prog_name} <oauth1-access-token-key> <oauth1-access-token-secret>"
19
+ exit 1
20
+ end
21
+
22
+ access_token_key = args[0]
23
+ access_token_secret = args[1]
24
+
25
+ sess = DropboxSession.new(APP_KEY, APP_SECRET)
26
+ sess.set_access_token(access_token_key, access_token_secret)
27
+ c = DropboxClient.new(sess)
28
+
29
+ print "Creating OAuth 2 access token...\n"
30
+ oauth2_access_token = c.create_oauth2_access_token
31
+
32
+ print "Using OAuth 2 access token to get account info...\n"
33
+ c2 = DropboxClient.new(oauth2_access_token)
34
+ pp c2.account_info
35
+
36
+ print "Disabling OAuth 1 access token...\n"
37
+ c.disable_access_token
38
+ end
39
+
40
+ main()
@@ -0,0 +1,322 @@
1
+ # An example use of the /delta API call. Maintains a local cache of
2
+ # the App Folder's contents. Use the 'update' sub-command to update
3
+ # the local cache. Use the 'find' sub-command to search the local
4
+ # cache.
5
+ #
6
+ # Example usage:
7
+ #
8
+ # 1. Link to your Dropbox account
9
+ # > ruby search_cache.rb link
10
+ #
11
+ # 2. Go to Dropbox and make changes to the contents.
12
+ #
13
+ # 3. Update the local cache to match what's on Dropbox.
14
+ # > ruby search_cache.rb update
15
+ #
16
+ # 4. Search the local cache.
17
+ # > ruby search_cache.rb find 'txt'
18
+
19
+ # Repeat steps 2-4 any number of times.
20
+
21
+
22
+
23
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
24
+ require 'json'
25
+
26
+ # You must use your Dropbox App key and secret to use the API.
27
+ # Find this at https://www.dropbox.com/developers
28
+ APP_KEY = ''
29
+ APP_SECRET = ''
30
+
31
+ STATE_FILE = 'search_cache.json'
32
+
33
+ def main()
34
+ if APP_KEY == '' or APP_SECRET == ''
35
+ warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
36
+ exit
37
+ end
38
+ prog_name = __FILE__
39
+ args = ARGV
40
+ if args.size == 0
41
+ warn("Usage:\n")
42
+ warn(" #{prog_name} link Link to a user's account.")
43
+ warn(" #{prog_name} update Update cache to the latest on Dropbox.")
44
+ warn(" #{prog_name} update <num> Update cache, limit to <num> pages of /delta.")
45
+ warn(" #{prog_name} find <term> Search cache for <term>.")
46
+ warn(" #{prog_name} find Display entire cache contents")
47
+ warn(" #{prog_name} reset Delete the cache.")
48
+ exit
49
+ end
50
+
51
+ command = args[0]
52
+ if command == 'link'
53
+ command_link(args)
54
+ elsif command == 'update'
55
+ command_update(args)
56
+ elsif command == 'find'
57
+ command_find(args)
58
+ elsif command == 'reset'
59
+ command_reset(args)
60
+ else
61
+ warn "ERROR: Unknown command: #{command}"
62
+ warn "Run with no arguments for help."
63
+ exit(1)
64
+ end
65
+ end
66
+
67
+
68
+ def command_link(args)
69
+ if args.size != 1
70
+ warn "ERROR: \"link\" doesn't take any arguments"
71
+ exit
72
+ end
73
+
74
+ web_auth = DropboxOAuth2FlowNoRedirect.new(APP_KEY, APP_SECRET)
75
+ authorize_url = web_auth.start()
76
+ puts "1. Go to: #{authorize_url}"
77
+ puts "2. Click \"Allow\" (you might have to log in first)."
78
+ puts "3. Copy the authorization code."
79
+
80
+ print "Enter the authorization code here: "
81
+ STDOUT.flush
82
+ auth_code = STDIN.gets.strip
83
+
84
+ access_token, user_id = web_auth.finish(auth_code)
85
+ puts "Link successful."
86
+
87
+ save_state({
88
+ 'access_token' => access_token,
89
+ 'tree' => {}
90
+ })
91
+ end
92
+
93
+
94
+ def command_update(args)
95
+ if args.size == 1
96
+ page_limit = nil
97
+ elsif args.size == 2
98
+ page_limit = Integer(args[1])
99
+ else
100
+ warn "ERROR: \"update\" takes either zero or one argument."
101
+ exit
102
+ end
103
+
104
+ # Load state
105
+ state = load_state()
106
+ access_token = state['access_token']
107
+ cursor = state['cursor']
108
+ tree = state['tree']
109
+
110
+ # Connect to Dropbox
111
+ c = DropboxClient.new(access_token)
112
+
113
+ page = 0
114
+ changed = false
115
+ while (page_limit == nil) or (page < page_limit)
116
+ # Get /delta results from Dropbox
117
+ result = c.delta(cursor)
118
+ page += 1
119
+ if result['reset'] == true
120
+ puts 'reset'
121
+ changed = true
122
+ tree = {}
123
+ end
124
+ cursor = result['cursor']
125
+ # Apply the entries one by one to our cached tree.
126
+ for delta_entry in result['entries']
127
+ changed = true
128
+ apply_delta(tree, delta_entry)
129
+ end
130
+ cursor = result['cursor']
131
+ if not result['has_more']
132
+ break
133
+ end
134
+ end
135
+
136
+ # Save state
137
+ if changed
138
+ state['cursor'] = cursor
139
+ state['tree'] = tree
140
+ save_state(state)
141
+ else
142
+ puts "No updates."
143
+ end
144
+
145
+ end
146
+
147
+ # We track folder state as a tree of Node objects.
148
+ class Node
149
+ attr_accessor :path, :content
150
+ def initialize(path, content)
151
+ # The "original" page (i.e. not the lower-case path)
152
+ @path = path
153
+ # For files, content is a pair (size, modified)
154
+ # For folders, content is a hash of children Nodes, keyed by lower-case file names.
155
+ @content = content
156
+ end
157
+ def folder?()
158
+ @content.is_a? Hash
159
+ end
160
+ def to_json()
161
+ [@path, Node.to_json_content(@content)]
162
+ end
163
+ def self.from_json(jnode)
164
+ path, jcontent = jnode
165
+ Node.new(path, Node.from_json_content(jcontent))
166
+ end
167
+ def self.to_json_content(content)
168
+ if content.is_a? Hash
169
+ map_hash_values(content) { |child| child.to_json }
170
+ else
171
+ content
172
+ end
173
+ end
174
+ def self.from_json_content(jcontent)
175
+ if jcontent.is_a? Hash
176
+ map_hash_values(jcontent) { |jchild| Node.from_json jchild }
177
+ else
178
+ jcontent
179
+ end
180
+ end
181
+ end
182
+
183
+ # Run a mapping function over every value in a Hash, returning a new Hash.
184
+ def map_hash_values(h)
185
+ new = {}
186
+ h.each { |k,v| new[k] = yield v }
187
+ new
188
+ end
189
+
190
+
191
+ def apply_delta(root, e)
192
+ path, metadata = e
193
+ branch, leaf = split_path(path)
194
+
195
+ if metadata != nil
196
+ puts "+ #{path}"
197
+ # Traverse down the tree until we find the parent folder of the entry
198
+ # we want to add. Create any missing folders along the way.
199
+ children = root
200
+ branch.each do |part|
201
+ node = get_or_create_child(children, part)
202
+ # If there's no folder here, make an empty one.
203
+ if not node.folder?
204
+ node.content = {}
205
+ end
206
+ children = node.content
207
+ end
208
+
209
+ # Create the file/folder.
210
+ node = get_or_create_child(children, leaf)
211
+ node.path = metadata['path'] # Save the un-lower-cased path.
212
+ if metadata['is_dir']
213
+ # Only create a folder if there isn't one there already.
214
+ node.content = {} if not node.folder?
215
+ else
216
+ node.content = metadata['size'], metadata['modified']
217
+ end
218
+ else
219
+ puts "- #{path}"
220
+ # Traverse down the tree until we find the parent of the entry we
221
+ # want to delete.
222
+ children = root
223
+ missing_parent = false
224
+ branch.each do |part|
225
+ node = children[part]
226
+ # If one of the parent folders is missing, then we're done.
227
+ if node == nil or not node.folder?
228
+ missing_parent = true
229
+ break
230
+ end
231
+ children = node.content
232
+ end
233
+ # If we made it all the way, delete the file/folder.
234
+ if not missing_parent
235
+ children.delete(leaf)
236
+ end
237
+ end
238
+ end
239
+
240
+ def get_or_create_child(children, name)
241
+ child = children[name]
242
+ if child == nil
243
+ children[name] = child = Node.new(nil, nil)
244
+ end
245
+ child
246
+ end
247
+
248
+ def split_path(path)
249
+ bad, *parts = path.split '/'
250
+ [parts, parts.pop]
251
+ end
252
+
253
+
254
+ def command_find(args)
255
+ if args.size == 1
256
+ term = ''
257
+ elsif args.size == 2
258
+ term = args[1]
259
+ else
260
+ warn("ERROR: \"find\" takes either zero or one arguments.")
261
+ exit
262
+ end
263
+
264
+ state = load_state()
265
+ results = []
266
+ search_tree(results, state['tree'], term)
267
+ for r in results
268
+ puts("#{r}")
269
+ end
270
+ puts("[Matches: #{results.size}]")
271
+ end
272
+
273
+
274
+ def command_reset(args)
275
+ if args.size != 1
276
+ warn("ERROR: \"reset\" takes no arguments.")
277
+ exit
278
+ end
279
+
280
+ # Delete cursor, empty tree.
281
+ state = load_state()
282
+ if state.has_key?('cursor')
283
+ state.delete('cursor')
284
+ end
285
+ state['tree'] = {}
286
+ save_state(state)
287
+ end
288
+
289
+
290
+ # Recursively search 'tree' for files that contain the string in 'term'.
291
+ # Print out any matches.
292
+ def search_tree(results, tree, term)
293
+ tree.each do |name_lc, node|
294
+ path = node.path
295
+ if (path != nil) and path.include?(term)
296
+ if node.folder?
297
+ results.push("#{path}")
298
+ else
299
+ size, modified = node.content
300
+ results.push("#{path} (#{size}, #{modified})")
301
+ end
302
+ end
303
+ if node.folder?
304
+ search_tree(results, node.content, term)
305
+ end
306
+ end
307
+ end
308
+
309
+ def save_state(state)
310
+ state['tree'] = Node.to_json_content(state['tree'])
311
+ File.open(STATE_FILE,"w") do |f|
312
+ f.write(JSON.pretty_generate(state, :max_nesting => false))
313
+ end
314
+ end
315
+
316
+ def load_state()
317
+ state = JSON.parse(File.read(STATE_FILE), :max_nesting => false)
318
+ state['tree'] = Node.from_json_content(state['tree'])
319
+ state
320
+ end
321
+
322
+ main()
@@ -0,0 +1,193 @@
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 set up:
8
+ # 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
9
+ # 2. Add http://localhost:5000/dropbox-auth-finish as a Redirect URI for your Dropbox app.
10
+ # 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
11
+ #
12
+ # To run:
13
+ # 1. Install Sinatra $ gem install sinatra
14
+ # 2. Launch server $ ruby web_file_browser.rb
15
+ # 3. Browse to http://localhost:5000/
16
+ # -------------------------------------------------------------------
17
+
18
+ require 'rubygems'
19
+ require 'sinatra'
20
+ require 'pp'
21
+ require 'securerandom'
22
+ require File.expand_path('../../lib/dropbox_sdk', __FILE__)
23
+
24
+ # Get your app's key and secret from https://www.dropbox.com/developers/
25
+ APP_KEY = ''
26
+ APP_SECRET = ''
27
+
28
+ # -------------------------------------------------------------------
29
+ # OAuth stuff
30
+
31
+ def get_web_auth()
32
+ return DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, url('/dropbox-auth-finish'),
33
+ session, :dropbox_auth_csrf_token)
34
+ end
35
+
36
+ get '/dropbox-auth-start' do
37
+ authorize_url = get_web_auth().start()
38
+
39
+ # Send the user to the Dropbox website so they can authorize our app. After the user
40
+ # authorizes our app, Dropbox will redirect them to our '/dropbox-auth-finish' endpoint.
41
+ redirect authorize_url
42
+ end
43
+
44
+ get '/dropbox-auth-finish' do
45
+ begin
46
+ access_token, user_id, url_state = get_web_auth.finish(params)
47
+ rescue DropboxOAuth2Flow::BadRequestError => e
48
+ return html_page "Error in OAuth 2 flow", "<p>Bad request to /dropbox-auth-finish: #{e}</p>"
49
+ rescue DropboxOAuth2Flow::BadStateError => e
50
+ return html_page "Error in OAuth 2 flow", "<p>Auth session expired: #{e}</p>"
51
+ rescue DropboxOAuth2Flow::CsrfError => e
52
+ logger.info("/dropbox-auth-finish: CSRF mismatch: #{e}")
53
+ return html_page "Error in OAuth 2 flow", "<p>CSRF mismatch</p>"
54
+ rescue DropboxOAuth2Flow::NotApprovedError => e
55
+ return html_page "Not Approved?", "<p>Why not, bro?</p>"
56
+ rescue DropboxOAuth2Flow::ProviderError => e
57
+ return html_page "Error in OAuth 2 flow", "Error redirect from Dropbox: #{e}"
58
+ rescue DropboxError => e
59
+ logger.info "Error getting OAuth 2 access token: #{e}"
60
+ return html_page "Error in OAuth 2 flow", "<p>Error getting access token</p>"
61
+ end
62
+
63
+ # In this simple example, we store the authorized DropboxSession in the session.
64
+ # A real webapp might store it somewhere more persistent.
65
+ session[:access_token] = access_token
66
+ redirect url('/')
67
+ end
68
+
69
+ get '/dropbox-unlink' do
70
+ session.delete(:access_token)
71
+ nil
72
+ end
73
+
74
+ # If we already have an authorized DropboxSession, returns a DropboxClient.
75
+ def get_dropbox_client
76
+ if session[:access_token]
77
+ return DropboxClient.new(session[:access_token])
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
+ client = get_dropbox_client
87
+ unless client
88
+ redirect url("/dropbox-auth-start")
89
+ end
90
+
91
+ # Call DropboxClient.metadata
92
+ path = params[:path] || '/'
93
+ begin
94
+ entry = client.metadata(path)
95
+ rescue DropboxAuthError => e
96
+ session.delete(:access_token) # An auth error means the access token is probably bad
97
+ logger.info "Dropbox auth error: #{e}"
98
+ return html_page "Dropbox auth error"
99
+ rescue DropboxError => e
100
+ if e.http_response.code == '404'
101
+ return html_page "Path not found: #{h path}"
102
+ else
103
+ logger.info "Dropbox API error: #{e}"
104
+ return html_page "Dropbox API error"
105
+ end
106
+ end
107
+
108
+ if entry['is_dir']
109
+ render_folder(client, entry)
110
+ else
111
+ render_file(client, entry)
112
+ end
113
+ end
114
+
115
+ def render_folder(client, entry)
116
+ # Provide an upload form (so the user can add files to this folder)
117
+ out = "<form action='/upload' method='post' enctype='multipart/form-data'>"
118
+ out += "<label for='file'>Upload file:</label> <input name='file' type='file'/>"
119
+ out += "<input type='submit' value='Upload'/>"
120
+ out += "<input name='folder' type='hidden' value='#{h entry['path']}'/>"
121
+ out += "</form>" # TODO: Add a token to counter CSRF attacks.
122
+ # List of folder contents
123
+ entry['contents'].each do |child|
124
+ cp = child['path'] # child path
125
+ cn = File.basename(cp) # child name
126
+ if (child['is_dir']) then cn += '/' end
127
+ out += "<div><a style='text-decoration: none' href='/?path=#{h cp}'>#{h cn}</a></div>"
128
+ end
129
+
130
+ html_page "Folder: #{entry['path']}", out
131
+ end
132
+
133
+ def render_file(client, entry)
134
+ # Just dump out metadata hash
135
+ html_page "File: #{entry['path']}", "<pre>#{h entry.pretty_inspect}</pre>"
136
+ end
137
+
138
+ # -------------------------------------------------------------------
139
+ # File upload handler
140
+
141
+ post '/upload' do
142
+ # Check POST parameter.
143
+ file = params[:file]
144
+ unless file && (temp_file = file[:tempfile]) && (name = file[:filename])
145
+ return html_page "Upload error", "<p>No file selected.</p>"
146
+ end
147
+
148
+ # Get the DropboxClient object.
149
+ client = get_dropbox_client
150
+ unless client
151
+ return html_page "Upload error", "<p>Not linked with a Dropbox account.</p>"
152
+ end
153
+
154
+ # Call DropboxClient.put_file
155
+ begin
156
+ entry = client.put_file("#{params[:folder]}/#{name}", temp_file.read)
157
+ rescue DropboxAuthError => e
158
+ session.delete(:access_token) # An auth error means the access token is probably bad
159
+ logger.info "Dropbox auth error: #{e}"
160
+ return html_page "Dropbox auth error"
161
+ rescue DropboxError => e
162
+ logger.info "Dropbox API error: #{e}"
163
+ return html_page "Dropbox API error"
164
+ end
165
+
166
+ html_page "Upload complete", "<pre>#{h entry.pretty_inspect}</pre>"
167
+ end
168
+
169
+ # -------------------------------------------------------------------
170
+
171
+ def html_page(title, body='')
172
+ "<html>" +
173
+ "<head><title>#{h title}</title></head>" +
174
+ "<body><h1>#{h title}</h1>#{body}</body>" +
175
+ "</html>"
176
+ end
177
+
178
+ # Rack will issue a warning if no session secret key is set. A real web app would not have
179
+ # a hard-coded secret in the code but would load it from a config file.
180
+ use Rack::Session::Cookie, :secret => 'dummy_secret'
181
+
182
+ set :port, 5000
183
+ enable :sessions
184
+
185
+ helpers do
186
+ include Rack::Utils
187
+ alias_method :h, :escape_html
188
+ end
189
+
190
+ if APP_KEY == '' or APP_SECRET == ''
191
+ puts "You must set APP_KEY and APP_SECRET at the top of \"#{__FILE__}\"!"
192
+ exit 1
193
+ end