bitferry 0.0.5 → 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: bb10ff66d68e1981c2011621d1d216cfe759e14378844f5101310b78e5938974
4
- data.tar.gz: 0341d89784f9acaa607f9267ddcbd494901efa80ca9954d60d84484d177c63ff
3
+ metadata.gz: f67881c016dae96456c1d0b1baee9b3949eb9bfd46aa5f96cf7232d219c1d7d8
4
+ data.tar.gz: a5613291cc0b050ce243c8cf4142d8020c363100b0655cf6fcb405c0b380daba
5
5
  SHA512:
6
- metadata.gz: 2ac8f19ed6ef8607082b8943d0cab8ae4fa973694bfac1ddc219fe34e2421c05116d9d4ea20213440b192a7a1eae50cc11a485f489976dd182ff5ea1dc5bf1d2
7
- data.tar.gz: cf4dabac47d7b46ed13129b749d82c92cd216f91b3fb41c6a8d2db9114738ae875f38db99d0112d26a7176db639d5cba68ebdeaee8e9e6f61f7f21dc658de568
6
+ metadata.gz: 670ea9837098777a1dfa56df30283f2d6fac41df303003a33cd1e80711d5c4967f09ab72ba2247dbdb00c57830f0310825a064397f5db463e0ee927de72ce6dc
7
+ data.tar.gz: 433fb3934ae559cf42eb0a20d051923c0dc5e93eed32f5249dc95a1dde1930d3982fb925efd64f03d418fa9fefa54a74959ec87118c3c26d05ba0927667ee0cf
data/CHANGES.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.0.7
2
+
3
+ - Fix backend execution issues in GUI
4
+ - Add verbosity control in GUI
5
+ - GUI info additions
6
+
7
+ ## 0.0.6
8
+
9
+ - Rudimentary GUI employing FXRuby
10
+
1
11
  ## 0.0.5
2
12
 
3
13
  - Disable cache usage in Restic check profiles
data/bin/bitferryfx ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bitferry/fx'
3
+ #
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
@@ -0,0 +1,131 @@
1
+ require 'fox16'
2
+ require 'stringio'
3
+ require 'bitferry'
4
+
5
+
6
+ include Fox
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
+
25
+ class UI < FXMainWindow
26
+
27
+ def initialize(app)
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 }
57
+ @sensible = [@process, @reload] # Controls which must be disabled during processing
58
+ reset
59
+ end
60
+
61
+ def process
62
+ Bitferry.simulate = @simulate.checked?
63
+ Bitferry.verbosity = @verbose.checked? ? :verbose : :default
64
+ @progress.setBarColor(:blue)
65
+ @progress.progress = 0
66
+ @output.text = nil
67
+ Thread.new {
68
+ @app.runOnUiThread { @sensible.each(&:disable) }
69
+ begin
70
+ Bitferry.process { |total, processed, failed|
71
+ @app.runOnUiThread {
72
+ @progress.setBarColor(:red) if failed > 0
73
+ @progress.progress = processed
74
+ @progress.total = total
75
+ }
76
+ }
77
+ ensure
78
+ @app.runOnUiThread { @sensible.each(&:enable) }
79
+ end
80
+ }
81
+ end
82
+
83
+ def reset
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
+ #
115
+ end
116
+
117
+ def create
118
+ super
119
+ show(PLACEMENT_SCREEN)
120
+ end
121
+
122
+ end
123
+
124
+
125
+ FXApp.new do |app|
126
+ Bitferry.verbosity = :verbose
127
+ Bitferry.ui = :gui
128
+ UI.new(app)
129
+ app.create
130
+ app.run
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.5'
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
 
@@ -85,9 +87,11 @@ module Bitferry
85
87
  end
86
88
 
87
89
 
88
- def self.process(*tags)
90
+ def self.intact_tasks = Volume.intact.collect { |volume| volume.intact_tasks }.flatten.uniq
91
+
92
+ def self.process(*tags, &block)
89
93
  log.info('processing tasks')
90
- tasks = Volume.intact.collect { |volume| volume.intact_tasks }.flatten.uniq
94
+ tasks = intact_tasks
91
95
  if tags.empty?
92
96
  process = tasks
93
97
  else
@@ -102,7 +106,17 @@ module Bitferry
102
106
  end
103
107
  end
104
108
  end
105
- result = process.uniq.all? { |task| task.process }
109
+ tasks = process.uniq
110
+ total = tasks.size
111
+ processed = 0
112
+ failed = 0
113
+ result = tasks.all? do |task|
114
+ r = task.process
115
+ processed += 1
116
+ failed += 1 unless r
117
+ yield(total, processed, failed) if block_given?
118
+ r
119
+ end
106
120
  result ? log.info('tasks processed') : log.warn('task process failure(s) reported')
107
121
  result
108
122
  end
@@ -137,6 +151,11 @@ module Bitferry
137
151
  def self.verbosity=(mode) @verbosity = mode end
138
152
 
139
153
 
154
+ @ui = :cli
155
+ def self.ui = @ui
156
+ def self.ui=(ui) @ui = ui end
157
+
158
+
140
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.)
141
160
  def self.windows?
