dbox 0.7.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,332 +0,0 @@
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 './lib/dropbox_sdk'
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
- ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
31
- #The default is :app_folder, but your application might be
32
- #set to have full :dropbox access. Check your app at
33
- #https://www.dropbox.com/developers/apps
34
-
35
-
36
- STATE_FILE = 'search_cache.json'
37
-
38
- def main()
39
- if APP_KEY == '' or APP_SECRET == ''
40
- warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
41
- exit
42
- end
43
- prog_name = __FILE__
44
- args = ARGV
45
- if args.size == 0
46
- warn("Usage:\n")
47
- warn(" #{prog_name} link Link to a user's account.")
48
- warn(" #{prog_name} update Update cache to the latest on Dropbox.")
49
- warn(" #{prog_name} update <num> Update cache, limit to <num> pages of /delta.")
50
- warn(" #{prog_name} find <term> Search cache for <term>.")
51
- warn(" #{prog_name} find Display entire cache contents")
52
- warn(" #{prog_name} reset Delete the cache.")
53
- exit
54
- end
55
-
56
- command = args[0]
57
- if command == 'link'
58
- command_link(args)
59
- elsif command == 'update'
60
- command_update(args)
61
- elsif command == 'find'
62
- command_find(args)
63
- elsif command == 'reset'
64
- command_reset(args)
65
- else
66
- warn "ERROR: Unknown command: #{command}"
67
- warn "Run with no arguments for help."
68
- exit(1)
69
- end
70
- end
71
-
72
-
73
-
74
- def command_link(args)
75
- if args.size != 1
76
- warn "ERROR: \"link\" doesn't take any arguments"
77
- exit
78
- end
79
-
80
- sess = DropboxSession.new(APP_KEY, APP_SECRET)
81
- sess.get_request_token
82
-
83
- # Make the user log in and authorize this token
84
- url = sess.get_authorize_url
85
- puts "1. Go to: #{url}"
86
- puts "2. Authorize this app."
87
- puts "After you're done, press ENTER."
88
- STDIN.gets
89
-
90
- # This will fail if the user didn't visit the above URL and hit 'Allow'
91
- sess.get_access_token
92
- access_token = sess.access_token
93
- puts "Link successful."
94
-
95
- save_state({
96
- 'access_token' => [access_token.key, access_token.secret],
97
- 'tree' => {}
98
- })
99
- end
100
-
101
-
102
- def command_update(args)
103
- if args.size == 1
104
- page_limit = nil
105
- elsif args.size == 2
106
- page_limit = Integer(args[1])
107
- else
108
- warn "ERROR: \"update\" takes either zero or one argument."
109
- exit
110
- end
111
-
112
- # Load state
113
- state = load_state()
114
- access_token = state['access_token']
115
- cursor = state['cursor']
116
- tree = state['tree']
117
-
118
- # Connect to Dropbox
119
- sess = DropboxSession.new(APP_KEY, APP_SECRET)
120
- sess.set_access_token(*access_token)
121
- c = DropboxClient.new(sess, ACCESS_TYPE)
122
-
123
- page = 0
124
- changed = false
125
- while (page_limit == nil) or (page < page_limit)
126
- # Get /delta results from Dropbox
127
- result = c.delta(cursor)
128
- page += 1
129
- if result['reset'] == true
130
- puts 'reset'
131
- changed = true
132
- tree = {}
133
- end
134
- cursor = result['cursor']
135
- # Apply the entries one by one to our cached tree.
136
- for delta_entry in result['entries']
137
- changed = true
138
- apply_delta(tree, delta_entry)
139
- end
140
- cursor = result['cursor']
141
- if not result['has_more']
142
- break
143
- end
144
- end
145
-
146
- # Save state
147
- if changed
148
- state['cursor'] = cursor
149
- state['tree'] = tree
150
- save_state(state)
151
- else
152
- puts "No updates."
153
- end
154
-
155
- end
156
-
157
- # We track folder state as a tree of Node objects.
158
- class Node
159
- attr_accessor :path, :content
160
- def initialize(path, content)
161
- # The "original" page (i.e. not the lower-case path)
162
- @path = path
163
- # For files, content is a pair (size, modified)
164
- # For folders, content is a hash of children Nodes, keyed by lower-case file names.
165
- @content = content
166
- end
167
- def folder?()
168
- @content.is_a? Hash
169
- end
170
- def to_json()
171
- [@path, Node.to_json_content(@content)]
172
- end
173
- def self.from_json(jnode)
174
- path, jcontent = jnode
175
- Node.new(path, Node.from_json_content(jcontent))
176
- end
177
- def self.to_json_content(content)
178
- if content.is_a? Hash
179
- map_hash_values(content) { |child| child.to_json }
180
- else
181
- content
182
- end
183
- end
184
- def self.from_json_content(jcontent)
185
- if jcontent.is_a? Hash
186
- map_hash_values(jcontent) { |jchild| Node.from_json jchild }
187
- else
188
- jcontent
189
- end
190
- end
191
- end
192
-
193
- # Run a mapping function over every value in a Hash, returning a new Hash.
194
- def map_hash_values(h)
195
- new = {}
196
- h.each { |k,v| new[k] = yield v }
197
- new
198
- end
199
-
200
-
201
- def apply_delta(root, e)
202
- path, metadata = e
203
- branch, leaf = split_path(path)
204
-
205
- if metadata != nil
206
- puts "+ #{path}"
207
- # Traverse down the tree until we find the parent folder of the entry
208
- # we want to add. Create any missing folders along the way.
209
- children = root
210
- branch.each do |part|
211
- node = get_or_create_child(children, part)
212
- # If there's no folder here, make an empty one.
213
- if not node.folder?
214
- node.content = {}
215
- end
216
- children = node.content
217
- end
218
-
219
- # Create the file/folder.
220
- node = get_or_create_child(children, leaf)
221
- node.path = metadata['path'] # Save the un-lower-cased path.
222
- if metadata['is_dir']
223
- # Only create a folder if there isn't one there already.
224
- node.content = {} if not node.folder?
225
- else
226
- node.content = metadata['size'], metadata['modified']
227
- end
228
- else
229
- puts "- #{path}"
230
- # Traverse down the tree until we find the parent of the entry we
231
- # want to delete.
232
- children = root
233
- missing_parent = false
234
- branch.each do |part|
235
- node = children[part]
236
- # If one of the parent folders is missing, then we're done.
237
- if node == nil or not node.folder?
238
- missing_parent = true
239
- break
240
- end
241
- children = node.content
242
- end
243
- # If we made it all the way, delete the file/folder.
244
- if not missing_parent
245
- children.delete(leaf)
246
- end
247
- end
248
- end
249
-
250
- def get_or_create_child(children, name)
251
- child = children[name]
252
- if child == nil
253
- children[name] = child = Node.new(nil, nil)
254
- end
255
- child
256
- end
257
-
258
- def split_path(path)
259
- bad, *parts = path.split '/'
260
- [parts, parts.pop]
261
- end
262
-
263
-
264
- def command_find(args)
265
- if args.size == 1
266
- term = ''
267
- elsif args.size == 2
268
- term = args[1]
269
- else
270
- warn("ERROR: \"find\" takes either zero or one arguments.")
271
- exit
272
- end
273
-
274
- state = load_state()
275
- results = []
276
- search_tree(results, state['tree'], term)
277
- for r in results
278
- puts("#{r}")
279
- end
280
- puts("[Matches: #{results.size}]")
281
- end
282
-
283
-
284
- def command_reset(args)
285
- if args.size != 1
286
- warn("ERROR: \"reset\" takes no arguments.")
287
- exit
288
- end
289
-
290
- # Delete cursor, empty tree.
291
- state = load_state()
292
- if state.has_key?('cursor')
293
- state.delete('cursor')
294
- end
295
- state['tree'] = {}
296
- save_state(state)
297
- end
298
-
299
-
300
- # Recursively search 'tree' for files that contain the string in 'term'.
301
- # Print out any matches.
302
- def search_tree(results, tree, term)
303
- tree.each do |name_lc, node|
304
- path = node.path
305
- if (path != nil) and path.include?(term)
306
- if node.folder?
307
- results.push("#{path}")
308
- else
309
- size, modified = node.content
310
- results.push("#{path} (#{size}, #{modified})")
311
- end
312
- end
313
- if node.folder?
314
- search_tree(results, node.content, term)
315
- end
316
- end
317
- end
318
-
319
- def save_state(state)
320
- state['tree'] = Node.to_json_content(state['tree'])
321
- File.open(STATE_FILE,"w") do |f|
322
- f.write(JSON.pretty_generate(state, :max_nesting => false))
323
- end
324
- end
325
-
326
- def load_state()
327
- state = JSON.parse(File.read(STATE_FILE), :max_nesting => false)
328
- state['tree'] = Node.from_json_content(state['tree'])
329
- state
330
- end
331
-
332
- main()
@@ -1,184 +0,0 @@
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