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.
- data/History.txt +4 -0
- data/README.md +14 -16
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/dbox.gemspec +6 -15
- data/lib/dbox/api.rb +22 -22
- metadata +85 -97
- data/vendor/dropbox-ruby-sdk/CHANGELOG +0 -42
- data/vendor/dropbox-ruby-sdk/LICENSE +0 -20
- data/vendor/dropbox-ruby-sdk/README +0 -7
- data/vendor/dropbox-ruby-sdk/cli_example.rb +0 -206
- data/vendor/dropbox-ruby-sdk/copy_between_accounts.rb +0 -155
- data/vendor/dropbox-ruby-sdk/dropbox_controller.rb +0 -57
- data/vendor/dropbox-ruby-sdk/gemspec.rb +0 -25
- data/vendor/dropbox-ruby-sdk/lib/dropbox_sdk.rb +0 -883
- data/vendor/dropbox-ruby-sdk/lib/trusted-certs.crt +0 -341
- data/vendor/dropbox-ruby-sdk/search_cache.json +0 -42830
- data/vendor/dropbox-ruby-sdk/search_cache.rb +0 -332
- data/vendor/dropbox-ruby-sdk/web_file_browser.rb +0 -184
@@ -1,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
|