142
161
  @windows ||= /^(mingw)/.match?(RbConfig::CONFIG['target_os']) # RubyInstaller's MRI, other MinGW-build MRI
@@ -388,6 +407,7 @@ module Bitferry
388
407
 
389
408
 
390
409
  def store
410
+ require 'neatjson'
391
411
  tasks.each(&:commit)
392
412
  hash = JSON.neat_generate(externalize, short: false, wrap: 200, afterColon: 1, afterComma: 1)
393
413
  if Bitferry.simulate?
@@ -657,9 +677,9 @@ module Bitferry
657
677
  def self.exec(*args)
658
678
  cmd = [executable] + args
659
679
  log.debug(cmd.collect(&:shellescape).join(' '))
660
- stdout, status = Open3.capture2(*cmd)
680
+ stdout, status = Open3.capture2e(*cmd)
661
681
  unless status.success?
662
- msg = "rclone exit code #{status.to_i}"
682
+ msg = "rclone exit code #{status.exitstatus}"
663
683
  log.error(msg)
664
684
  raise RuntimeError, msg
665
685
  end
@@ -667,10 +687,26 @@ module Bitferry
667
687
  end
668
688
 
669
689
 
670
- 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"
671
692
 
672
693
 
673
- def self.reveal(token) = exec('reveal', '--', token)
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
700
+
701
+
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
674
710
 
675
711
 
676
712
  class Encryption
@@ -872,9 +908,18 @@ module Bitferry
872
908
  def execute(*args)
873
909
  cmd = [Rclone.executable] + args
874
910
  cms = cmd.collect(&:shellescape).join(' ')
875
- puts cms if Bitferry.verbosity == :verbose
911
+ $stdout.puts cms if Bitferry.verbosity == :verbose
876
912
  log.info(cms)
877
- 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
878
923
  raise RuntimeError, "rclone exit code #{status.exitstatus}" unless status.success?
879
924
  status.success?
880
925
  end
@@ -1058,8 +1103,9 @@ module Bitferry
1058
1103
  def execute(*args, simulate: false, chdir: nil)
1059
1104
  cmd = [Restic.executable] + args
1060
1105
  ENV['RESTIC_PASSWORD'] = password
1106
+ ENV['RESTIC_PROGRESS_FPS'] = 1.to_s if Bitferry.verbosity == :verbose && Bitferry.ui == :gui
1061
1107
  cms = cmd.collect(&:shellescape).join(' ')
1062
- puts cms if Bitferry.verbosity == :verbose
1108
+ $stdout.puts cms if Bitferry.verbosity == :verbose
1063
1109
  log.info(cms)
1064
1110
  if simulate
1065
1111
  log.info('(simulated)')
@@ -1068,7 +1114,16 @@ module Bitferry
1068
1114
  wd = Dir.getwd unless chdir.nil?
1069
1115
  begin
1070
1116
  Dir.chdir(chdir) unless chdir.nil?
1071
- 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
1072
1127
  raise RuntimeError, "restic exit code #{status.exitstatus}" unless status.success?
1073
1128
  status.success?
1074
1129
  ensure
@@ -1147,6 +1202,10 @@ module Bitferry
1147
1202
  def show_direction = '-->'
1148
1203
 
1149
1204
 
1205
+ alias :source :directory
1206
+ alias :destination :repository
1207
+
1208
+
1150
1209
  def process
1151
1210
  begin
1152
1211
  log.info("processing task #{tag}")
@@ -1235,7 +1294,7 @@ module Bitferry
1235
1294
  end
1236
1295
 
1237
1296
 
1238
- def exclude_filters = exclude.collect { |x| ['--exclude', x]}.flatten
1297
+ def exclude_filters = exclude.collect { |x| ['--exclude', x] }.flatten
1239
1298
 
1240
1299
 
1241
1300
  def show_status = "#{show_operation} #{repository.show_status} #{show_direction} #{directory.show_status} #{show_filters}"
@@ -1247,6 +1306,10 @@ module Bitferry
1247
1306
  def show_direction = '-->'
1248
1307
 
1249
1308
 
1309
+ alias :destination :directory
1310
+ alias :source :repository
1311
+
1312
+
1250
1313
  def externalize
1251
1314
  restic = {
1252
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.5
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-03-26 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,19 +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
+ - !ruby/object:Gem::Dependency
56
+ name: archive-zip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::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'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: neatjson
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +94,20 @@ dependencies:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
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'
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: clamp
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -84,14 +126,17 @@ description:
84
126
  email: fougas@mail.ru
85
127
  executables:
86
128
  - bitferry
129
+ - bitferryfx
87
130
  extensions: []
88
131
  extra_rdoc_files: []
89
132
  files:
90
133
  - CHANGES.md
91
134
  - README.md
92
135
  - bin/bitferry
136
+ - bin/bitferryfx
93
137
  - lib/bitferry.rb
94
138
  - lib/bitferry/cli.rb
139
+ - lib/bitferry/fx.rb
95
140
  homepage: https://github.com/okhlybov/bitferry
96
141
  licenses:
97
142
  - BSD-3-Clause