dbox 0.6.13 → 0.6.14

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.
@@ -0,0 +1,332 @@
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()
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbox
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 6
9
- - 13
10
- version: 0.6.13
9
+ - 14
10
+ version: 0.6.14
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ken Pratt
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-06-04 00:00:00 Z
18
+ date: 2012-07-26 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: multipart-post
@@ -127,15 +127,19 @@ files:
127
127
  - sample_polling_script.rb
128
128
  - spec/dbox_spec.rb
129
129
  - spec/spec_helper.rb
130
- - vendor/dropbox-ruby-sdk/CHANGELOG
131
- - vendor/dropbox-ruby-sdk/LICENSE
132
- - vendor/dropbox-ruby-sdk/README
133
- - vendor/dropbox-ruby-sdk/cli_example.rb
134
- - vendor/dropbox-ruby-sdk/dropbox_controller.rb
135
- - vendor/dropbox-ruby-sdk/gemspec.rb
136
- - vendor/dropbox-ruby-sdk/lib/dropbox_sdk.rb
137
- - vendor/dropbox-ruby-sdk/lib/trusted-certs.crt
138
- - vendor/dropbox-ruby-sdk/web_file_browser.rb
130
+ - vendor/dropbox-ruby-sdk-1.3.1/.search_cache.rb.swp
131
+ - vendor/dropbox-ruby-sdk-1.3.1/CHANGELOG
132
+ - vendor/dropbox-ruby-sdk-1.3.1/LICENSE
133
+ - vendor/dropbox-ruby-sdk-1.3.1/README
134
+ - vendor/dropbox-ruby-sdk-1.3.1/cli_example.rb
135
+ - vendor/dropbox-ruby-sdk-1.3.1/copy_between_accounts.rb
136
+ - vendor/dropbox-ruby-sdk-1.3.1/dropbox_controller.rb
137
+ - vendor/dropbox-ruby-sdk-1.3.1/gemspec.rb
138
+ - vendor/dropbox-ruby-sdk-1.3.1/lib/dropbox_sdk.rb
139
+ - vendor/dropbox-ruby-sdk-1.3.1/lib/trusted-certs.crt
140
+ - vendor/dropbox-ruby-sdk-1.3.1/search_cache.json
141
+ - vendor/dropbox-ruby-sdk-1.3.1/search_cache.rb
142
+ - vendor/dropbox-ruby-sdk-1.3.1/web_file_browser.rb
139
143
  homepage: http://github.com/kenpratt/dbox
140
144
  licenses:
141
145
  - MIT