bdsync 0.1.1 → 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +36 -0
- data/README.md +1 -3
- data/bsync.gemspec +2 -0
- data/lib/bsync.rb +4 -2
- data/lib/bsync/core.rb +429 -0
- data/lib/bsync/lfs.rb +93 -0
- data/lib/bsync/sftp.rb +85 -0
- data/lib/bsync/utils.rb +34 -0
- data/lib/bsync/version.rb +1 -1
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 692f6cf3fe115aa7894fb65d6549438dede5ec6737290da158a10290c3a5f02a
|
4
|
+
data.tar.gz: 5a3ca8c23c7f7e35f18fee6789f1e888d94f1cce86bf9adfafc39ab14fa2e6a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a24223fd0e493e73af666a2f6427f4e8f822383ca3b30c25f54406fc8e265a1db8bfc6c7ccb0469e82599bc111a2b6f1b5fed47fc5f549398c2ad9a1aacbfe6a
|
7
|
+
data.tar.gz: e4e999f5a136ad3221ea3c4cd096c0adb88fbcbe7bb2fa905cdc634af13abad1c52d0536c5a331921b109cbbee87adaafcb90181905671c77edd375b796d4191
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bdsync (0.1.1)
|
5
|
+
colorize (~> 0.8)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
colorize (0.8.1)
|
11
|
+
diff-lcs (1.3)
|
12
|
+
rake (12.3.3)
|
13
|
+
rspec (3.9.0)
|
14
|
+
rspec-core (~> 3.9.0)
|
15
|
+
rspec-expectations (~> 3.9.0)
|
16
|
+
rspec-mocks (~> 3.9.0)
|
17
|
+
rspec-core (3.9.0)
|
18
|
+
rspec-support (~> 3.9.0)
|
19
|
+
rspec-expectations (3.9.0)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.9.0)
|
22
|
+
rspec-mocks (3.9.0)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.9.0)
|
25
|
+
rspec-support (3.9.0)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
bdsync!
|
32
|
+
rake (~> 12.0)
|
33
|
+
rspec (~> 3.0)
|
34
|
+
|
35
|
+
BUNDLED WITH
|
36
|
+
2.1.2
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Bsync
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Bidirectional Synchronization tool for sftp or local file system
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
data/bsync.gemspec
CHANGED
data/lib/bsync.rb
CHANGED
data/lib/bsync/core.rb
ADDED
@@ -0,0 +1,429 @@
|
|
1
|
+
require "bsync/utils"
|
2
|
+
require "colorize"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module Bsync
|
6
|
+
class Core
|
7
|
+
attr_reader :data_path
|
8
|
+
|
9
|
+
def initialize params, sync_type
|
10
|
+
raise "local path not specified" if !params["local_root_path"]
|
11
|
+
raise "remote path not specified" if !params["remote_root_path"]
|
12
|
+
|
13
|
+
@local_root_path = params["local_root_path"]
|
14
|
+
@remote_root_path = params["remote_root_path"]
|
15
|
+
@infinite_loop = params["infinite_loop"]
|
16
|
+
@data_path = "#{Dir.home}/.bsync/#{sync_type}_#{Utils.md5 params.to_s}.yaml"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.options
|
20
|
+
["local_root_path:", "remote_root_path:", "infinite_loop"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def synchronize
|
24
|
+
# Run only one instance of a Ruby program at the same time - self locking
|
25
|
+
# SEE: https://code-maven.com/run-only-one-instance-of-a-script
|
26
|
+
exit if !Utils.try_lock
|
27
|
+
|
28
|
+
loop {
|
29
|
+
@old_data = load_data
|
30
|
+
@data = {}
|
31
|
+
|
32
|
+
start_session {
|
33
|
+
remote_ensure_dir @remote_root_path
|
34
|
+
local_ensure_dir @local_root_path
|
35
|
+
|
36
|
+
puts "\n==== traverse_remote_path ===="
|
37
|
+
traverse_remote_path @remote_root_path
|
38
|
+
|
39
|
+
# merge @data to @old_data, and clear @data
|
40
|
+
@old_data.merge! @data
|
41
|
+
@data = {}
|
42
|
+
|
43
|
+
puts "\n==== traverse_local_path ===="
|
44
|
+
traverse_local_path @local_root_path
|
45
|
+
}
|
46
|
+
|
47
|
+
save_data @data
|
48
|
+
|
49
|
+
break if !@infinite_loop
|
50
|
+
|
51
|
+
sleep 1
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def start_session &block
|
56
|
+
fail NotImplementedError, "A subclass class must be able to #{__method__}!"
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_data
|
60
|
+
puts "\nload #{@data_path}"
|
61
|
+
YAML.load_file @data_path
|
62
|
+
rescue Errno::ENOENT
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
|
66
|
+
def save_data data
|
67
|
+
local_ensure_parent @data_path
|
68
|
+
File.write @data_path, data.to_yaml
|
69
|
+
puts "\nsaved to #{@data_path}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def traverse_remote_path remote_path
|
73
|
+
next_level_dirs = []
|
74
|
+
|
75
|
+
remote_dir_foreach(remote_path) { |entry|
|
76
|
+
next if [".", "..", ".conflict"].include? entry.name
|
77
|
+
|
78
|
+
path = "#{remote_path}/#{entry.name}"
|
79
|
+
remote = entry.attributes
|
80
|
+
|
81
|
+
if remote.directory?
|
82
|
+
next_level_dirs << [path, remote]
|
83
|
+
else
|
84
|
+
handle_remote_entry remote, path, :file
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
next_level_dirs.sort.each { |path, remote|
|
89
|
+
handle_remote_entry remote, path, :directory
|
90
|
+
traverse_remote_path path
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def traverse_local_path local_path
|
95
|
+
next_level_dirs = []
|
96
|
+
|
97
|
+
Dir.foreach(local_path) { |entry_name|
|
98
|
+
next if [".", "..", ".conflict"].include? entry_name
|
99
|
+
|
100
|
+
path = "#{local_path}/#{entry_name}"
|
101
|
+
|
102
|
+
if File.directory? path
|
103
|
+
next_level_dirs << path
|
104
|
+
else
|
105
|
+
handle_local_entry path, :file
|
106
|
+
end
|
107
|
+
}
|
108
|
+
|
109
|
+
next_level_dirs.sort.each { |path|
|
110
|
+
handle_local_entry path, :directory
|
111
|
+
traverse_local_path path
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def handle_remote_entry remote, path, type
|
116
|
+
puts "#{'%9s' % type}: #{path}".green
|
117
|
+
|
118
|
+
# NOTE: force_encoding to output readable japanese text
|
119
|
+
relative_path = path.sub(@remote_root_path + "/", "").force_encoding("UTF-8")
|
120
|
+
|
121
|
+
local_path = "#{@local_root_path}/#{relative_path}"
|
122
|
+
remote_path = "#{@remote_root_path}/#{relative_path}"
|
123
|
+
|
124
|
+
old = @old_data[relative_path]
|
125
|
+
|
126
|
+
if !old # no old sync record
|
127
|
+
do_first_time_sync_from_remote relative_path, local_path, remote_path, remote, type
|
128
|
+
else # old sync record found
|
129
|
+
do_sync_from_remote relative_path, local_path, remote_path, remote, type, old
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def handle_local_entry path, type
|
134
|
+
puts "#{'%9s' % type}: #{path}".green
|
135
|
+
|
136
|
+
# NOTE: force_encoding to output readable japanese text
|
137
|
+
relative_path = path.sub(@local_root_path + "/", "").force_encoding("UTF-8")
|
138
|
+
|
139
|
+
local_path = "#{@local_root_path}/#{relative_path}"
|
140
|
+
remote_path = "#{@remote_root_path}/#{relative_path}"
|
141
|
+
|
142
|
+
remote = remote_get_object remote_path
|
143
|
+
|
144
|
+
old = @old_data[relative_path]
|
145
|
+
|
146
|
+
if !old # no old sync record
|
147
|
+
do_first_time_sync_from_local relative_path, local_path, remote_path, remote, type
|
148
|
+
else # old sync record found
|
149
|
+
do_sync_from_local relative_path, local_path, remote_path, remote, type, old
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def do_first_time_sync_from_remote relative_path, local_path, remote_path, remote, type
|
154
|
+
case type
|
155
|
+
when :file
|
156
|
+
if !File.exist? local_path
|
157
|
+
download_file local_path, remote_path
|
158
|
+
update_file_data relative_path, local_path, remote.mtime
|
159
|
+
elsif File.directory? local_path
|
160
|
+
handle_local_conflict local_path
|
161
|
+
download_file local_path, remote_path
|
162
|
+
update_file_data relative_path, local_path, remote.mtime
|
163
|
+
else
|
164
|
+
handle_local_conflict local_path
|
165
|
+
download_file local_path, remote_path
|
166
|
+
update_file_data relative_path, local_path, remote.mtime
|
167
|
+
end
|
168
|
+
when :directory
|
169
|
+
if !File.exist? local_path
|
170
|
+
local_mkdir local_path
|
171
|
+
update_directory_data relative_path, local_path, remote.mtime
|
172
|
+
elsif File.directory? local_path
|
173
|
+
update_directory_data relative_path, local_path, remote.mtime
|
174
|
+
else
|
175
|
+
handle_local_conflict local_path
|
176
|
+
local_mkdir local_path
|
177
|
+
update_directory_data relative_path, local_path, remote.mtime
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def do_first_time_sync_from_local relative_path, local_path, remote_path, remote, type
|
183
|
+
case type
|
184
|
+
when :file
|
185
|
+
if !remote
|
186
|
+
remote = upload_file local_path, remote_path
|
187
|
+
update_file_data relative_path, local_path, remote.mtime
|
188
|
+
elsif remote.directory?
|
189
|
+
handle_remote_conflict remote_path
|
190
|
+
remote = upload_file local_path, remote_path
|
191
|
+
update_file_data relative_path, local_path, remote.mtime
|
192
|
+
else
|
193
|
+
handle_remote_conflict remote_path
|
194
|
+
remote = upload_file local_path, remote_path
|
195
|
+
update_file_data relative_path, local_path, remote.mtime
|
196
|
+
end
|
197
|
+
when :directory
|
198
|
+
if !remote
|
199
|
+
remote = remote_mkdir remote_path
|
200
|
+
update_directory_data relative_path, local_path, remote.mtime
|
201
|
+
elsif remote.directory?
|
202
|
+
update_directory_data relative_path, local_path, remote.mtime
|
203
|
+
else
|
204
|
+
handle_remote_conflict remote_path
|
205
|
+
remote = remote_mkdir remote_path
|
206
|
+
update_directory_data relative_path, local_path, remote.mtime
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def do_sync_from_remote relative_path, local_path, remote_path, remote, type, old
|
212
|
+
remote_changed = (remote.mtime - old[:remote_mtime]) != 0
|
213
|
+
|
214
|
+
case type
|
215
|
+
when :file
|
216
|
+
if !File.exist? local_path
|
217
|
+
if !remote_changed
|
218
|
+
remote_remove_file remote_path
|
219
|
+
else
|
220
|
+
handle_remote_conflict remote_path
|
221
|
+
end
|
222
|
+
elsif File.directory? local_path
|
223
|
+
if !remote_changed
|
224
|
+
remote_remove_file remote_path
|
225
|
+
remote = remote_mkdir remote_path
|
226
|
+
update_directory_data relative_path, local_path, remote.mtime
|
227
|
+
else
|
228
|
+
if File.mtime(local_path).to_i > remote.mtime
|
229
|
+
handle_remote_conflict remote_path
|
230
|
+
remote = remote_mkdir remote_path
|
231
|
+
update_directory_data relative_path, local_path, remote.mtime
|
232
|
+
else
|
233
|
+
handle_local_conflict local_path
|
234
|
+
download_file local_path, remote_path
|
235
|
+
update_file_data relative_path, local_path, remote.mtime
|
236
|
+
end
|
237
|
+
end
|
238
|
+
else
|
239
|
+
local_changed = (File.mtime(local_path).to_i - old[:local_mtime]) != 0
|
240
|
+
|
241
|
+
if !local_changed && !remote_changed
|
242
|
+
@data[relative_path] = old
|
243
|
+
elsif local_changed && !remote_changed
|
244
|
+
remote = upload_file local_path, remote_path
|
245
|
+
update_file_data relative_path, local_path, remote.mtime
|
246
|
+
elsif !local_changed && remote_changed
|
247
|
+
download_file local_path, remote_path
|
248
|
+
update_file_data relative_path, local_path, remote.mtime
|
249
|
+
else
|
250
|
+
if File.mtime(local_path).to_i > remote.mtime
|
251
|
+
handle_remote_conflict remote_path
|
252
|
+
remote = upload_file local_path, remote_path
|
253
|
+
update_file_data relative_path, local_path, remote.mtime
|
254
|
+
else
|
255
|
+
handle_local_conflict local_path
|
256
|
+
download_file local_path, remote_path
|
257
|
+
update_file_data relative_path, local_path, remote.mtime
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
when :directory
|
262
|
+
if !File.exist? local_path
|
263
|
+
if !remote_changed
|
264
|
+
remote_remove_dir remote_path
|
265
|
+
else
|
266
|
+
handle_remote_conflict remote_path
|
267
|
+
end
|
268
|
+
elsif File.directory? local_path
|
269
|
+
update_directory_data relative_path, local_path, remote.mtime
|
270
|
+
else
|
271
|
+
if !remote_changed
|
272
|
+
remote_remove_dir remote_path
|
273
|
+
remote = upload_file local_path, remote_path
|
274
|
+
update_file_data relative_path, local_path, remote.mtime
|
275
|
+
else
|
276
|
+
if File.mtime(local_path).to_i > remote.mtime
|
277
|
+
handle_remote_conflict remote_path
|
278
|
+
remote = upload_file local_path, remote_path
|
279
|
+
update_file_data relative_path, local_path, remote.mtime
|
280
|
+
else
|
281
|
+
handle_local_conflict local_path
|
282
|
+
local_mkdir local_path
|
283
|
+
update_directory_data relative_path, local_path, remote.mtime
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def do_sync_from_local relative_path, local_path, remote_path, remote, type, old
|
291
|
+
local_changed = (File.mtime(local_path).to_i - old[:local_mtime]) != 0
|
292
|
+
|
293
|
+
case type
|
294
|
+
when :file
|
295
|
+
if !remote
|
296
|
+
if !local_changed
|
297
|
+
local_remove_file local_path
|
298
|
+
else
|
299
|
+
handle_local_conflict local_path
|
300
|
+
end
|
301
|
+
elsif remote.directory?
|
302
|
+
if !local_changed
|
303
|
+
local_remove_file local_path
|
304
|
+
local_mkdir local_path
|
305
|
+
update_directory_data relative_path, local_path, remote.mtime
|
306
|
+
else
|
307
|
+
if File.mtime(local_path).to_i > remote.mtime
|
308
|
+
handle_remote_conflict remote_path
|
309
|
+
remote = upload_file local_path, remote_path
|
310
|
+
update_file_data relative_path, local_path, remote.mtime
|
311
|
+
else
|
312
|
+
handle_local_conflict local_path
|
313
|
+
local_mkdir local_path
|
314
|
+
update_directory_data relative_path, local_path, remote.mtime
|
315
|
+
end
|
316
|
+
end
|
317
|
+
else
|
318
|
+
remote_changed = (remote.mtime - old[:remote_mtime]) != 0
|
319
|
+
|
320
|
+
if !local_changed && !remote_changed
|
321
|
+
@data[relative_path] = old
|
322
|
+
elsif local_changed && !remote_changed
|
323
|
+
remote = upload_file local_path, remote_path
|
324
|
+
update_file_data relative_path, local_path, remote.mtime
|
325
|
+
elsif !local_changed && remote_changed
|
326
|
+
download_file local_path, remote_path
|
327
|
+
update_file_data relative_path, local_path, remote.mtime
|
328
|
+
else
|
329
|
+
if File.mtime(local_path).to_i > remote.mtime
|
330
|
+
handle_remote_conflict remote_path
|
331
|
+
remote = upload_file local_path, remote_path
|
332
|
+
update_file_data relative_path, local_path, remote.mtime
|
333
|
+
else
|
334
|
+
handle_local_conflict local_path
|
335
|
+
download_file local_path, remote_path
|
336
|
+
update_file_data relative_path, local_path, remote.mtime
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
when :directory
|
341
|
+
if !remote
|
342
|
+
if !local_changed
|
343
|
+
local_remove_directory local_path
|
344
|
+
else
|
345
|
+
handle_local_conflict local_path
|
346
|
+
end
|
347
|
+
elsif remote.directory?
|
348
|
+
update_directory_data relative_path, local_path, remote.mtime
|
349
|
+
else
|
350
|
+
if !local_changed
|
351
|
+
local_remove_directory local_path
|
352
|
+
download_file local_path, remote_path
|
353
|
+
update_file_data relative_path, local_path, remote.mtime
|
354
|
+
else
|
355
|
+
if File.mtime(local_path).to_i > remote.mtime
|
356
|
+
handle_remote_conflict remote_path
|
357
|
+
remote = remote_mkdir remote_path
|
358
|
+
update_directory_data relative_path, local_path, remote.mtime
|
359
|
+
else
|
360
|
+
handle_local_conflict local_path
|
361
|
+
download_file local_path, remote_path
|
362
|
+
update_file_data relative_path, local_path, remote.mtime
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def update_file_data relative_path, local_path, remote_mtime
|
370
|
+
@data[relative_path] = {
|
371
|
+
type: :file,
|
372
|
+
remote_mtime: remote_mtime,
|
373
|
+
local_mtime: File.mtime(local_path).to_i
|
374
|
+
}
|
375
|
+
end
|
376
|
+
|
377
|
+
def update_directory_data relative_path, local_path, remote_mtime
|
378
|
+
@data[relative_path] = {
|
379
|
+
type: :directory,
|
380
|
+
remote_mtime: remote_mtime,
|
381
|
+
local_mtime: File.mtime(local_path).to_i
|
382
|
+
}
|
383
|
+
end
|
384
|
+
|
385
|
+
def handle_local_conflict local_path
|
386
|
+
ts = Utils.timestamp
|
387
|
+
local_conflict_path = local_path.sub(@local_root_path, "#{@local_root_path}/.conflict") + "." + ts
|
388
|
+
|
389
|
+
local_ensure_parent local_conflict_path
|
390
|
+
local_rename local_path, local_conflict_path
|
391
|
+
end
|
392
|
+
|
393
|
+
def handle_remote_conflict remote_path
|
394
|
+
ts = Utils.timestamp
|
395
|
+
remote_conflict_path = remote_path.sub(@remote_root_path, "#{@remote_root_path}/.conflict") + "." + ts
|
396
|
+
|
397
|
+
remote_ensure_parent remote_conflict_path
|
398
|
+
remote_rename remote_path, remote_conflict_path
|
399
|
+
end
|
400
|
+
|
401
|
+
def local_mkdir local_path
|
402
|
+
puts "#{Utils.caller_info 1} mkdir_p #{local_path}".white
|
403
|
+
FileUtils.mkdir_p local_path
|
404
|
+
end
|
405
|
+
|
406
|
+
def local_remove_file local_path
|
407
|
+
puts "#{Utils.caller_info 1} rm #{local_path}".white
|
408
|
+
FileUtils.rm local_path
|
409
|
+
end
|
410
|
+
|
411
|
+
def local_remove_directory local_path
|
412
|
+
puts "#{Utils.caller_info 1} rm_rf #{local_path}".white
|
413
|
+
FileUtils.rm_rf local_path
|
414
|
+
end
|
415
|
+
|
416
|
+
def local_rename local_path, new_local_path
|
417
|
+
puts "#{Utils.caller_info 1} mv #{local_path} #{new_local_path}".yellow
|
418
|
+
FileUtils.mv local_path, new_local_path
|
419
|
+
end
|
420
|
+
|
421
|
+
def local_ensure_dir path
|
422
|
+
FileUtils.mkdir_p path if !File.directory? path
|
423
|
+
end
|
424
|
+
|
425
|
+
def local_ensure_parent path
|
426
|
+
local_ensure_dir File.dirname path
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
data/lib/bsync/lfs.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "bsync/core"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
module Bsync
|
5
|
+
class Lfs < Core
|
6
|
+
def initialize params
|
7
|
+
super params, "lfs"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.options
|
11
|
+
Core.options
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_session &block
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
|
18
|
+
# yield object like this
|
19
|
+
# {
|
20
|
+
# name:
|
21
|
+
# attributes: {
|
22
|
+
# directory?:
|
23
|
+
# mtime:
|
24
|
+
# }
|
25
|
+
# }
|
26
|
+
def remote_dir_foreach remote_path
|
27
|
+
Dir.foreach(remote_path) { |filename|
|
28
|
+
file_path = "#{remote_path}/#{filename}"
|
29
|
+
|
30
|
+
yield OpenStruct.new name: filename, attributes: OpenStruct.new(
|
31
|
+
directory?: File.directory?(file_path),
|
32
|
+
mtime: File.mtime(file_path).to_i
|
33
|
+
)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def remote_get_object remote_path
|
38
|
+
stat = File.lstat remote_path
|
39
|
+
|
40
|
+
OpenStruct.new(
|
41
|
+
directory?: stat.directory?,
|
42
|
+
mtime: stat.mtime.to_i
|
43
|
+
)
|
44
|
+
rescue Errno::ENOENT
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def download_file local_path, remote_path
|
49
|
+
local_ensure_parent local_path
|
50
|
+
|
51
|
+
puts "#{Utils.caller_info 1} cp #{remote_path}, #{local_path}".white
|
52
|
+
FileUtils.cp remote_path, local_path
|
53
|
+
end
|
54
|
+
|
55
|
+
def upload_file local_path, remote_path
|
56
|
+
remote_ensure_parent remote_path
|
57
|
+
|
58
|
+
puts "#{Utils.caller_info 1} cp #{local_path}, #{remote_path}".white
|
59
|
+
FileUtils.cp local_path, remote_path
|
60
|
+
|
61
|
+
remote_get_object remote_path
|
62
|
+
end
|
63
|
+
|
64
|
+
def remote_mkdir remote_path
|
65
|
+
puts "#{Utils.caller_info 1} mkdir #{remote_path}".white
|
66
|
+
FileUtils.mkdir remote_path
|
67
|
+
remote_get_object remote_path
|
68
|
+
end
|
69
|
+
|
70
|
+
def remote_remove_file remote_path
|
71
|
+
puts "#{Utils.caller_info 1} rm #{remote_path}".white
|
72
|
+
FileUtils.rm remote_path
|
73
|
+
end
|
74
|
+
|
75
|
+
def remote_remove_dir remote_path
|
76
|
+
puts "#{Utils.caller_info 1} rm_rf #{remote_path}".white
|
77
|
+
FileUtils.rm_rf remote_path
|
78
|
+
end
|
79
|
+
|
80
|
+
def remote_rename remote_path, new_remote_path
|
81
|
+
puts "#{Utils.caller_info 1} mv #{remote_path} #{new_remote_path}".yellow
|
82
|
+
FileUtils.mv remote_path, new_remote_path
|
83
|
+
end
|
84
|
+
|
85
|
+
def remote_ensure_dir path
|
86
|
+
local_ensure_dir path
|
87
|
+
end
|
88
|
+
|
89
|
+
def remote_ensure_parent path
|
90
|
+
local_ensure_parent path
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/bsync/sftp.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require "bsync/core"
|
2
|
+
|
3
|
+
module Bsync
|
4
|
+
class Sftp < Core
|
5
|
+
def initialize params
|
6
|
+
@site = params["site"]
|
7
|
+
@user = params["user"]
|
8
|
+
|
9
|
+
super params, "sftp"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.options
|
13
|
+
Core.options + ["site:", "user:"]
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_session &block
|
17
|
+
Net::SFTP.start(@site, @user) { |sftp|
|
18
|
+
@sftp = sftp
|
19
|
+
yield
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def remote_dir_foreach remote_path
|
24
|
+
@sftp.dir.foreach(remote_path) { |entry|
|
25
|
+
yield entry
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def remote_get_object remote_path
|
30
|
+
@sftp.lstat! remote_path
|
31
|
+
rescue Net::SFTP::StatusException
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def download_file local_path, remote_path
|
36
|
+
local_ensure_parent local_path
|
37
|
+
|
38
|
+
puts "#{Utils.caller_info 1} sftp.download! #{remote_path}, #{local_path}".white
|
39
|
+
@sftp.download! remote_path, local_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def upload_file local_path, remote_path
|
43
|
+
remote_ensure_parent remote_path
|
44
|
+
|
45
|
+
puts "#{Utils.caller_info 1} sftp.upload! #{local_path}, #{remote_path}".white
|
46
|
+
@sftp.upload! local_path, remote_path
|
47
|
+
|
48
|
+
remote_get_object remote_path
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_mkdir remote_path
|
52
|
+
puts "#{Utils.caller_info 1} sftp.mkdir! #{remote_path}".white
|
53
|
+
@sftp.mkdir! remote_path
|
54
|
+
remote_get_object remote_path
|
55
|
+
end
|
56
|
+
|
57
|
+
def remote_remove_file remote_path
|
58
|
+
puts "#{Utils.caller_info 1} sftp.remove! #{remote_path}".white
|
59
|
+
@sftp.remove! remote_path
|
60
|
+
end
|
61
|
+
|
62
|
+
def remote_remove_dir remote_path
|
63
|
+
puts "#{Utils.caller_info 1} sftp.session.exec! rm -rf #{remote_path}".white
|
64
|
+
@sftp.session.exec! "rm -rf #{remote_path}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def remote_rename remote_path, new_remote_path
|
68
|
+
puts "#{Utils.caller_info 1} sftp.rename! #{remote_path} #{new_remote_path}".yellow
|
69
|
+
@sftp.rename! remote_path, new_remote_path
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote_ensure_dir path
|
73
|
+
begin
|
74
|
+
@sftp.lstat! path
|
75
|
+
rescue Net::SFTP::StatusException
|
76
|
+
remote_ensure_parent path
|
77
|
+
@sftp.mkdir! path
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def remote_ensure_parent path
|
82
|
+
remote_ensure_dir File.dirname path
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/bsync/utils.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
3
|
+
module Bsync
|
4
|
+
module Utils
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# puts Utils.timestamp
|
8
|
+
# > 2019-12-11.16-15-57
|
9
|
+
#
|
10
|
+
# puts Utils.timestamp[0...10].delete("-")
|
11
|
+
# > 20191211
|
12
|
+
#
|
13
|
+
def self.timestamp
|
14
|
+
ts = Time.now.to_s[0..18]
|
15
|
+
ts[10] = "."
|
16
|
+
ts[13] = ts[16] = "-"
|
17
|
+
|
18
|
+
ts
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.md5(s)
|
22
|
+
Digest::MD5.hexdigest(s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.caller_info level
|
26
|
+
info = caller[level].match(%r{([^/]+):(\d+):in `(.+)'})
|
27
|
+
"#{info.captures[0]}:#{info.captures[1]} - #{info.captures[2]}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.try_lock
|
31
|
+
File.open(__FILE__, 'r').flock(File::LOCK_EX | File::LOCK_NB)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/bsync/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bdsync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xia Xiongjun
|
@@ -9,7 +9,21 @@ autorequire:
|
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2019-12-26 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
13
27
|
description: Bidirectional Synchronization tool for sftp or local file system
|
14
28
|
email:
|
15
29
|
- xxjapp@gmail.com
|
@@ -22,6 +36,7 @@ files:
|
|
22
36
|
- ".travis.yml"
|
23
37
|
- CODE_OF_CONDUCT.md
|
24
38
|
- Gemfile
|
39
|
+
- Gemfile.lock
|
25
40
|
- LICENSE.txt
|
26
41
|
- README.md
|
27
42
|
- Rakefile
|
@@ -29,6 +44,10 @@ files:
|
|
29
44
|
- bin/setup
|
30
45
|
- bsync.gemspec
|
31
46
|
- lib/bsync.rb
|
47
|
+
- lib/bsync/core.rb
|
48
|
+
- lib/bsync/lfs.rb
|
49
|
+
- lib/bsync/sftp.rb
|
50
|
+
- lib/bsync/utils.rb
|
32
51
|
- lib/bsync/version.rb
|
33
52
|
homepage: https://github.com/xxjapp/bsync
|
34
53
|
licenses:
|