bitferry 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/lib/bitferry/cli.rb +13 -12
- data/lib/bitferry/fx.rb +84 -16
- data/lib/bitferry.rb +62 -11
- metadata +37 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f67881c016dae96456c1d0b1baee9b3949eb9bfd46aa5f96cf7232d219c1d7d8
|
4
|
+
data.tar.gz: a5613291cc0b050ce243c8cf4142d8020c363100b0655cf6fcb405c0b380daba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 670ea9837098777a1dfa56df30283f2d6fac41df303003a33cd1e80711d5c4967f09ab72ba2247dbdb00c57830f0310825a064397f5db463e0ee927de72ce6dc
|
7
|
+
data.tar.gz: 433fb3934ae559cf42eb0a20d051923c0dc5e93eed32f5249dc95a1dde1930d3982fb925efd64f03d418fa9fefa54a74959ec87118c3c26d05ba0927667ee0cf
|
data/CHANGES.md
CHANGED
data/lib/bitferry/cli.rb
CHANGED
@@ -110,6 +110,7 @@ def obtain_password
|
|
110
110
|
end
|
111
111
|
|
112
112
|
|
113
|
+
Bitferry.ui = :cli
|
113
114
|
Bitferry.log.level = Logger::DEBUG if $DEBUG
|
114
115
|
|
115
116
|
|
@@ -120,7 +121,7 @@ Clamp do
|
|
120
121
|
|
121
122
|
|
122
123
|
option '--version', :flag, 'Print version' do
|
123
|
-
puts Bitferry::VERSION
|
124
|
+
$stdout.puts Bitferry::VERSION
|
124
125
|
exit
|
125
126
|
end
|
126
127
|
|
@@ -144,26 +145,26 @@ Clamp do
|
|
144
145
|
def execute
|
145
146
|
Bitferry.restore
|
146
147
|
unless (xs = Bitferry::Volume.intact).empty?
|
147
|
-
puts '# Intact volumes'
|
148
|
-
puts
|
148
|
+
$stdout.puts '# Intact volumes'
|
149
|
+
$stdout.puts
|
149
150
|
xs.each do |volume|
|
150
|
-
puts " #{volume.tag} #{volume.root}"
|
151
|
+
$stdout.puts " #{volume.tag} #{volume.root}"
|
151
152
|
end
|
152
153
|
end
|
153
154
|
unless (xs = Bitferry::Task.intact).empty?
|
154
|
-
puts
|
155
|
-
puts '# Intact tasks'
|
156
|
-
puts
|
155
|
+
$stdout.puts
|
156
|
+
$stdout.puts '# Intact tasks'
|
157
|
+
$stdout.puts
|
157
158
|
xs.each do |task|
|
158
|
-
puts " #{task.tag} #{task.show_status}"
|
159
|
+
$stdout.puts " #{task.tag} #{task.show_status}"
|
159
160
|
end
|
160
161
|
end
|
161
162
|
if !(xs = Bitferry::Task.stale).empty? && Bitferry.verbosity == :verbose
|
162
|
-
puts
|
163
|
-
puts '# Stale tasks'
|
164
|
-
puts
|
163
|
+
$stdout.puts
|
164
|
+
$stdout.puts '# Stale tasks'
|
165
|
+
$stdout.puts
|
165
166
|
xs.each do |task|
|
166
|
-
puts " #{task.tag} #{task.show_status}"
|
167
|
+
$stdout.puts " #{task.tag} #{task.show_status}"
|
167
168
|
end
|
168
169
|
end
|
169
170
|
end
|
data/lib/bitferry/fx.rb
CHANGED
@@ -1,36 +1,73 @@
|
|
1
1
|
require 'fox16'
|
2
|
+
require 'stringio'
|
2
3
|
require 'bitferry'
|
3
4
|
|
5
|
+
|
4
6
|
include Fox
|
5
7
|
|
8
|
+
|
9
|
+
class Output < StringIO
|
10
|
+
|
11
|
+
def initialize(app, output)
|
12
|
+
super('rw+')
|
13
|
+
@app = app
|
14
|
+
@output = output
|
15
|
+
@output.text = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(*args) = @app.runOnUiThread { @output.appendText(args.join) }
|
19
|
+
|
20
|
+
def flush = nil
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
6
25
|
class UI < FXMainWindow
|
7
26
|
|
8
27
|
def initialize(app)
|
9
|
-
super(@app = app, 'BitferryFX', width: 400,
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
28
|
+
super(@app = app, 'BitferryFX', width: 400, height: 300)
|
29
|
+
top_frame = FXVerticalFrame.new(self, opts: LAYOUT_FILL)
|
30
|
+
tabs = FXTabBook.new(top_frame, opts: LAYOUT_FILL)
|
31
|
+
output_tab = FXTabItem.new(tabs, 'Output')
|
32
|
+
@output = FXText.new(tabs)
|
33
|
+
tasks_tab = FXTabItem.new(tabs, 'Tasks')
|
34
|
+
@tasks = FXTable.new(tabs)
|
35
|
+
@tasks.tableStyle |= TABLE_COL_SIZABLE | TABLE_NO_COLSELECT | TABLE_READONLY
|
36
|
+
@tasks.rowHeaderMode = LAYOUT_FIX_WIDTH
|
37
|
+
@tasks.rowHeaderWidth = 0
|
38
|
+
volumes_tab = FXTabItem.new(tabs, 'Volumes')
|
39
|
+
@volumes = FXTable.new(tabs)
|
40
|
+
@volumes.tableStyle |= TABLE_COL_SIZABLE | TABLE_NO_COLSELECT | TABLE_READONLY
|
41
|
+
@volumes.rowHeaderMode = LAYOUT_FIX_WIDTH
|
42
|
+
@volumes.rowHeaderWidth = 0
|
43
|
+
@progress = FXProgressBar.new(top_frame, height: 16, opts: LAYOUT_FILL_X | LAYOUT_FIX_HEIGHT)
|
44
|
+
controls = FXPacker.new(top_frame, opts: LAYOUT_FILL_X)
|
45
|
+
@simulate = FXCheckButton.new(controls, "&Simulation mode (dry run)\tPrevent operations from making any on-disk changes")
|
46
|
+
@simulate.checkState = Bitferry.simulate?
|
47
|
+
@verbose = FXCheckButton.new(controls, "&Verbose mode\tOutput internal logging information")
|
48
|
+
@verbose.checkState = false
|
49
|
+
buttons = FXPacker.new(top_frame, opts: LAYOUT_FILL_X | PACK_UNIFORM_WIDTH | FRAME_SUNKEN)
|
50
|
+
@process = FXButton.new(buttons, "&Process\tProcess all intact tasks", opts: BUTTON_NORMAL | BUTTON_INITIAL | BUTTON_DEFAULT | LAYOUT_SIDE_LEFT)
|
51
|
+
@process.connect(SEL_COMMAND) { process }
|
52
|
+
@process.setFocus
|
53
|
+
@quit = FXButton.new(buttons, "&Quit\tStop any pending operations and exit", opts: BUTTON_NORMAL | LAYOUT_SIDE_RIGHT)
|
54
|
+
@quit.connect(SEL_COMMAND) { exit }
|
55
|
+
@reload = FXButton.new(buttons, "&Reload\tReread volumes and tasks to capture volume changes", opts: BUTTON_NORMAL | LAYOUT_SIDE_RIGHT)
|
56
|
+
@reload.connect(SEL_COMMAND) { reset }
|
22
57
|
@sensible = [@process, @reload] # Controls which must be disabled during processing
|
23
58
|
reset
|
24
59
|
end
|
25
60
|
|
26
61
|
def process
|
27
62
|
Bitferry.simulate = @simulate.checked?
|
63
|
+
Bitferry.verbosity = @verbose.checked? ? :verbose : :default
|
28
64
|
@progress.setBarColor(:blue)
|
29
65
|
@progress.progress = 0
|
66
|
+
@output.text = nil
|
30
67
|
Thread.new {
|
31
68
|
@app.runOnUiThread { @sensible.each(&:disable) }
|
32
69
|
begin
|
33
|
-
Bitferry.process { |total, processed, failed
|
70
|
+
Bitferry.process { |total, processed, failed|
|
34
71
|
@app.runOnUiThread {
|
35
72
|
@progress.setBarColor(:red) if failed > 0
|
36
73
|
@progress.progress = processed
|
@@ -44,8 +81,37 @@ class UI < FXMainWindow
|
|
44
81
|
end
|
45
82
|
|
46
83
|
def reset
|
47
|
-
@progress.progress = 0
|
48
84
|
Bitferry.restore
|
85
|
+
@progress.progress = 0
|
86
|
+
$stdout = Output.new(@app, @output)
|
87
|
+
Bitferry::Logging.log = log = Logger.new($stdout)
|
88
|
+
log.progname = :bitferryfx
|
89
|
+
log.level = Logger::WARN
|
90
|
+
#
|
91
|
+
@volumes.setTableSize(Bitferry::Volume.intact.size, 2)
|
92
|
+
@volumes.setColumnText(0, 'Volume')
|
93
|
+
@volumes.setColumnText(1, 'Root')
|
94
|
+
i = 0
|
95
|
+
Bitferry::Volume.intact.each do |v|
|
96
|
+
@volumes.setItemText(i, 0, v.tag)
|
97
|
+
@volumes.setItemText(i, 1, v.root.to_s)
|
98
|
+
i += 1
|
99
|
+
end
|
100
|
+
#
|
101
|
+
@tasks.setTableSize(Bitferry::Task.intact.size, 4)
|
102
|
+
@tasks.setColumnText(0, 'Task')
|
103
|
+
@tasks.setColumnText(1, 'Operation')
|
104
|
+
@tasks.setColumnText(2, 'Source')
|
105
|
+
@tasks.setColumnText(3, 'Destination')
|
106
|
+
i = 0
|
107
|
+
Bitferry::Task.intact.each do |t|
|
108
|
+
@tasks.setItemText(i, 0, t.tag)
|
109
|
+
@tasks.setItemText(i, 1, t.show_operation)
|
110
|
+
@tasks.setItemText(i, 2, t.source.show_status)
|
111
|
+
@tasks.setItemText(i, 3, t.destination.show_status)
|
112
|
+
i += 1
|
113
|
+
end
|
114
|
+
#
|
49
115
|
end
|
50
116
|
|
51
117
|
def create
|
@@ -55,9 +121,11 @@ class UI < FXMainWindow
|
|
55
121
|
|
56
122
|
end
|
57
123
|
|
124
|
+
|
58
125
|
FXApp.new do |app|
|
59
126
|
Bitferry.verbosity = :verbose
|
127
|
+
Bitferry.ui = :gui
|
60
128
|
UI.new(app)
|
61
129
|
app.create
|
62
130
|
app.run
|
63
|
-
end
|
131
|
+
end
|
data/lib/bitferry.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'date'
|
3
3
|
require 'open3'
|
4
|
+
require 'base64'
|
4
5
|
require 'logger'
|
6
|
+
require 'openssl'
|
5
7
|
require 'pathname'
|
6
|
-
require 'neatjson'
|
7
8
|
require 'rbconfig'
|
8
9
|
require 'fileutils'
|
9
10
|
require 'shellwords'
|
@@ -12,7 +13,7 @@ require 'shellwords'
|
|
12
13
|
module Bitferry
|
13
14
|
|
14
15
|
|
15
|
-
VERSION = '0.0.
|
16
|
+
VERSION = '0.0.7'
|
16
17
|
|
17
18
|
|
18
19
|
module Logging
|
@@ -24,6 +25,7 @@ module Bitferry
|
|
24
25
|
end
|
25
26
|
@log
|
26
27
|
end
|
28
|
+
def self.log=(log) @log = log end
|
27
29
|
def log = Logging.log
|
28
30
|
end
|
29
31
|
|
@@ -149,6 +151,11 @@ module Bitferry
|
|
149
151
|
def self.verbosity=(mode) @verbosity = mode end
|
150
152
|
|
151
153
|
|
154
|
+
@ui = :cli
|
155
|
+
def self.ui = @ui
|
156
|
+
def self.ui=(ui) @ui = ui end
|
157
|
+
|
158
|
+
|
152
159
|
# Return true if run in the real Windows environment (e.g. not in real *NIX or various emulation layers such as MSYS, Cygwin etc.)
|
153
160
|
def self.windows?
|
154
161
|
@windows ||= /^(mingw)/.match?(RbConfig::CONFIG['target_os']) # RubyInstaller's MRI, other MinGW-build MRI
|
@@ -400,6 +407,7 @@ module Bitferry
|
|
400
407
|
|
401
408
|
|
402
409
|
def store
|
410
|
+
require 'neatjson'
|
403
411
|
tasks.each(&:commit)
|
404
412
|
hash = JSON.neat_generate(externalize, short: false, wrap: 200, afterColon: 1, afterComma: 1)
|
405
413
|
if Bitferry.simulate?
|
@@ -669,9 +677,9 @@ module Bitferry
|
|
669
677
|
def self.exec(*args)
|
670
678
|
cmd = [executable] + args
|
671
679
|
log.debug(cmd.collect(&:shellescape).join(' '))
|
672
|
-
stdout, status = Open3.
|
680
|
+
stdout, status = Open3.capture2e(*cmd)
|
673
681
|
unless status.success?
|
674
|
-
msg = "rclone exit code #{status.
|
682
|
+
msg = "rclone exit code #{status.exitstatus}"
|
675
683
|
log.error(msg)
|
676
684
|
raise RuntimeError, msg
|
677
685
|
end
|
@@ -679,10 +687,26 @@ module Bitferry
|
|
679
687
|
end
|
680
688
|
|
681
689
|
|
682
|
-
|
690
|
+
# https://github.com/rclone/rclone/blob/master/fs/config/obscure/obscure.go
|
691
|
+
SECRET = "\x9c\x93\x5b\x48\x73\x0a\x55\x4d\x6b\xfd\x7c\x63\xc8\x86\xa9\x2b\xd3\x90\x19\x8e\xb8\x12\x8a\xfb\xf4\xde\x16\x2b\x8b\x95\xf6\x38"
|
692
|
+
|
693
|
+
|
694
|
+
def self.obscure(plain)
|
695
|
+
cipher = OpenSSL::Cipher.new('AES-256-CTR')
|
696
|
+
cipher.encrypt
|
697
|
+
cipher.key = SECRET
|
698
|
+
Base64.urlsafe_encode64(cipher.random_iv + cipher.update(plain) + cipher.final, padding: false)
|
699
|
+
end
|
683
700
|
|
684
701
|
|
685
|
-
def self.reveal(token)
|
702
|
+
def self.reveal(token)
|
703
|
+
data = Base64.urlsafe_decode64(token)
|
704
|
+
cipher = OpenSSL::Cipher.new('AES-256-CTR')
|
705
|
+
cipher.decrypt
|
706
|
+
cipher.key = SECRET
|
707
|
+
cipher.iv = data[0...cipher.iv_len]
|
708
|
+
cipher.update(data[cipher.iv_len..-1]) + cipher.final
|
709
|
+
end
|
686
710
|
|
687
711
|
|
688
712
|
class Encryption
|
@@ -884,9 +908,18 @@ module Bitferry
|
|
884
908
|
def execute(*args)
|
885
909
|
cmd = [Rclone.executable] + args
|
886
910
|
cms = cmd.collect(&:shellescape).join(' ')
|
887
|
-
puts cms if Bitferry.verbosity == :verbose
|
911
|
+
$stdout.puts cms if Bitferry.verbosity == :verbose
|
888
912
|
log.info(cms)
|
889
|
-
|
913
|
+
if Bitferry.ui == :gui
|
914
|
+
t = nil
|
915
|
+
Open3.popen2e(*cmd) do |i, oe, thr|
|
916
|
+
while x = oe.gets; $stdout.puts(x) end
|
917
|
+
t = thr
|
918
|
+
end
|
919
|
+
status = t.value
|
920
|
+
else
|
921
|
+
status = Open3.pipeline(cmd).first
|
922
|
+
end
|
890
923
|
raise RuntimeError, "rclone exit code #{status.exitstatus}" unless status.success?
|
891
924
|
status.success?
|
892
925
|
end
|
@@ -1070,8 +1103,9 @@ module Bitferry
|
|
1070
1103
|
def execute(*args, simulate: false, chdir: nil)
|
1071
1104
|
cmd = [Restic.executable] + args
|
1072
1105
|
ENV['RESTIC_PASSWORD'] = password
|
1106
|
+
ENV['RESTIC_PROGRESS_FPS'] = 1.to_s if Bitferry.verbosity == :verbose && Bitferry.ui == :gui
|
1073
1107
|
cms = cmd.collect(&:shellescape).join(' ')
|
1074
|
-
puts cms if Bitferry.verbosity == :verbose
|
1108
|
+
$stdout.puts cms if Bitferry.verbosity == :verbose
|
1075
1109
|
log.info(cms)
|
1076
1110
|
if simulate
|
1077
1111
|
log.info('(simulated)')
|
@@ -1080,7 +1114,16 @@ module Bitferry
|
|
1080
1114
|
wd = Dir.getwd unless chdir.nil?
|
1081
1115
|
begin
|
1082
1116
|
Dir.chdir(chdir) unless chdir.nil?
|
1083
|
-
|
1117
|
+
if Bitferry.ui == :gui
|
1118
|
+
t = nil
|
1119
|
+
Open3.popen2e(*cmd) do |i, oe, thr|
|
1120
|
+
while x = oe.gets; $stdout.puts(x) end
|
1121
|
+
t = thr
|
1122
|
+
end
|
1123
|
+
status = t.value
|
1124
|
+
else
|
1125
|
+
status = Open3.pipeline(cmd).first
|
1126
|
+
end
|
1084
1127
|
raise RuntimeError, "restic exit code #{status.exitstatus}" unless status.success?
|
1085
1128
|
status.success?
|
1086
1129
|
ensure
|
@@ -1159,6 +1202,10 @@ module Bitferry
|
|
1159
1202
|
def show_direction = '-->'
|
1160
1203
|
|
1161
1204
|
|
1205
|
+
alias :source :directory
|
1206
|
+
alias :destination :repository
|
1207
|
+
|
1208
|
+
|
1162
1209
|
def process
|
1163
1210
|
begin
|
1164
1211
|
log.info("processing task #{tag}")
|
@@ -1247,7 +1294,7 @@ module Bitferry
|
|
1247
1294
|
end
|
1248
1295
|
|
1249
1296
|
|
1250
|
-
def exclude_filters = exclude.collect { |x| ['--exclude', x]}.flatten
|
1297
|
+
def exclude_filters = exclude.collect { |x| ['--exclude', x] }.flatten
|
1251
1298
|
|
1252
1299
|
|
1253
1300
|
def show_status = "#{show_operation} #{repository.show_status} #{show_direction} #{directory.show_status} #{show_filters}"
|
@@ -1259,6 +1306,10 @@ module Bitferry
|
|
1259
1306
|
def show_direction = '-->'
|
1260
1307
|
|
1261
1308
|
|
1309
|
+
alias :destination :directory
|
1310
|
+
alias :source :repository
|
1311
|
+
|
1312
|
+
|
1262
1313
|
def externalize
|
1263
1314
|
restic = {
|
1264
1315
|
process: process_options
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitferry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleg A. Khlybov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -39,33 +39,47 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: seven-zip
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.4'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.4'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: archive-zip
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
type: :
|
61
|
+
version: '0.12'
|
62
|
+
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.6'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: neatjson
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,20 @@ dependencies:
|
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0.10'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: fxruby
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.6'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.6'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: clamp
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|