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 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