bitferry 0.0.5 → 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: 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