dbox 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +83 -0
- data/README.md +31 -7
- data/TODO.txt +1 -1
- data/VERSION +1 -1
- data/bin/dbox +2 -1
- data/dbox.gemspec +13 -11
- data/lib/dbox.rb +11 -2
- data/lib/dbox/api.rb +130 -66
- data/lib/dbox/cacert.pem +3376 -0
- data/lib/dbox/database.rb +106 -8
- data/lib/dbox/syncer.rb +103 -86
- data/lib/dbox/utils.rb +82 -0
- data/spec/dbox_spec.rb +201 -0
- data/spec/spec_helper.rb +2 -2
- data/vendor/dropbox-ruby-sdk/CHANGELOG +29 -0
- data/vendor/{dropbox-client-ruby → dropbox-ruby-sdk}/LICENSE +1 -1
- data/vendor/dropbox-ruby-sdk/README +7 -0
- data/vendor/dropbox-ruby-sdk/cli_example.rb +197 -0
- data/vendor/dropbox-ruby-sdk/dropbox_controller.rb +57 -0
- data/vendor/dropbox-ruby-sdk/gemspec.rb +25 -0
- data/vendor/dropbox-ruby-sdk/lib/dropbox_sdk.rb +690 -0
- data/vendor/dropbox-ruby-sdk/web_file_browser.rb +184 -0
- metadata +16 -14
- data/vendor/dropbox-client-ruby/README +0 -17
- data/vendor/dropbox-client-ruby/Rakefile +0 -41
- data/vendor/dropbox-client-ruby/config/testing.json.example +0 -16
- data/vendor/dropbox-client-ruby/lib/dropbox.rb +0 -259
- data/vendor/dropbox-client-ruby/manifest +0 -9
- data/vendor/dropbox-client-ruby/test/authenticator_test.rb +0 -53
- data/vendor/dropbox-client-ruby/test/client_test.rb +0 -100
- data/vendor/dropbox-client-ruby/test/util.rb +0 -21
data/lib/dbox/database.rb
CHANGED
@@ -3,6 +3,7 @@ module Dbox
|
|
3
3
|
|
4
4
|
class Database
|
5
5
|
include Loggable
|
6
|
+
include Utils
|
6
7
|
|
7
8
|
DB_FILENAME = ".dbox.sqlite3"
|
8
9
|
|
@@ -61,16 +62,20 @@ module Dbox
|
|
61
62
|
path varchar(255) UNIQUE NOT NULL,
|
62
63
|
is_dir boolean NOT NULL,
|
63
64
|
parent_id integer REFERENCES entries(id) ON DELETE CASCADE,
|
64
|
-
|
65
|
+
local_hash varchar(255),
|
66
|
+
remote_hash varchar(255),
|
65
67
|
modified datetime,
|
66
|
-
revision
|
68
|
+
revision varchar(255)
|
67
69
|
);
|
68
70
|
CREATE INDEX IF NOT EXISTS entry_parent_ids ON entries(parent_id);
|
69
71
|
})
|
70
72
|
end
|
71
73
|
|
72
74
|
def migrate
|
75
|
+
# removing local_path from metadata
|
73
76
|
if metadata[:version] < 2
|
77
|
+
log.info "Migrating to database schema v2"
|
78
|
+
|
74
79
|
@db.execute_batch(%{
|
75
80
|
BEGIN TRANSACTION;
|
76
81
|
ALTER TABLE metadata RENAME TO metadata_old;
|
@@ -85,15 +90,105 @@ module Dbox
|
|
85
90
|
COMMIT;
|
86
91
|
})
|
87
92
|
end
|
93
|
+
|
94
|
+
# migrating to new Dropbox API 1.0 (from integer revisions to
|
95
|
+
# string revisions)
|
96
|
+
if metadata[:version] < 3
|
97
|
+
log.info "Migrating to database schema v3"
|
98
|
+
|
99
|
+
api = API.connect
|
100
|
+
new_revisions = {}
|
101
|
+
|
102
|
+
# fetch the new revision IDs from dropbox
|
103
|
+
find_entries().each do |entry|
|
104
|
+
path = relative_to_remote_path(entry[:path])
|
105
|
+
begin
|
106
|
+
data = api.metadata(path, nil, false)
|
107
|
+
# record nev revision ("rev") iff old revisions ("revision") match
|
108
|
+
if entry[:revision] == data["revision"]
|
109
|
+
new_revisions[entry[:id]] = data["rev"]
|
110
|
+
end
|
111
|
+
rescue Dbox::ServerError => e
|
112
|
+
log.error e
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# modify the table to have a string for revision (blanked out
|
117
|
+
# for each entry)
|
118
|
+
@db.execute_batch(%{
|
119
|
+
BEGIN TRANSACTION;
|
120
|
+
ALTER TABLE entries RENAME TO entries_old;
|
121
|
+
CREATE TABLE entries (
|
122
|
+
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
123
|
+
path varchar(255) UNIQUE NOT NULL,
|
124
|
+
is_dir boolean NOT NULL,
|
125
|
+
parent_id integer REFERENCES entries(id) ON DELETE CASCADE,
|
126
|
+
hash varchar(255),
|
127
|
+
modified datetime,
|
128
|
+
revision varchar(255)
|
129
|
+
);
|
130
|
+
INSERT INTO entries SELECT id, path, is_dir, parent_id, hash, modified, null FROM entries_old;
|
131
|
+
})
|
132
|
+
|
133
|
+
# copy in the new revision IDs
|
134
|
+
new_revisions.each do |id, revision|
|
135
|
+
update_entry_by_id(id, :revision => revision)
|
136
|
+
end
|
137
|
+
|
138
|
+
# drop old table and commit
|
139
|
+
@db.execute_batch(%{
|
140
|
+
DROP TABLE entries_old;
|
141
|
+
UPDATE metadata SET version = 3;
|
142
|
+
COMMIT;
|
143
|
+
})
|
144
|
+
end
|
145
|
+
|
146
|
+
if metadata[:version] < 4
|
147
|
+
log.info "Migrating to database schema v4"
|
148
|
+
|
149
|
+
# add local_hash column, rename hash to remote_hash
|
150
|
+
@db.execute_batch(%{
|
151
|
+
BEGIN TRANSACTION;
|
152
|
+
ALTER TABLE entries RENAME TO entries_old;
|
153
|
+
CREATE TABLE entries (
|
154
|
+
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
155
|
+
path varchar(255) UNIQUE NOT NULL,
|
156
|
+
is_dir boolean NOT NULL,
|
157
|
+
parent_id integer REFERENCES entries(id) ON DELETE CASCADE,
|
158
|
+
local_hash varchar(255),
|
159
|
+
remote_hash varchar(255),
|
160
|
+
modified datetime,
|
161
|
+
revision varchar(255)
|
162
|
+
);
|
163
|
+
INSERT INTO entries SELECT id, path, is_dir, parent_id, null, hash, modified, revision FROM entries_old;
|
164
|
+
})
|
165
|
+
|
166
|
+
# calculate hashes on files with same timestamp as we have (as that was the previous mechanism used to check freshness)
|
167
|
+
find_entries().each do |entry|
|
168
|
+
unless entry[:is_dir]
|
169
|
+
path = relative_to_local_path(entry[:path])
|
170
|
+
if times_equal?(File.mtime(path), entry[:modified])
|
171
|
+
update_entry_by_id(entry[:id], :local_hash => calculate_hash(path))
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# drop old table and commit
|
177
|
+
@db.execute_batch(%{
|
178
|
+
DROP TABLE entries_old;
|
179
|
+
UPDATE metadata SET version = 4;
|
180
|
+
COMMIT;
|
181
|
+
})
|
182
|
+
end
|
88
183
|
end
|
89
184
|
|
90
185
|
METADATA_COLS = [ :remote_path, :version ] # don't need to return id
|
91
|
-
ENTRY_COLS = [ :id, :path, :is_dir, :parent_id, :
|
186
|
+
ENTRY_COLS = [ :id, :path, :is_dir, :parent_id, :local_hash, :remote_hash, :modified, :revision ]
|
92
187
|
|
93
188
|
def bootstrap(remote_path)
|
94
189
|
@db.execute(%{
|
95
190
|
INSERT INTO metadata (remote_path, version) VALUES (?, ?);
|
96
|
-
}, remote_path,
|
191
|
+
}, remote_path, 4)
|
97
192
|
@db.execute(%{
|
98
193
|
INSERT INTO entries (path, is_dir) VALUES (?, ?)
|
99
194
|
}, "", 1)
|
@@ -116,6 +211,10 @@ module Dbox
|
|
116
211
|
out
|
117
212
|
end
|
118
213
|
|
214
|
+
def remote_path
|
215
|
+
metadata()[:remote_path]
|
216
|
+
end
|
217
|
+
|
119
218
|
def update_metadata(fields)
|
120
219
|
set_str = fields.keys.map {|k| "#{k}=?" }.join(",")
|
121
220
|
@db.execute(%{
|
@@ -142,8 +241,8 @@ module Dbox
|
|
142
241
|
find_entries("WHERE parent_id=? AND is_dir=1", dir_id)
|
143
242
|
end
|
144
243
|
|
145
|
-
def add_entry(path, is_dir, parent_id, modified, revision,
|
146
|
-
insert_entry(:path => path, :is_dir => is_dir, :parent_id => parent_id, :modified => modified, :revision => revision, :
|
244
|
+
def add_entry(path, is_dir, parent_id, modified, revision, remote_hash, local_hash)
|
245
|
+
insert_entry(:path => path, :is_dir => is_dir, :parent_id => parent_id, :modified => modified, :revision => revision, :remote_hash => remote_hash, :local_hash => local_hash)
|
147
246
|
end
|
148
247
|
|
149
248
|
def update_entry_by_id(id, fields)
|
@@ -172,7 +271,7 @@ module Dbox
|
|
172
271
|
|
173
272
|
def migrate_entry_from_old_db_format(entry, parent = nil)
|
174
273
|
# insert entry into sqlite db
|
175
|
-
add_entry(entry.path, entry.dir?, (parent ? parent[:id] : nil), entry.modified_at, entry.revision, nil)
|
274
|
+
add_entry(entry.path, entry.dir?, (parent ? parent[:id] : nil), entry.modified_at, entry.revision, nil, nil)
|
176
275
|
|
177
276
|
# recur on children
|
178
277
|
if entry.dir?
|
@@ -233,7 +332,6 @@ module Dbox
|
|
233
332
|
h = make_fields(ENTRY_COLS, res)
|
234
333
|
h[:is_dir] = (h[:is_dir] == 1)
|
235
334
|
h[:modified] = Time.at(h[:modified]) if h[:modified]
|
236
|
-
h.delete(:hash) unless h[:is_dir]
|
237
335
|
h
|
238
336
|
else
|
239
337
|
nil
|
data/lib/dbox/syncer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Dbox
|
2
2
|
class Syncer
|
3
3
|
MAX_PARALLEL_DBOX_OPS = 5
|
4
|
+
MIN_BYTES_TO_STREAM_DOWNLOAD = 1024 * 100 # 100kB
|
4
5
|
|
5
6
|
include Loggable
|
6
7
|
|
@@ -37,6 +38,7 @@ module Dbox
|
|
37
38
|
|
38
39
|
class Operation
|
39
40
|
include Loggable
|
41
|
+
include Utils
|
40
42
|
|
41
43
|
attr_reader :database
|
42
44
|
|
@@ -65,38 +67,6 @@ module Dbox
|
|
65
67
|
metadata[:remote_path]
|
66
68
|
end
|
67
69
|
|
68
|
-
def local_to_relative_path(path)
|
69
|
-
if path.include?(local_path)
|
70
|
-
path.sub(local_path, "").sub(/^\//, "")
|
71
|
-
else
|
72
|
-
raise BadPath, "Not a local path: #{path}"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def remote_to_relative_path(path)
|
77
|
-
if path.include?(remote_path)
|
78
|
-
path.sub(remote_path, "").sub(/^\//, "")
|
79
|
-
else
|
80
|
-
raise BadPath, "Not a remote path: #{path}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def relative_to_local_path(path)
|
85
|
-
if path && path.length > 0
|
86
|
-
File.join(local_path, path)
|
87
|
-
else
|
88
|
-
local_path
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def relative_to_remote_path(path)
|
93
|
-
if path && path.length > 0
|
94
|
-
File.join(remote_path, path)
|
95
|
-
else
|
96
|
-
remote_path
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
70
|
def remove_dotfiles(contents)
|
101
71
|
contents.reject {|c| File.basename(c[:path]).start_with?(".") }
|
102
72
|
end
|
@@ -116,29 +86,11 @@ module Dbox
|
|
116
86
|
@_ids[path] ||= database.find_by_path(path)[:id]
|
117
87
|
end
|
118
88
|
|
119
|
-
def time_to_s(t)
|
120
|
-
case t
|
121
|
-
when Time
|
122
|
-
# matches dropbox time format
|
123
|
-
t.utc.strftime("%a, %d %b %Y %H:%M:%S +0000")
|
124
|
-
when String
|
125
|
-
t
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def parse_time(t)
|
130
|
-
case t
|
131
|
-
when Time
|
132
|
-
t
|
133
|
-
when String
|
134
|
-
Time.parse(t)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
89
|
def saving_timestamp(path)
|
139
90
|
mtime = File.mtime(path)
|
140
|
-
yield
|
91
|
+
res = yield
|
141
92
|
File.utime(Time.now, mtime, path)
|
93
|
+
res
|
142
94
|
end
|
143
95
|
|
144
96
|
def saving_parent_timestamp(entry, &proc)
|
@@ -152,7 +104,7 @@ module Dbox
|
|
152
104
|
end
|
153
105
|
|
154
106
|
def gather_remote_info(entry)
|
155
|
-
res = api.metadata(relative_to_remote_path(entry[:path]), entry[:
|
107
|
+
res = api.metadata(relative_to_remote_path(entry[:path]), entry[:remote_hash])
|
156
108
|
case res
|
157
109
|
when Hash
|
158
110
|
out = process_basic_remote_props(res)
|
@@ -175,11 +127,12 @@ module Dbox
|
|
175
127
|
|
176
128
|
def process_basic_remote_props(res)
|
177
129
|
out = {}
|
178
|
-
out[:path]
|
179
|
-
out[:modified]
|
180
|
-
out[:is_dir]
|
181
|
-
out[:
|
182
|
-
out[:revision]
|
130
|
+
out[:path] = remote_to_relative_path(res[:path])
|
131
|
+
out[:modified] = parse_time(res[:modified])
|
132
|
+
out[:is_dir] = res[:is_dir]
|
133
|
+
out[:remote_hash] = res[:hash] if res[:hash]
|
134
|
+
out[:revision] = res[:rev] if res[:rev]
|
135
|
+
out[:size] = res[:bytes] if res[:bytes]
|
183
136
|
out
|
184
137
|
end
|
185
138
|
|
@@ -228,14 +181,19 @@ module Dbox
|
|
228
181
|
# directory creation cannot go in a thread, since later
|
229
182
|
# operations might depend on the directory being there
|
230
183
|
create_dir(c)
|
231
|
-
database.add_entry(c[:path], true, c[:parent_id], c[:modified], c[:revision], c[:
|
184
|
+
database.add_entry(c[:path], true, c[:parent_id], c[:modified], c[:revision], c[:remote_hash], nil)
|
232
185
|
changelist[:created] << c[:path]
|
233
186
|
else
|
234
187
|
ptasks.add do
|
235
188
|
begin
|
236
|
-
create_file(c)
|
237
|
-
|
189
|
+
res = create_file(c)
|
190
|
+
local_hash = calculate_hash(relative_to_local_path(c[:path]))
|
191
|
+
database.add_entry(c[:path], false, c[:parent_id], c[:modified], c[:revision], c[:remote_hash], local_hash)
|
238
192
|
changelist[:created] << c[:path]
|
193
|
+
if res.kind_of?(Array) && res[0] == :conflict
|
194
|
+
changelist[:conflicts] ||= []
|
195
|
+
changelist[:conflicts] << res[1]
|
196
|
+
end
|
239
197
|
rescue Dbox::ServerError => e
|
240
198
|
log.error "Error while downloading #{c[:path]}: #{e.inspect}"
|
241
199
|
parent_ids_of_failed_entries << c[:parent_id]
|
@@ -246,14 +204,19 @@ module Dbox
|
|
246
204
|
when :update
|
247
205
|
if c[:is_dir]
|
248
206
|
update_dir(c)
|
249
|
-
database.update_entry_by_path(c[:path], :modified => c[:modified], :revision => c[:revision], :
|
207
|
+
database.update_entry_by_path(c[:path], :modified => c[:modified], :revision => c[:revision], :remote_hash => c[:remote_hash])
|
250
208
|
changelist[:updated] << c[:path]
|
251
209
|
else
|
252
210
|
ptasks.add do
|
253
211
|
begin
|
254
|
-
update_file(c)
|
255
|
-
|
212
|
+
res = update_file(c)
|
213
|
+
local_hash = calculate_hash(relative_to_local_path(c[:path]))
|
214
|
+
database.update_entry_by_path(c[:path], :modified => c[:modified], :revision => c[:revision], :remote_hash => c[:remote_hash], :local_hash => local_hash)
|
256
215
|
changelist[:updated] << c[:path]
|
216
|
+
if res.kind_of?(Array) && res[0] == :conflict
|
217
|
+
changelist[:conflicts] ||= []
|
218
|
+
changelist[:conflicts] << res[1]
|
219
|
+
end
|
257
220
|
rescue Dbox::ServerError => e
|
258
221
|
log.error "Error while downloading #{c[:path]}: #{e.inspect}"
|
259
222
|
parent_ids_of_failed_entries << c[:parent_id]
|
@@ -276,11 +239,11 @@ module Dbox
|
|
276
239
|
# clear hashes on any dirs with children that failed so that
|
277
240
|
# they are processed again on next pull
|
278
241
|
parent_ids_of_failed_entries.uniq.each do |id|
|
279
|
-
database.update_entry_by_id(id, :
|
242
|
+
database.update_entry_by_id(id, :remote_hash => nil)
|
280
243
|
end
|
281
244
|
|
282
245
|
# sort & return output
|
283
|
-
changelist.keys.each {|k| changelist[k].sort! }
|
246
|
+
changelist.keys.each {|k| k == :conflicts ? changelist[k].sort! {|c1, c2| c1[:original] <=> c2[:original] } : changelist[k].sort! }
|
284
247
|
changelist
|
285
248
|
end
|
286
249
|
|
@@ -317,7 +280,7 @@ module Dbox
|
|
317
280
|
c[:modified] = parse_time(c[:modified])
|
318
281
|
if c[:is_dir]
|
319
282
|
# queue dir for later
|
320
|
-
c[:
|
283
|
+
c[:remote_hash] = entry[:remote_hash]
|
321
284
|
recur_dirs << [:update, c]
|
322
285
|
else
|
323
286
|
# update iff modified
|
@@ -355,9 +318,9 @@ module Dbox
|
|
355
318
|
|
356
319
|
def modified?(entry, res)
|
357
320
|
out = (entry[:revision] != res[:revision]) ||
|
358
|
-
(
|
359
|
-
out ||= (entry[:
|
360
|
-
log.debug "#{entry[:path]} modified? r#{entry[:revision]} vs. r#{res[:revision]}, h#{entry[:
|
321
|
+
!times_equal?(entry[:modified], res[:modified])
|
322
|
+
out ||= (entry[:remote_hash] != res[:remote_hash]) if res.has_key?(:remote_hash)
|
323
|
+
log.debug "#{entry[:path]} modified? r#{entry[:revision]} vs. r#{res[:revision]}, h#{entry[:remote_hash]} vs. h#{res[:remote_hash]}, t#{time_to_s(entry[:modified])} vs. t#{time_to_s(res[:modified])} => #{out}"
|
361
324
|
out
|
362
325
|
end
|
363
326
|
|
@@ -404,15 +367,43 @@ module Dbox
|
|
404
367
|
local_path = relative_to_local_path(file[:path])
|
405
368
|
remote_path = relative_to_remote_path(file[:path])
|
406
369
|
|
407
|
-
#
|
370
|
+
# check to ensure we aren't overwriting an untracked file or a
|
371
|
+
# file with local modifications
|
372
|
+
clobbering = false
|
373
|
+
if entry = database.find_by_path(file[:path])
|
374
|
+
clobbering = calculate_hash(local_path) != entry[:local_hash]
|
375
|
+
else
|
376
|
+
clobbering = File.exists?(local_path)
|
377
|
+
end
|
378
|
+
|
379
|
+
# stream files larger than the minimum
|
380
|
+
stream = file[:size] && file[:size] > MIN_BYTES_TO_STREAM_DOWNLOAD
|
381
|
+
|
382
|
+
# download to temp file
|
408
383
|
tmp = generate_tmpfilename(file[:path])
|
409
384
|
File.open(tmp, "w") do |f|
|
410
|
-
api.get_file(remote_path, f)
|
385
|
+
api.get_file(remote_path, f, stream)
|
386
|
+
end
|
387
|
+
|
388
|
+
# rename old file if clobbering
|
389
|
+
if clobbering && File.exists?(local_path)
|
390
|
+
backup_path = find_nonconflicting_path(local_path)
|
391
|
+
FileUtils.mv(local_path, backup_path)
|
392
|
+
backup_relpath = local_to_relative_path(backup_path)
|
393
|
+
log.warn "#{file[:path]} had a conflict and the existing copy was renamed to #{backup_relpath} locally"
|
411
394
|
end
|
412
|
-
FileUtils.mv(tmp, local_path)
|
413
395
|
|
396
|
+
# atomic move over to the real file, and update the timestamp
|
397
|
+
FileUtils.mv(tmp, local_path)
|
414
398
|
update_file_timestamp(file)
|
399
|
+
|
400
|
+
if backup_relpath
|
401
|
+
[:conflict, { :original => file[:path], :renamed => backup_relpath }]
|
402
|
+
else
|
403
|
+
true
|
404
|
+
end
|
415
405
|
end
|
406
|
+
|
416
407
|
end
|
417
408
|
|
418
409
|
class Push < Operation
|
@@ -445,17 +436,24 @@ module Dbox
|
|
445
436
|
# directory creation cannot go in a thread, since later
|
446
437
|
# operations might depend on the directory being there
|
447
438
|
create_dir(c)
|
448
|
-
database.add_entry(c[:path], true, c[:parent_id], nil, nil, nil)
|
439
|
+
database.add_entry(c[:path], true, c[:parent_id], nil, nil, nil, nil)
|
449
440
|
force_metadata_update_from_server(c)
|
450
441
|
changelist[:created] << c[:path]
|
451
442
|
else
|
452
443
|
# spin up a thread to upload the file
|
453
444
|
ptasks.add do
|
454
445
|
begin
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
446
|
+
local_hash = calculate_hash(relative_to_local_path(c[:path]))
|
447
|
+
res = upload_file(c)
|
448
|
+
database.add_entry(c[:path], false, c[:parent_id], nil, nil, nil, local_hash)
|
449
|
+
if c[:path] == res[:path]
|
450
|
+
force_metadata_update_from_server(c)
|
451
|
+
changelist[:created] << c[:path]
|
452
|
+
else
|
453
|
+
log.warn "#{c[:path]} had a conflict and was renamed to #{res[:path]} on the server"
|
454
|
+
changelist[:conflicts] ||= []
|
455
|
+
changelist[:conflicts] << { :original => c[:path], :renamed => res[:path] }
|
456
|
+
end
|
459
457
|
rescue Dbox::ServerError => e
|
460
458
|
log.error "Error while uploading #{c[:path]}: #{e.inspect}"
|
461
459
|
changelist[:failed] << { :operation => :create, :path => c[:path], :error => e }
|
@@ -474,9 +472,17 @@ module Dbox
|
|
474
472
|
# spin up a thread to upload the file
|
475
473
|
ptasks.add do
|
476
474
|
begin
|
477
|
-
|
478
|
-
|
479
|
-
|
475
|
+
local_hash = calculate_hash(relative_to_local_path(c[:path]))
|
476
|
+
res = upload_file(c)
|
477
|
+
database.update_entry_by_path(c[:path], :local_hash => local_hash)
|
478
|
+
if c[:path] == res[:path]
|
479
|
+
force_metadata_update_from_server(c)
|
480
|
+
changelist[:updated] << c[:path]
|
481
|
+
else
|
482
|
+
log.warn "#{c[:path]} had a conflict and was renamed to #{res[:path]} on the server"
|
483
|
+
changelist[:conflicts] ||= []
|
484
|
+
changelist[:conflicts] << { :original => c[:path], :renamed => res[:path] }
|
485
|
+
end
|
480
486
|
rescue Dbox::ServerError => e
|
481
487
|
log.error "Error while uploading #{c[:path]}: #{e.inspect}"
|
482
488
|
changelist[:failed] << { :operation => :update, :path => c[:path], :error => e }
|
@@ -526,7 +532,7 @@ module Dbox
|
|
526
532
|
child_paths = list_contents(dir).sort
|
527
533
|
|
528
534
|
child_paths.each do |p|
|
529
|
-
c = { :path => p, :modified => mtime(p), :is_dir => is_dir(p), :parent_path => dir[:path] }
|
535
|
+
c = { :path => p, :modified => mtime(p), :is_dir => is_dir(p), :parent_path => dir[:path], :local_hash => calculate_hash(relative_to_local_path(p)) }
|
530
536
|
if entry = existing_entries[p]
|
531
537
|
c[:id] = entry[:id]
|
532
538
|
recur_dirs << c if c[:is_dir] # queue dir for later
|
@@ -560,8 +566,16 @@ module Dbox
|
|
560
566
|
end
|
561
567
|
|
562
568
|
def modified?(entry, res)
|
563
|
-
out =
|
564
|
-
|
569
|
+
out = true
|
570
|
+
if entry[:is_dir]
|
571
|
+
out = !times_equal?(entry[:modified], res[:modified])
|
572
|
+
log.debug "#{entry[:path]} modified? t#{time_to_s(entry[:modified])} vs. t#{time_to_s(res[:modified])} => #{out}"
|
573
|
+
else
|
574
|
+
eh = entry[:local_hash]
|
575
|
+
rh = res[:local_hash]
|
576
|
+
out = !(eh && rh && eh == rh)
|
577
|
+
log.debug "#{entry[:path]} modified? #{eh} vs. #{rh} => #{out}"
|
578
|
+
end
|
565
579
|
out
|
566
580
|
end
|
567
581
|
|
@@ -591,14 +605,17 @@ module Dbox
|
|
591
605
|
local_path = relative_to_local_path(file[:path])
|
592
606
|
remote_path = relative_to_remote_path(file[:path])
|
593
607
|
File.open(local_path) do |f|
|
594
|
-
|
608
|
+
db_entry = database.find_by_path(file[:path])
|
609
|
+
last_revision = db_entry ? db_entry[:revision] : nil
|
610
|
+
res = api.put_file(remote_path, f, last_revision)
|
611
|
+
process_basic_remote_props(res)
|
595
612
|
end
|
596
613
|
end
|
597
614
|
|
598
615
|
def force_metadata_update_from_server(entry)
|
599
616
|
res = gather_remote_info(entry)
|
600
617
|
unless res == :not_modified
|
601
|
-
database.update_entry_by_path(entry[:path], :modified => res[:modified], :revision => res[:revision], :
|
618
|
+
database.update_entry_by_path(entry[:path], :modified => res[:modified], :revision => res[:revision], :remote_hash => res[:remote_hash])
|
602
619
|
end
|
603
620
|
update_file_timestamp(database.find_by_path(entry[:path]))
|
604
621
|
end
|