dropbox-sdk 1.6.4 → 1.6.5
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.
- checksums.yaml +7 -0
- data/CHANGELOG +6 -0
- data/README +9 -0
- data/examples/chunked_upload.rb +47 -47
- data/examples/cli_example.rb +172 -172
- data/examples/copy_between_accounts.rb +106 -106
- data/examples/dropbox_controller.rb +70 -70
- data/examples/oauth1_upgrade.rb +28 -28
- data/examples/search_cache.rb +231 -231
- data/examples/web_file_browser.rb +111 -111
- data/lib/dropbox_sdk.rb +1273 -1228
- metadata +66 -51
data/examples/search_cache.rb
CHANGED
@@ -31,292 +31,292 @@ APP_SECRET = ''
|
|
31
31
|
STATE_FILE = 'search_cache.json'
|
32
32
|
|
33
33
|
def main()
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
65
|
end
|
66
66
|
|
67
67
|
|
68
68
|
def command_link(args)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
91
|
end
|
92
92
|
|
93
93
|
|
94
94
|
def command_update(args)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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 = {}
|
102
123
|
end
|
103
|
-
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
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)
|
134
129
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
state['cursor'] = cursor
|
139
|
-
state['tree'] = tree
|
140
|
-
save_state(state)
|
141
|
-
else
|
142
|
-
puts "No updates."
|
130
|
+
cursor = result['cursor']
|
131
|
+
if not result['has_more']
|
132
|
+
break
|
143
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
144
|
|
145
145
|
end
|
146
146
|
|
147
147
|
# We track folder state as a tree of Node objects.
|
148
148
|
class Node
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
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
|
173
172
|
end
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
180
179
|
end
|
180
|
+
end
|
181
181
|
end
|
182
182
|
|
183
183
|
# Run a mapping function over every value in a Hash, returning a new Hash.
|
184
184
|
def map_hash_values(h)
|
185
|
-
|
186
|
-
|
187
|
-
|
185
|
+
new = {}
|
186
|
+
h.each { |k,v| new[k] = yield v }
|
187
|
+
new
|
188
188
|
end
|
189
189
|
|
190
190
|
|
191
191
|
def apply_delta(root, e)
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
else
|
216
|
-
node.content = metadata['size'], metadata['modified']
|
217
|
-
end
|
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?
|
218
215
|
else
|
219
|
-
|
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
|
216
|
+
node.content = metadata['size'], metadata['modified']
|
237
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
238
|
end
|
239
239
|
|
240
240
|
def get_or_create_child(children, name)
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
241
|
+
child = children[name]
|
242
|
+
if child == nil
|
243
|
+
children[name] = child = Node.new(nil, nil)
|
244
|
+
end
|
245
|
+
child
|
246
246
|
end
|
247
247
|
|
248
248
|
def split_path(path)
|
249
|
-
|
250
|
-
|
249
|
+
bad, *parts = path.split '/'
|
250
|
+
[parts, parts.pop]
|
251
251
|
end
|
252
252
|
|
253
253
|
|
254
254
|
def command_find(args)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
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
271
|
end
|
272
272
|
|
273
273
|
|
274
274
|
def command_reset(args)
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
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
287
|
end
|
288
288
|
|
289
289
|
|
290
290
|
# Recursively search 'tree' for files that contain the string in 'term'.
|
291
291
|
# Print out any matches.
|
292
292
|
def search_tree(results, tree, term)
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
end
|
303
|
-
if node.folder?
|
304
|
-
search_tree(results, node.content, term)
|
305
|
-
end
|
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
|
306
302
|
end
|
303
|
+
if node.folder?
|
304
|
+
search_tree(results, node.content, term)
|
305
|
+
end
|
306
|
+
end
|
307
307
|
end
|
308
308
|
|
309
309
|
def save_state(state)
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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
314
|
end
|
315
315
|
|
316
316
|
def load_state()
|
317
|
-
|
318
|
-
|
319
|
-
|
317
|
+
state = JSON.parse(File.read(STATE_FILE), :max_nesting => false)
|
318
|
+
state['tree'] = Node.from_json_content(state['tree'])
|
319
|
+
state
|
320
320
|
end
|
321
321
|
|
322
322
|
main()
|