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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ef499867b4d23f30f8edeb19f087d80d5c6e10f22a0b969b296c953e5d3b2e6
4
- data.tar.gz: 9a4945bbcd8c6ea7c0994f291c8f278cd08cc4aae2fab432811059a169c805b3
3
+ metadata.gz: f67881c016dae96456c1d0b1baee9b3949eb9bfd46aa5f96cf7232d219c1d7d8
4
+ data.tar.gz: a5613291cc0b050ce243c8cf4142d8020c363100b0655cf6fcb405c0b380daba
5
5
  SHA512:
6
- metadata.gz: 2737d6337aa29f0046eaa9d9088dd14ac83fc3934c234bd9d659c788aca15fb03c618aa5698327a99985b5049f68f52e320d649e867bac1b18aaff0a876f2d5f
7
- data.tar.gz: 362ba450ce7b4322f19d153b1958d6bb615e1ffd379c9b90a3bd191c1f39f706f30e495b5836ce2ab575b96b7f22510dd59de1386bb98461919f46e22d30ff79
6
+ metadata.gz: 670ea9837098777a1dfa56df30283f2d6fac41df303003a33cd1e80711d5c4967f09ab72ba2247dbdb00c57830f0310825a064397f5db463e0ee927de72ce6dc
7
+ data.tar.gz: 433fb3934ae559cf42eb0a20d051923c0dc5e93eed32f5249dc95a1dde1930d3982fb925efd64f03d418fa9fefa54a74959ec87118c3c26d05ba0927667ee0cf
data/CHANGES.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.0.7
2
+
3
+ - Fix backend execution issues in GUI
4
+ - Add verbosity control in GUI
5
+ - GUI info additions
6
+
1
7
  ## 0.0.6
2
8
 
3
9
  - Rudimentary GUI employing FXRuby
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, padding: 4)
10
- FXToolTip.new(app)
11
- @progress = FXProgressBar.new(self, padding: 4, height: 16, opts: LAYOUT_FILL_X | JUSTIFY_BOTTOM | LAYOUT_FIX_HEIGHT)
12
- @simulate = FXCheckButton.new(self, "&Simulation mode (dry run)\tPrevent operations from making any on-disk changes")
13
- @simulate.checkState = Bitferry.simulate?
14
- buttons = FXPacker.new(self, opts: LAYOUT_FILL_X | LAYOUT_SIDE_BOTTOM | PACK_UNIFORM_WIDTH | FRAME_SUNKEN)
15
- @process = FXButton.new(buttons, "&Process\tProcess all intact tasks", opts: BUTTON_NORMAL | BUTTON_INITIAL | BUTTON_DEFAULT | LAYOUT_SIDE_LEFT)
16
- @process.connect(SEL_COMMAND) { process }
17
- @process.setFocus
18
- @quit = FXButton.new(buttons, "&Quit\tStop any pending operations and exit", opts: BUTTON_NORMAL | LAYOUT_SIDE_RIGHT)
19
- @quit.connect(SEL_COMMAND) { exit }
20
- @reload = FXButton.new(buttons, "&Reload\tReread volumes and tasks to capture volume changes", opts: BUTTON_NORMAL | LAYOUT_SIDE_RIGHT)
21
- @reload.connect(SEL_COMMAND) { reset }
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.6'
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.capture2(*cmd)
680
+ stdout, status = Open3.capture2e(*cmd)
673
681
  unless status.success?
674
- msg = "rclone exit code #{status.to_i}"
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
- def self.obscure(plain) = exec('obscure', '--', plain)
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) = exec('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
- status = Open3.pipeline(cmd).first
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
- status = Open3.pipeline(cmd).first
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.6
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-04-03 00:00:00.000000000 Z
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: commonmarker
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.0'
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.0'
54
+ version: '1.4'
55
55
  - !ruby/object:Gem::Dependency
56
- name: fxruby
56
+ name: archive-zip
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.6'
62
- type: :runtime
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: '1.6'
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