dbox 0.5.3 → 0.6.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 +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
|