mclone 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +16 -8
- data/README.md +409 -409
- data/bin/mclone +1 -157
- data/lib/mclone/cli.rb +157 -0
- data/lib/mclone.rb +752 -745
- metadata +4 -3
data/bin/mclone
CHANGED
@@ -1,159 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require 'clamp'
|
6
|
-
require 'mclone'
|
7
|
-
|
8
|
-
include Mclone
|
9
|
-
|
10
|
-
begin
|
11
|
-
|
12
|
-
Clamp do
|
13
|
-
|
14
|
-
using Refinements
|
15
|
-
|
16
|
-
self.default_subcommand = 'info'
|
17
|
-
|
18
|
-
option ['-f', '--force'], :flag, 'Insist on potentially dangerous actions', default: false
|
19
|
-
option ['-n', '--dry-run'], :flag, 'Simulation mode with no on-disk modifications', default: false
|
20
|
-
option ['-v', '--verbose'], :flag, 'Verbose operation', default: false
|
21
|
-
|
22
|
-
option ['-V', '--version'], :flag, 'Show version' do
|
23
|
-
puts VERSION
|
24
|
-
exit(true)
|
25
|
-
end
|
26
|
-
|
27
|
-
def session
|
28
|
-
session = Mclone::Session.new
|
29
|
-
session.force = force?
|
30
|
-
session.simulate = dry_run?
|
31
|
-
session.verbose = verbose?
|
32
|
-
session.restore_volumes!
|
33
|
-
session
|
34
|
-
end
|
35
|
-
|
36
|
-
def resolve_mode(mode)
|
37
|
-
case (m = Task::MODES.resolve(mode)).size
|
38
|
-
when 0 then raise(Task::Error, %(no modes matching pattern "#{mode}"))
|
39
|
-
when 1 then m.first
|
40
|
-
else raise(Task::Error, %(ambiguous mode pattern "#{mode}"))
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
subcommand 'info', 'Output information on volumes & tasks' do
|
45
|
-
def execute
|
46
|
-
s = session
|
47
|
-
$stdout.puts "# Mclone version #{Mclone::VERSION}"
|
48
|
-
$stdout.puts
|
49
|
-
$stdout.puts '## Volumes'
|
50
|
-
$stdout.puts
|
51
|
-
s.volumes.each { |volume| $stdout.puts "* [#{volume.id}] :: (#{volume.root})" }
|
52
|
-
stales = []
|
53
|
-
intacts = []
|
54
|
-
intact_tasks = s.intact_tasks
|
55
|
-
s.tasks.each do |task|
|
56
|
-
ts = (t = intact_tasks[task]).nil? ? "<#{task.id}>" : "[#{task.id}]"
|
57
|
-
svs = s.volumes.volume(task.source_id).nil? ? "<#{task.source_id}>" : "[#{task.source_id}]"
|
58
|
-
dvs = s.volumes.volume(task.destination_id).nil? ? "<#{task.destination_id}>" : "[#{task.destination_id}]"
|
59
|
-
crypter_mode = task.crypter_mode.nil? ? nil : "#{task.crypter_mode}+"
|
60
|
-
xs = ["* #{ts} :: #{crypter_mode}#{task.mode} #{svs}(#{task.source_root}) -> #{dvs}(#{task.destination_root})"]
|
61
|
-
xs << "include #{task.include}" unless task.include.nil? || task.include.empty?
|
62
|
-
xs << "exclude #{task.exclude}" unless task.exclude.nil? || task.exclude.empty?
|
63
|
-
(t.nil? ? stales : intacts) << xs.join(' :: ')
|
64
|
-
end
|
65
|
-
unless intacts.empty?
|
66
|
-
$stdout.puts
|
67
|
-
$stdout.puts '## Intact tasks'
|
68
|
-
$stdout.puts
|
69
|
-
intacts.each { |x| $stdout.puts x }
|
70
|
-
end
|
71
|
-
unless stales.empty?
|
72
|
-
$stdout.puts
|
73
|
-
$stdout.puts '## Stale tasks'
|
74
|
-
$stdout.puts
|
75
|
-
stales.each { |x| $stdout.puts x }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
subcommand 'volume', 'Volume operations' do
|
81
|
-
|
82
|
-
subcommand ['new', 'create'], 'Create new volume' do
|
83
|
-
parameter 'DIRECTORY', 'Directory to become a Mclone volume'
|
84
|
-
def execute
|
85
|
-
session.format_volume!(directory)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
subcommand 'delete', 'Delete existing volume' do
|
90
|
-
parameter 'VOLUME', 'Volume ID pattern'
|
91
|
-
def execute
|
92
|
-
session.delete_volume!(volume).commit!
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
subcommand 'task', 'Task operations' do
|
99
|
-
|
100
|
-
def self.set_task_opts
|
101
|
-
modes = Task::MODES.collect(&:to_s).join(' | ')
|
102
|
-
option ['-m', '--mode'], 'MODE', "Operation mode (#{modes})", default: Task::MODES.first.to_s
|
103
|
-
option ['-i', '--include'], 'PATTERN', 'Include paths pattern'
|
104
|
-
option ['-x', '--exclude'], 'PATTERN', 'Exclude paths pattern'
|
105
|
-
end
|
106
|
-
|
107
|
-
subcommand ['new', 'create'], 'Create new SOURCE -> DESTINATION task' do
|
108
|
-
set_task_opts
|
109
|
-
option ['-d', '--decrypt'], :flag, 'Decrypt source'
|
110
|
-
option ['-e', '--encrypt'], :flag, 'Encrypt destination'
|
111
|
-
option ['-p', '--password'], 'PASSWORD', 'Plain text password'
|
112
|
-
option ['-t', '--token'], 'TOKEN', 'Rclone crypt token (obscured password)'
|
113
|
-
parameter 'SOURCE', 'Source path'
|
114
|
-
parameter 'DESTINATION', 'Destination path'
|
115
|
-
def execute
|
116
|
-
crypter_mode = nil
|
117
|
-
signal_usage_error 'choose either encryption or decryption mode, not both' if decrypt? && encrypt?
|
118
|
-
signal_usage_error 'specify either plain text password or Rclone crypt token, not both' if !password.nil? && !token.nil?
|
119
|
-
crypter_mode = :encrypt if encrypt?
|
120
|
-
crypter_mode = :decrypt if decrypt?
|
121
|
-
session.create_task!(
|
122
|
-
resolve_mode(mode),
|
123
|
-
source,
|
124
|
-
destination,
|
125
|
-
include: include, exclude: exclude, crypter_mode: crypter_mode, crypter_password: password, crypter_token: token
|
126
|
-
).commit!
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
subcommand 'modify', 'Modify existing task' do
|
131
|
-
set_task_opts
|
132
|
-
parameter 'TASK', 'Task ID pattern'
|
133
|
-
def execute
|
134
|
-
session.modify_task!(task, mode: resolve_mode(mode), include: include, exclude: exclude).commit!
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
subcommand 'delete', 'Delete existing task' do
|
139
|
-
parameter 'TASK', 'Task ID pattern'
|
140
|
-
def execute
|
141
|
-
session.delete_task!(task).commit!
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
subcommand 'process', 'Process specified tasks' do
|
146
|
-
parameter '[TASK] ...', 'Task ID pattern(s)', attribute_name: :tasks
|
147
|
-
def execute
|
148
|
-
session.process_tasks!(*tasks)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
rescue Mclone::Error
|
157
|
-
$stderr.puts "ERROR: #{$!.message}"
|
158
|
-
exit(false)
|
159
|
-
end
|
3
|
+
require 'mclone/cli'
|
data/lib/mclone/cli.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'clamp'
|
4
|
+
require 'mclone'
|
5
|
+
|
6
|
+
include Mclone
|
7
|
+
|
8
|
+
begin
|
9
|
+
|
10
|
+
Clamp do
|
11
|
+
|
12
|
+
using Refinements
|
13
|
+
|
14
|
+
self.default_subcommand = 'info'
|
15
|
+
|
16
|
+
option ['-f', '--force'], :flag, 'Insist on potentially dangerous actions', default: false
|
17
|
+
option ['-n', '--dry-run'], :flag, 'Simulation mode with no on-disk modifications', default: false
|
18
|
+
option ['-v', '--verbose'], :flag, 'Verbose operation', default: false
|
19
|
+
|
20
|
+
option ['-V', '--version'], :flag, 'Show version' do
|
21
|
+
puts VERSION
|
22
|
+
exit(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def session
|
26
|
+
session = Mclone::Session.new
|
27
|
+
session.force = force?
|
28
|
+
session.simulate = dry_run?
|
29
|
+
session.verbose = verbose?
|
30
|
+
session.restore_volumes!
|
31
|
+
session
|
32
|
+
end
|
33
|
+
|
34
|
+
def resolve_mode(mode)
|
35
|
+
case (m = Task::MODES.resolve(mode)).size
|
36
|
+
when 0 then raise(Task::Error, %(no modes matching pattern "#{mode}"))
|
37
|
+
when 1 then m.first
|
38
|
+
else raise(Task::Error, %(ambiguous mode pattern "#{mode}"))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
subcommand 'info', 'Output information on volumes & tasks' do
|
43
|
+
def execute
|
44
|
+
s = session
|
45
|
+
$stdout.puts "# Mclone version #{Mclone::VERSION}"
|
46
|
+
$stdout.puts
|
47
|
+
$stdout.puts '## Volumes'
|
48
|
+
$stdout.puts
|
49
|
+
s.volumes.each { |volume| $stdout.puts "* [#{volume.id}] :: (#{volume.root})" }
|
50
|
+
stales = []
|
51
|
+
intacts = []
|
52
|
+
intact_tasks = s.intact_tasks
|
53
|
+
s.tasks.each do |task|
|
54
|
+
ts = (t = intact_tasks[task]).nil? ? "<#{task.id}>" : "[#{task.id}]"
|
55
|
+
svs = s.volumes.volume(task.source_id).nil? ? "<#{task.source_id}>" : "[#{task.source_id}]"
|
56
|
+
dvs = s.volumes.volume(task.destination_id).nil? ? "<#{task.destination_id}>" : "[#{task.destination_id}]"
|
57
|
+
crypter_mode = task.crypter_mode.nil? ? nil : "#{task.crypter_mode}+"
|
58
|
+
xs = ["* #{ts} :: #{crypter_mode}#{task.mode} #{svs}(#{task.source_root}) -> #{dvs}(#{task.destination_root})"]
|
59
|
+
xs << "include #{task.include}" unless task.include.nil? || task.include.empty?
|
60
|
+
xs << "exclude #{task.exclude}" unless task.exclude.nil? || task.exclude.empty?
|
61
|
+
(t.nil? ? stales : intacts) << xs.join(' :: ')
|
62
|
+
end
|
63
|
+
unless intacts.empty?
|
64
|
+
$stdout.puts
|
65
|
+
$stdout.puts '## Intact tasks'
|
66
|
+
$stdout.puts
|
67
|
+
intacts.each { |x| $stdout.puts x }
|
68
|
+
end
|
69
|
+
unless stales.empty?
|
70
|
+
$stdout.puts
|
71
|
+
$stdout.puts '## Stale tasks'
|
72
|
+
$stdout.puts
|
73
|
+
stales.each { |x| $stdout.puts x }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
subcommand 'volume', 'Volume operations' do
|
79
|
+
|
80
|
+
subcommand ['new', 'create'], 'Create new volume' do
|
81
|
+
parameter 'DIRECTORY', 'Directory to become a Mclone volume'
|
82
|
+
def execute
|
83
|
+
session.format_volume!(directory)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
subcommand 'delete', 'Delete existing volume' do
|
88
|
+
parameter 'VOLUME', 'Volume ID pattern'
|
89
|
+
def execute
|
90
|
+
session.delete_volume!(volume).commit!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
subcommand 'task', 'Task operations' do
|
97
|
+
|
98
|
+
def self.set_task_opts
|
99
|
+
modes = Task::MODES.collect(&:to_s).join(' | ')
|
100
|
+
option ['-m', '--mode'], 'MODE', "Operation mode (#{modes})", default: Task::MODES.first.to_s
|
101
|
+
option ['-i', '--include'], 'PATTERN', 'Include paths pattern'
|
102
|
+
option ['-x', '--exclude'], 'PATTERN', 'Exclude paths pattern'
|
103
|
+
end
|
104
|
+
|
105
|
+
subcommand ['new', 'create'], 'Create new SOURCE -> DESTINATION task' do
|
106
|
+
set_task_opts
|
107
|
+
option ['-d', '--decrypt'], :flag, 'Decrypt source'
|
108
|
+
option ['-e', '--encrypt'], :flag, 'Encrypt destination'
|
109
|
+
option ['-p', '--password'], 'PASSWORD', 'Plain text password'
|
110
|
+
option ['-t', '--token'], 'TOKEN', 'Rclone crypt token (obscured password)'
|
111
|
+
parameter 'SOURCE', 'Source path'
|
112
|
+
parameter 'DESTINATION', 'Destination path'
|
113
|
+
def execute
|
114
|
+
crypter_mode = nil
|
115
|
+
signal_usage_error 'choose either encryption or decryption mode, not both' if decrypt? && encrypt?
|
116
|
+
signal_usage_error 'specify either plain text password or Rclone crypt token, not both' if !password.nil? && !token.nil?
|
117
|
+
crypter_mode = :encrypt if encrypt?
|
118
|
+
crypter_mode = :decrypt if decrypt?
|
119
|
+
session.create_task!(
|
120
|
+
resolve_mode(mode),
|
121
|
+
source,
|
122
|
+
destination,
|
123
|
+
include: include, exclude: exclude, crypter_mode: crypter_mode, crypter_password: password, crypter_token: token
|
124
|
+
).commit!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
subcommand 'modify', 'Modify existing task' do
|
129
|
+
set_task_opts
|
130
|
+
parameter 'TASK', 'Task ID pattern'
|
131
|
+
def execute
|
132
|
+
session.modify_task!(task, mode: resolve_mode(mode), include: include, exclude: exclude).commit!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
subcommand 'delete', 'Delete existing task' do
|
137
|
+
parameter 'TASK', 'Task ID pattern'
|
138
|
+
def execute
|
139
|
+
session.delete_task!(task).commit!
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
subcommand 'process', 'Process specified tasks' do
|
144
|
+
parameter '[TASK] ...', 'Task ID pattern(s)', attribute_name: :tasks
|
145
|
+
def execute
|
146
|
+
session.process_tasks!(*tasks)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
rescue Mclone::Error
|
155
|
+
$stderr.puts "ERROR: #{$!.message}"
|
156
|
+
exit(false)
|
157
|
+
end
|