dbox 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ module Dbox
2
+ module Utils
3
+ def times_equal?(t1, t2)
4
+ time_to_s(t1) == time_to_s(t2)
5
+ end
6
+
7
+ def time_to_s(t)
8
+ case t
9
+ when Time
10
+ # matches dropbox time format
11
+ t.utc.strftime("%a, %d %b %Y %H:%M:%S +0000")
12
+ when String
13
+ t
14
+ end
15
+ end
16
+
17
+ def parse_time(t)
18
+ case t
19
+ when Time
20
+ t
21
+ when String
22
+ Time.parse(t)
23
+ end
24
+ end
25
+
26
+ # assumes local_path is defined
27
+ def local_to_relative_path(path)
28
+ if path.include?(local_path)
29
+ path.sub(local_path, "").sub(/^\//, "")
30
+ else
31
+ raise BadPath, "Not a local path: #{path}"
32
+ end
33
+ end
34
+
35
+ # assumes remote_path is defined
36
+ def remote_to_relative_path(path)
37
+ if path.include?(remote_path)
38
+ path.sub(remote_path, "").sub(/^\//, "")
39
+ else
40
+ raise BadPath, "Not a remote path: #{path}"
41
+ end
42
+ end
43
+
44
+ # assumes local_path is defined
45
+ def relative_to_local_path(path)
46
+ if path && path.length > 0
47
+ File.join(local_path, path)
48
+ else
49
+ local_path
50
+ end
51
+ end
52
+
53
+ # assumes remote_path is defined
54
+ def relative_to_remote_path(path)
55
+ if path && path.length > 0
56
+ File.join(remote_path, path)
57
+ else
58
+ remote_path
59
+ end
60
+ end
61
+
62
+ def calculate_hash(filepath)
63
+ begin
64
+ Digest::MD5.file(filepath).to_s
65
+ rescue Errno::EISDIR
66
+ nil
67
+ rescue Errno::ENOENT
68
+ nil
69
+ end
70
+ end
71
+
72
+ def find_nonconflicting_path(filepath)
73
+ proposed = filepath
74
+ while File.exists?(proposed)
75
+ dir, p = File.split(proposed)
76
+ p = p.sub(/^(.*?)( \((\d+)\))?(\..*?)?$/) { "#{$1} (#{$3 ? $3.to_i + 1 : 1})#{$4}" }
77
+ proposed = File.join(dir, p)
78
+ end
79
+ proposed
80
+ end
81
+ end
82
+ end
@@ -122,6 +122,101 @@ describe Dbox do
122
122
  Dbox.pull(@local).should eql(:created => ["subdir", "subdir/one.txt"], :deleted => ["foo.txt"], :updated => ["", "baz.txt"], :failed => [])
123
123
  Dbox.pull(@local).should eql(:created => [], :deleted => [], :updated => [], :failed => [])
124
124
  end
125
+
126
+ it "should be able to download a bunch of files at the same time" do
127
+ Dbox.create(@remote, @local)
128
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
129
+ Dbox.clone(@remote, @alternate)
130
+
131
+ # generate 20 x 100kB files
132
+ 20.times do
133
+ make_file "#{@alternate}/#{randname}.txt", 100
134
+ end
135
+
136
+ Dbox.push(@alternate)
137
+
138
+ res = Dbox.pull(@local)
139
+ res[:deleted].should eql([])
140
+ res[:updated].should eql([""])
141
+ res[:failed].should eql([])
142
+ res[:created].size.should eql(20)
143
+ end
144
+
145
+ it "should be able to pull a series of updates to the same file" do
146
+ Dbox.create(@remote, @local)
147
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
148
+ Dbox.clone(@remote, @alternate)
149
+
150
+ make_file "#{@local}/hello.txt"
151
+ Dbox.push(@local)
152
+ Dbox.pull(@alternate).should eql(:created => ["hello.txt"], :deleted => [], :updated => [""], :failed => [])
153
+ make_file "#{@local}/hello.txt"
154
+ Dbox.push(@local)
155
+ Dbox.pull(@alternate).should eql(:created => [], :deleted => [], :updated => ["", "hello.txt"], :failed => [])
156
+ make_file "#{@local}/hello.txt"
157
+ Dbox.push(@local)
158
+ Dbox.pull(@alternate).should eql(:created => [], :deleted => [], :updated => ["", "hello.txt"], :failed => [])
159
+ end
160
+
161
+ it "should handle conflicting pulls of new files gracefully" do
162
+ Dbox.create(@remote, @local)
163
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
164
+ Dbox.clone(@remote, @alternate)
165
+
166
+ make_file "#{@local}/hello.txt"
167
+ Dbox.push(@local)
168
+
169
+ make_file "#{@alternate}/hello.txt"
170
+ Dbox.pull(@alternate).should eql(:created => ["hello.txt"], :deleted => [], :updated => [""], :conflicts => [{:original => "hello.txt", :renamed => "hello (1).txt"}], :failed => [])
171
+ end
172
+
173
+ it "should handle conflicting pulls of updated files gracefully" do
174
+ Dbox.create(@remote, @local)
175
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
176
+ Dbox.clone(@remote, @alternate)
177
+
178
+ make_file "#{@local}/hello.txt"
179
+ Dbox.push(@local)
180
+ Dbox.pull(@alternate).should eql(:created => ["hello.txt"], :deleted => [], :updated => [""], :failed => [])
181
+
182
+ make_file "#{@local}/hello.txt"
183
+ Dbox.push(@local)
184
+
185
+ make_file "#{@alternate}/hello.txt"
186
+ Dbox.pull(@alternate).should eql(:created => [], :deleted => [], :updated => ["", "hello.txt"], :conflicts => [{:original => "hello.txt", :renamed => "hello (1).txt"}], :failed => [])
187
+ end
188
+
189
+ it "should deal with all sorts of weird filenames when renaming due to conflicts on pull" do
190
+ Dbox.create(@remote, @local)
191
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
192
+ Dbox.clone(@remote, @alternate)
193
+
194
+ make_file "#{@local}/hello.txt"
195
+ make_file "#{@local}/hello (1).txt"
196
+ make_file "#{@local}/goodbye.txt"
197
+ Dbox.push(@local)
198
+
199
+ make_file "#{@alternate}/hello.txt"
200
+ make_file "#{@alternate}/hello (1).txt"
201
+ make_file "#{@alternate}/hello (3).txt"
202
+ make_file "#{@alternate}/hello (4).txt"
203
+ make_file "#{@alternate}/hello (test).txt"
204
+ make_file "#{@alternate}/goodbye.txt"
205
+ make_file "#{@alternate}/goodbye (1).txt"
206
+ make_file "#{@alternate}/goodbye (2).txt"
207
+ make_file "#{@alternate}/goodbye (3).txt"
208
+ make_file "#{@alternate}/goodbye ().txt"
209
+
210
+ # there's a race condition, so the output could be one of two things
211
+ res = Dbox.pull(@alternate)
212
+ res[:created].should eql(["goodbye.txt", "hello (1).txt", "hello.txt"])
213
+ res[:updated].should eql([""])
214
+ res[:deleted].should eql([])
215
+ res[:failed].should eql([])
216
+ c = (res[:conflicts] == [{:original => "goodbye.txt", :renamed => "goodbye (4).txt"}, {:original => "hello (1).txt", :renamed => "hello (5).txt"}, {:original => "hello.txt", :renamed => "hello (2).txt"}]) ||
217
+ (res[:conflicts] == [{:original => "goodbye.txt", :renamed => "goodbye (4).txt"}, {:original => "hello (1).txt", :renamed => "hello (2).txt"}, {:original => "hello.txt", :renamed => "hello (5).txt"}])
218
+ c.should be_true
219
+ end
125
220
  end
126
221
 
127
222
  describe "#push" do
@@ -230,6 +325,112 @@ describe Dbox do
230
325
  rm_rf @local
231
326
  Dbox.clone(@remote, @local).should eql(:created => [crazy_name1, File.join(crazy_name1, "foo.txt")], :deleted => [], :updated => [""], :failed => [])
232
327
  end
328
+
329
+ it "should be able to upload a bunch of files at the same time" do
330
+ Dbox.create(@remote, @local)
331
+
332
+ # generate 20 x 100kB files
333
+ 20.times do
334
+ make_file "#{@local}/#{randname}.txt", 100
335
+ end
336
+
337
+ res = Dbox.push(@local)
338
+ res[:deleted].should eql([])
339
+ res[:updated].should eql([])
340
+ res[:failed].should eql([])
341
+ res[:created].size.should eql(20)
342
+ end
343
+
344
+ it "should be able to push a series of updates to the same file" do
345
+ Dbox.create(@remote, @local)
346
+
347
+ make_file "#{@local}/hello.txt"
348
+ Dbox.push(@local).should eql(:created => ["hello.txt"], :deleted => [], :updated => [], :failed => [])
349
+ make_file "#{@local}/hello.txt"
350
+ Dbox.push(@local).should eql(:created => [], :deleted => [], :updated => ["hello.txt"], :failed => [])
351
+ make_file "#{@local}/hello.txt"
352
+ Dbox.push(@local).should eql(:created => [], :deleted => [], :updated => ["hello.txt"], :failed => [])
353
+ end
354
+
355
+ it "should handle conflicting pushes of new files gracefully" do
356
+ Dbox.create(@remote, @local)
357
+
358
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
359
+ Dbox.clone(@remote, @alternate)
360
+
361
+ make_file "#{@local}/hello.txt"
362
+ Dbox.push(@local).should eql(:created => ["hello.txt"], :deleted => [], :updated => [], :failed => [])
363
+
364
+ make_file "#{@alternate}/hello.txt"
365
+ Dbox.push(@alternate).should eql(:created => [], :deleted => [], :updated => [], :conflicts => [{:original => "hello.txt", :renamed => "hello (1).txt"}], :failed => [])
366
+ end
367
+
368
+ it "should handle conflicting pushes of updated files gracefully" do
369
+ Dbox.create(@remote, @local)
370
+ make_file "#{@local}/hello.txt"
371
+ Dbox.push(@local)
372
+
373
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
374
+ Dbox.clone(@remote, @alternate)
375
+
376
+ make_file "#{@local}/hello.txt"
377
+ Dbox.push(@local).should eql(:created => [], :deleted => [], :updated => ["hello.txt"], :failed => [])
378
+
379
+ make_file "#{@alternate}/hello.txt"
380
+ Dbox.push(@alternate).should eql(:created => [], :deleted => [], :updated => [], :conflicts => [{:original => "hello.txt", :renamed => "hello (1).txt"}], :failed => [])
381
+ end
382
+ end
383
+
384
+ describe "#sync" do
385
+ it "should fail if the local dir is missing" do
386
+ expect { Dbox.sync(@local) }.to raise_error(Dbox::DatabaseError)
387
+ end
388
+
389
+ it "should be able to sync basic changes" do
390
+ Dbox.create(@remote, @local)
391
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
392
+ Dbox.clone(@remote, @alternate)
393
+
394
+ Dbox.sync(@local).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
395
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
396
+ Dbox.sync(@alternate).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
397
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
398
+
399
+ make_file "#{@local}/hello.txt"
400
+ Dbox.sync(@local).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
401
+ :push => { :created => ["hello.txt"], :deleted => [], :updated => [], :failed => [] })
402
+ Dbox.sync(@alternate).should eql(:pull => { :created => ["hello.txt"], :deleted => [], :updated => [""], :failed => [] },
403
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
404
+ end
405
+
406
+ it "should be able to sync complex changes" do
407
+ Dbox.create(@remote, @local)
408
+ @alternate = "#{ALTERNATE_LOCAL_TEST_PATH}/#{@name}"
409
+ Dbox.clone(@remote, @alternate)
410
+
411
+ Dbox.sync(@local).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
412
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
413
+ Dbox.sync(@alternate).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
414
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
415
+
416
+ make_file "#{@local}/hello.txt"
417
+ make_file "#{@local}/goodbye.txt"
418
+ make_file "#{@local}/so_long.txt"
419
+ make_file "#{@alternate}/hello.txt"
420
+ make_file "#{@alternate}/farewell.txt"
421
+ Dbox.sync(@local).should eql(:pull => { :created => [], :deleted => [], :updated => [], :failed => [] },
422
+ :push => { :created => ["goodbye.txt", "hello.txt", "so_long.txt"], :deleted => [], :updated => [], :failed => [] })
423
+ Dbox.sync(@alternate).should eql(:pull => { :created => ["goodbye.txt", "hello.txt", "so_long.txt"], :deleted => [], :updated => [""], :failed => [], :conflicts => [{ :renamed => "hello (1).txt", :original => "hello.txt" }] },
424
+ :push => { :created => ["farewell.txt", "hello (1).txt"], :deleted => [], :updated => [], :failed => [] })
425
+
426
+ make_file "#{@alternate}/farewell.txt"
427
+ make_file "#{@alternate}/goodbye.txt"
428
+ make_file "#{@alternate}/au_revoir.txt"
429
+ Dbox.sync(@alternate).should eql(:pull => { :created => [], :deleted => [], :updated => [""], :failed => [] },
430
+ :push => { :created => ["au_revoir.txt"], :deleted => [], :updated => ["farewell.txt", "goodbye.txt"], :failed => [] })
431
+ Dbox.sync(@local).should eql(:pull => { :created => ["au_revoir.txt", "farewell.txt", "hello (1).txt"], :deleted => [], :updated => ["", "goodbye.txt"], :failed => [] },
432
+ :push => { :created => [], :deleted => [], :updated => [], :failed => [] })
433
+ end
233
434
  end
234
435
 
235
436
  describe "#move" do
@@ -36,8 +36,8 @@ def log
36
36
  LOGGER
37
37
  end
38
38
 
39
- def make_file(filepath)
40
- File.open(filepath, "w") {|f| f << randname }
39
+ def make_file(filepath, size_in_kb=1)
40
+ `dd if=/dev/urandom of="#{filepath.gsub('"','\"')}" bs=1024 count=#{size_in_kb} 1>/dev/null 2>/dev/null`
41
41
  end
42
42
 
43
43
  RSpec::Matchers.define :exist do
@@ -0,0 +1,29 @@
1
+ 1.1 (2011-10-17)
2
+ * Various bug fixes found during beta period
3
+ * Improved documentation
4
+ * Improved module structure
5
+ * Removed dependency on the oauth module, using plaintext authentication over https
6
+
7
+ 1.0 (2011-8-16)
8
+ * Backwards compatibility broken
9
+ - Changed interface
10
+ - Change 'sandbox' references to 'app_folder'
11
+ * Updated SDK to Dropbox API Version 1, supporting all calls
12
+ - Added 'rev' parameter to metadata and get_file
13
+ - Added 'parent_rev' parameter to put_file
14
+ - Added search, share, media, revisions, and restore
15
+ - put_file uses /files_put instead of multipart POST
16
+ - Removed methods for calls that were removed from v1 of the REST API
17
+ * Changed return format for calls
18
+ - On error (non-200 response), an exception is raised
19
+ - On success, the JSON is parsed and a Hash is returned
20
+ * Updated examples
21
+ - Improved CLI example
22
+ - Added a Ruby on Rails 3 controller example
23
+ - Added a web based file browser/uploader that uses Sinatra
24
+ * put_file no longer takes a "name" arugment, only takes a full path
25
+ * Removed reliance on config files
26
+ * Assorted bugfixes and improvements
27
+ * All calls are now made over SSL
28
+ * Fully documented code for RDoc generation
29
+ * Added a CHANGELOG
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Dropbox (Evenflow, Inc.), http://getdropbox.com/
1
+ Copyright (c) 2009-2011 Dropbox Inc., http://www.dropbox.com/
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,7 @@
1
+ Getting started with the Dropbox Ruby SDK:
2
+ 1. Install json and oauth ruby gems with:
3
+ gem install json oauth
4
+ 2. Edit cli_example.rb to to include your APP_KEY and APP_SECRET
5
+ 3. Try running the example app: 'ruby clie_example.rb'.
6
+ 4. See dropbox_controller.rb for an example Ruby on Rails controller. It needs an APP_KEY and APP_SECRET set too.
7
+ 5. Check out the dropbox website for reference and help! http://dropbox.com/developers_beta
@@ -0,0 +1,197 @@
1
+ require './lib/dropbox_sdk'
2
+ require 'pp'
3
+
4
+ ####
5
+ # An example app using the Dropbox API Ruby Client
6
+ # This ruby script sets up a basic command line interface (CLI)
7
+ # that prompts a user to authenticate on the web, then
8
+ # allows them to type commands to manipulate their dropbox.
9
+ ####
10
+
11
+ # You must use your Dropbox App key and secret to use the API.
12
+ # Find this at https://www.dropbox.com/developers
13
+ APP_KEY = ''
14
+ APP_SECRET = ''
15
+ ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
16
+ #The default is :app_folder, but your application might be
17
+ #set to have full :dropbox access. Check your app at
18
+ #https://www.dropbox.com/developers/apps
19
+
20
+ class DropboxCLI
21
+ LOGIN_REQUIRED = %w{put get cp mv rm ls mkdir info logout search}
22
+
23
+ def initialize
24
+ if APP_KEY == '' or APP_SECRET == ''
25
+ puts "You must set your APP_KEY and APP_SECRET in cli_example.rb!"
26
+ puts "Find this in your apps page at https://www.dropbox.com/developers/"
27
+ exit
28
+ end
29
+
30
+ @session = DropboxSession.new(APP_KEY, APP_SECRET)
31
+ @client = nil
32
+ end
33
+
34
+ def login
35
+ ########
36
+ # Instead of going to a authorize URL, you can set a access token key and secret
37
+ # from a previous session
38
+ ########
39
+ # @session.set_access_token('key', 'secret')
40
+
41
+ if @session.authorized?
42
+ puts "already logged in!"
43
+ else
44
+
45
+ # grab the request token for session
46
+ @session.get_request_token
47
+
48
+ authorize_url = @session.get_authorize_url
49
+ puts "Got a request token. Your request token key is #{@session.request_token.key} and your token secret is #{@session.request_token.secret}"
50
+
51
+ # make the user log in and authorize this token
52
+ puts "AUTHORIZING", authorize_url, "Please visit that web page and hit 'Allow', then hit Enter here."
53
+ gets
54
+
55
+ # get the access token from the server. Its then stored in the session.
56
+ @session.get_access_token
57
+
58
+ end
59
+ puts "You are logged in. Your access token key is #{@session.access_token.key} your secret is #{@session.access_token.secret}"
60
+ @client = DropboxClient.new(@session, ACCESS_TYPE)
61
+ end
62
+
63
+ def command_loop
64
+ puts "Enter a command or 'help' or 'exit'"
65
+ command_line = ''
66
+ while command_line.strip != 'exit'
67
+ begin
68
+ execute_dropbox_command(command_line)
69
+ rescue RuntimeError => e
70
+ puts "Command Line Error! #{e.class}: #{e}"
71
+ puts e.backtrace
72
+ end
73
+ print '> '
74
+ command_line = gets.strip
75
+ end
76
+ puts 'goodbye'
77
+ exit(0)
78
+ end
79
+
80
+ def execute_dropbox_command(cmd_line)
81
+ command = cmd_line.split
82
+ method = command.first
83
+ if LOGIN_REQUIRED.include? method
84
+ if @client
85
+ send(method.to_sym, command)
86
+ else
87
+ puts 'must be logged in; type \'login\' to get started.'
88
+ end
89
+ elsif ['login', 'help'].include? method
90
+ send(method.to_sym)
91
+ else
92
+ if command.first && !command.first.strip.empty?
93
+ puts 'invalid command. type \'help\' to see commands.'
94
+ end
95
+ end
96
+ end
97
+
98
+ def logout(command)
99
+ @session.clear_access_token
100
+ puts "You are logged out."
101
+ @client = nil
102
+ end
103
+
104
+ def put(command)
105
+ fname = command[1]
106
+
107
+ #If the user didn't specifiy the file name, just use the name of the file on disk
108
+ if command[2]
109
+ new_name = command[2]
110
+ else
111
+ new_name = File.basename(fname)
112
+ end
113
+
114
+ if fname && !fname.empty? && File.exists?(fname) && (File.ftype(fname) == 'file') && File.stat(fname).readable?
115
+ #This is where we call the the Dropbox Client
116
+ pp @client.put_file(new_name, open(fname))
117
+ else
118
+ puts "couldn't find the file #{ fname }"
119
+ end
120
+ end
121
+
122
+ def get(command)
123
+ dest = command[2]
124
+ if !command[1] || command[1].empty?
125
+ puts "please specify item to get"
126
+ elsif !dest || dest.empty?
127
+ puts "please specify full local path to dest, i.e. the file to write to"
128
+ elsif File.exists?(dest)
129
+ puts "error: File #{dest} already exists."
130
+ else
131
+ src = clean_up(command[1])
132
+ out = @client.get_file('/' + src)
133
+ open(dest, 'w'){|f| f.puts out }
134
+ puts "wrote file #{dest}."
135
+ end
136
+ end
137
+
138
+ def mkdir(command)
139
+ pp @client.file_create_folder(command[1])
140
+ end
141
+
142
+ def thumbnail(command)
143
+ command[2] ||= 'small'
144
+ pp @client.thumbnail(command[1], command[2])
145
+ end
146
+
147
+ def cp(command)
148
+ src = clean_up(command[1])
149
+ dest = clean_up(command[2])
150
+ pp @client.file_copy(src, dest)
151
+ end
152
+
153
+ def mv(command)
154
+ src = clean_up(command[1])
155
+ dest = clean_up(command[2])
156
+ pp @client.file_move(src, dest)
157
+ end
158
+
159
+ def rm(command)
160
+ pp @client.file_delete(clean_up(command[1]))
161
+ end
162
+
163
+ def search(command)
164
+ resp = @client.search('/',clean_up(command[1]))
165
+
166
+ for item in resp
167
+ puts item['path']
168
+ end
169
+ end
170
+
171
+ def info(command)
172
+ pp @client.account_info
173
+ end
174
+
175
+ def ls(command)
176
+ command[1] = '/' + clean_up(command[1] || '')
177
+ resp = @client.metadata(command[1])
178
+
179
+ if resp['contents'].length > 0
180
+ for item in resp['contents']
181
+ puts item['path']
182
+ end
183
+ end
184
+ end
185
+
186
+ def help
187
+ puts "commands are: login #{LOGIN_REQUIRED.join(' ')} help exit"
188
+ end
189
+
190
+ def clean_up(str)
191
+ return str.gsub(/^\/+/, '') if str
192
+ str
193
+ end
194
+ end
195
+
196
+ cli = DropboxCLI.new
197
+ cli.command_loop