bitferry 0.0.6 → 0.0.7
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/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
|