yobi 0.1.0 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +93 -124
- data/lib/yobi/argv_builder.rb +5 -0
- data/lib/yobi/errors.rb +6 -10
- data/lib/yobi/fancy_hash.rb +31 -0
- data/lib/yobi/io_handle.rb +9 -7
- data/lib/yobi/{mount.rb → mount_handle.rb} +11 -10
- data/lib/yobi/repository/backup.rb +75 -85
- data/lib/yobi/repository/cat.rb +10 -0
- data/lib/yobi/repository/check.rb +61 -45
- data/lib/yobi/repository/diff.rb +130 -47
- data/lib/yobi/repository/dump.rb +1 -1
- data/lib/yobi/repository/find.rb +11 -33
- data/lib/yobi/repository/forget.rb +6 -48
- data/lib/yobi/repository/init.rb +70 -0
- data/lib/yobi/repository/key.rb +59 -52
- data/lib/yobi/repository/ls.rb +48 -37
- data/lib/yobi/repository/mount.rb +16 -18
- data/lib/yobi/repository/restore.rb +42 -40
- data/lib/yobi/repository/snapshots.rb +2 -29
- data/lib/yobi/repository/stats.rb +47 -4
- data/lib/yobi/repository/tag.rb +50 -35
- data/lib/yobi/repository.rb +8 -43
- data/lib/yobi/restic.rb +73 -32
- data/lib/yobi/restic_output.rb +169 -12
- data/lib/yobi/snapshot.rb +81 -2
- data/lib/yobi/version.rb +2 -2
- data/lib/yobi.rb +2 -1
- data/sig/yobi.rbs +252 -139
- metadata +5 -7
- data/Rakefile +0 -12
data/lib/yobi/restic.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "tempfile"
|
|
4
3
|
require "open3"
|
|
5
4
|
require "json"
|
|
6
5
|
|
|
@@ -164,10 +163,10 @@ module Yobi
|
|
|
164
163
|
|
|
165
164
|
# `restic version`: the installed binary's own version info.
|
|
166
165
|
#
|
|
167
|
-
# @return [
|
|
166
|
+
# @return [Yobi::ResticVersion]
|
|
168
167
|
def version
|
|
169
168
|
execution = run(build_argv("version"), skip_version_check: true)
|
|
170
|
-
|
|
169
|
+
Yobi::ResticVersion.new(parse_version_output(execution[:output].to_s))
|
|
171
170
|
end
|
|
172
171
|
|
|
173
172
|
# `restic cache`: lists and optionally cleans local cache directories.
|
|
@@ -193,13 +192,16 @@ module Yobi
|
|
|
193
192
|
# @param argv [Array<String>]
|
|
194
193
|
# @param extra_env [Hash{String => String}]
|
|
195
194
|
# @param skip_version_check [Boolean] used internally by {#version} to avoid recursing into {#ensure_minimum_version!}
|
|
196
|
-
# @
|
|
195
|
+
# @param output [Yobi::ResticOutput, nil] a caller-configured one (e.g. with its own `transform:`),
|
|
196
|
+
# used instead of a plain one this creates itself; forces streaming even without a block
|
|
197
|
+
# @yieldparam message [Object] each message, live, as the command runs - transformed via
|
|
198
|
+
# `output`'s own `transform:` if it has one, the raw parsed Hash otherwise
|
|
197
199
|
# @return [Hash] `{exit_code:, output:, argv:}` on exit code 0/3
|
|
198
200
|
# @raise [Yobi::RepositoryNotFound, Yobi::RepositoryLocked, Yobi::AuthenticationFailed, Yobi::ResticCommandFailed]
|
|
199
|
-
def run(argv, extra_env: {}, skip_version_check: false, &block)
|
|
201
|
+
def run(argv, extra_env: {}, skip_version_check: false, output: nil, &block)
|
|
200
202
|
ensure_minimum_version! unless skip_version_check
|
|
201
|
-
execution = if block
|
|
202
|
-
execute_with_streaming(argv, extra_env, &block)
|
|
203
|
+
execution = if output || block
|
|
204
|
+
execute_with_streaming(argv, extra_env, output: output, &block)
|
|
203
205
|
else
|
|
204
206
|
execute(argv, extra_env)
|
|
205
207
|
end
|
|
@@ -241,15 +243,14 @@ module Yobi
|
|
|
241
243
|
# @return [Object] the block's own return value, otherwise
|
|
242
244
|
def run_dump(argv, extra_env: {})
|
|
243
245
|
ensure_minimum_version!
|
|
244
|
-
|
|
245
|
-
file.unlink
|
|
246
|
+
output = Yobi::ResticOutput.new
|
|
246
247
|
read_end, write_end = IO.pipe
|
|
247
248
|
read_end.binmode
|
|
248
249
|
|
|
249
|
-
pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: write_end, err: file)
|
|
250
|
+
pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: write_end, err: output.file)
|
|
250
251
|
write_end.close
|
|
251
252
|
|
|
252
|
-
return Yobi::IOHandle.new(read_end, pid: pid,
|
|
253
|
+
return Yobi::IOHandle.new(read_end, pid: pid, output: output, argv: argv) unless block_given?
|
|
253
254
|
|
|
254
255
|
begin
|
|
255
256
|
result = yield read_end
|
|
@@ -258,10 +259,10 @@ module Yobi
|
|
|
258
259
|
_, status = Process.wait2(pid)
|
|
259
260
|
end
|
|
260
261
|
|
|
261
|
-
self.class.dispatch(exit_code: status.exitstatus, output:
|
|
262
|
+
self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv)
|
|
262
263
|
result
|
|
263
264
|
rescue Errno::ENOENT
|
|
264
|
-
file&.close
|
|
265
|
+
output&.file&.close
|
|
265
266
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
266
267
|
end
|
|
267
268
|
|
|
@@ -284,6 +285,20 @@ module Yobi
|
|
|
284
285
|
|
|
285
286
|
private
|
|
286
287
|
|
|
288
|
+
# An old enough Restic ignores --json for `version` entirely and
|
|
289
|
+
# prints plain text instead (e.g. "restic 0.9.6 compiled with
|
|
290
|
+
# go1.13.4 on linux/amd64"), which JSON.parse can't handle.
|
|
291
|
+
VERSION_LINE_PATTERN = /restic\s+(\d+\.\d+\.\d+\S*)/
|
|
292
|
+
|
|
293
|
+
def parse_version_output(raw)
|
|
294
|
+
JSON.parse(raw)
|
|
295
|
+
rescue JSON::ParserError
|
|
296
|
+
match = VERSION_LINE_PATTERN.match(raw)
|
|
297
|
+
raise unless match
|
|
298
|
+
|
|
299
|
+
{"version" => match[1]}
|
|
300
|
+
end
|
|
301
|
+
|
|
287
302
|
def build_argv(*base)
|
|
288
303
|
builder = ArgvBuilder.new
|
|
289
304
|
builder.append(*base)
|
|
@@ -304,38 +319,64 @@ module Yobi
|
|
|
304
319
|
end
|
|
305
320
|
|
|
306
321
|
def execute(argv, extra_env)
|
|
307
|
-
|
|
308
|
-
file.
|
|
309
|
-
pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: file, err: file)
|
|
322
|
+
output = Yobi::ResticOutput.new
|
|
323
|
+
pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: output.file, err: output.file)
|
|
310
324
|
_, status = Process.wait2(pid)
|
|
311
|
-
{exit_code: status.exitstatus, output:
|
|
325
|
+
{exit_code: status.exitstatus, output: output, argv: argv}
|
|
312
326
|
rescue Errno::ENOENT
|
|
313
|
-
file&.close
|
|
327
|
+
output&.file&.close
|
|
314
328
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
315
329
|
end
|
|
316
330
|
|
|
317
|
-
def execute_with_streaming(argv, extra_env)
|
|
318
|
-
|
|
319
|
-
file.unlink
|
|
331
|
+
def execute_with_streaming(argv, extra_env, output: nil)
|
|
332
|
+
output ||= Yobi::ResticOutput.new
|
|
320
333
|
|
|
321
|
-
Open3.
|
|
334
|
+
Open3.popen3(env.merge(extra_env), restic_path, *argv) do |stdin, stdout, stderr, wait_thr|
|
|
322
335
|
stdin.close
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
336
|
+
readers = {stdout => :stdout, stderr => :stderr}
|
|
337
|
+
|
|
338
|
+
until readers.empty?
|
|
339
|
+
ready, = IO.select(readers.keys)
|
|
340
|
+
ready.each do |io|
|
|
341
|
+
line = io.gets
|
|
342
|
+
if line.nil?
|
|
343
|
+
readers.delete(io)
|
|
344
|
+
next
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
parsed = output.write_and_parse(line, readers[io])
|
|
348
|
+
yield parsed if parsed && block_given?
|
|
349
|
+
end
|
|
327
350
|
end
|
|
328
|
-
|
|
351
|
+
|
|
352
|
+
{exit_code: wait_thr.value.exitstatus, output: output, argv: argv}
|
|
329
353
|
end
|
|
330
354
|
rescue Errno::ENOENT
|
|
331
|
-
file&.close
|
|
355
|
+
output&.file&.close
|
|
332
356
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
333
357
|
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# The result of one {Yobi::Restic#version} call.
|
|
361
|
+
class ResticVersion < Yobi::FancyHash
|
|
362
|
+
# @return [String]
|
|
363
|
+
def version
|
|
364
|
+
self["version"]
|
|
365
|
+
end
|
|
334
366
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
367
|
+
# @return [String, nil]
|
|
368
|
+
def go_version
|
|
369
|
+
self["go_version"]
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# @return [String, nil]
|
|
373
|
+
def go_os
|
|
374
|
+
self["go_os"]
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# @return [String, nil]
|
|
378
|
+
def go_arch
|
|
379
|
+
self["go_arch"]
|
|
339
380
|
end
|
|
340
381
|
end
|
|
341
382
|
end
|
data/lib/yobi/restic_output.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
# @private
|
|
7
|
-
MESSAGE_TYPE_LINE_PATTERN = /"message_type"\s*:\s*"(\w+)"/
|
|
3
|
+
require "json"
|
|
4
|
+
require "tempfile"
|
|
8
5
|
|
|
6
|
+
module Yobi
|
|
9
7
|
# Wraps the tempfile a repository operation's stdout+stderr are both
|
|
10
8
|
# written to, with random access so a caller asking only for the last
|
|
11
9
|
# line (see {#last_line}) doesn't have to read the entire output.
|
|
@@ -15,11 +13,19 @@ module Yobi
|
|
|
15
13
|
# Default read window size, in bytes, for {#last_line}.
|
|
16
14
|
DEFAULT_TAIL_CHUNK_SIZE = 4096
|
|
17
15
|
|
|
18
|
-
# @
|
|
16
|
+
# @return [File] a fresh, already-unlinked tempfile of its own
|
|
17
|
+
attr_reader :file
|
|
18
|
+
|
|
19
19
|
# @param tail_chunk_size [Integer] read window size for {#last_line}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# @param transform [Proc, nil] used by {#write_and_parse} during a streaming run to transform
|
|
21
|
+
# each message, and as {#messages}' default transform afterward, when no explicit one is given
|
|
22
|
+
def initialize(tail_chunk_size: DEFAULT_TAIL_CHUNK_SIZE, transform: nil)
|
|
23
|
+
@file = Tempfile.new("yobi-restic-output")
|
|
24
|
+
@file.unlink
|
|
22
25
|
@tail_chunk_size = tail_chunk_size
|
|
26
|
+
@transform = transform
|
|
27
|
+
@index = nil
|
|
28
|
+
@stderr_offsets = []
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
# Byte offsets of every line, grouped by message_type. Built once and
|
|
@@ -69,26 +75,177 @@ module Yobi
|
|
|
69
75
|
@file.each_line(&block)
|
|
70
76
|
end
|
|
71
77
|
|
|
78
|
+
# Every message recorded in {#index}, restricted to one +message_type+
|
|
79
|
+
# if given, in file order. Without an explicit transform block, uses
|
|
80
|
+
# the +transform:+ given to {#initialize} (if any); with neither, just
|
|
81
|
+
# yields/returns the raw parsed Hash. Whenever a transform (explicit
|
|
82
|
+
# or the stored one) applies, wraps the result in a {LazyList}
|
|
83
|
+
# instead of yielding.
|
|
84
|
+
#
|
|
85
|
+
# @param message_type [String, nil] every message, if omitted
|
|
86
|
+
# @yieldparam message [Hash] the raw parsed message, to transform
|
|
87
|
+
# @return [Yobi::ResticOutput::LazyList] if a transform block (or a stored transform) applies
|
|
88
|
+
# @return [Enumerator] of raw Hashes, otherwise
|
|
89
|
+
def messages(message_type = nil, &transform)
|
|
90
|
+
transform ||= @transform
|
|
91
|
+
return raw_messages(message_type) unless transform
|
|
92
|
+
|
|
93
|
+
offsets = message_type ? index[message_type] : index.values.flatten.sort
|
|
94
|
+
LazyList.new(self, offsets, transform)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Every line known to have arrived on stderr rather than stdout during
|
|
98
|
+
# a streaming run (see {#write_and_parse}), in file order.
|
|
99
|
+
#
|
|
100
|
+
# @yieldparam line [String]
|
|
101
|
+
# @return [Enumerator] if no block is given
|
|
102
|
+
def stderr_lines
|
|
103
|
+
return enum_for(:stderr_lines) unless block_given?
|
|
104
|
+
|
|
105
|
+
@stderr_offsets.each { |offset| yield read_line_at(offset) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Called once per line read from a live streaming run's stdout/stderr.
|
|
109
|
+
# Writes it to the file, parses it, and - if it's a JSON message -
|
|
110
|
+
# indexes it by message_type and returns it transformed via the
|
|
111
|
+
# +transform:+ given to {#initialize} (or the raw parsed Hash, if none
|
|
112
|
+
# was given). A non-JSON line's offset is recorded in {#stderr_lines}
|
|
113
|
+
# instead, if +origin+ is +:stderr+; either way, returns nil.
|
|
114
|
+
#
|
|
115
|
+
# @param line [String]
|
|
116
|
+
# @param origin [:stdout, :stderr]
|
|
117
|
+
# @return [Object, nil]
|
|
118
|
+
def write_and_parse(line, origin)
|
|
119
|
+
offset = @file.pos
|
|
120
|
+
@file.write(line)
|
|
121
|
+
|
|
122
|
+
parsed = begin
|
|
123
|
+
JSON.parse(line)
|
|
124
|
+
rescue JSON::ParserError
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if parsed
|
|
129
|
+
(@index ||= Hash.new { |hash, message_type| hash[message_type] = [] })[parsed["message_type"]] << offset if parsed["message_type"]
|
|
130
|
+
@transform ? @transform.call(parsed) : parsed
|
|
131
|
+
elsif origin == :stderr
|
|
132
|
+
@stderr_offsets << offset
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
72
137
|
# @return [String] the full captured output
|
|
73
138
|
def to_s
|
|
74
139
|
@file.rewind
|
|
75
140
|
@file.read
|
|
76
141
|
end
|
|
77
142
|
|
|
143
|
+
# @return [String]
|
|
144
|
+
def inspect
|
|
145
|
+
"#<#{self.class} #{@file.size} bytes>"
|
|
146
|
+
end
|
|
147
|
+
|
|
78
148
|
private
|
|
79
149
|
|
|
80
|
-
def
|
|
81
|
-
|
|
150
|
+
def raw_messages(message_type)
|
|
151
|
+
return enum_for(:raw_messages, message_type) unless block_given?
|
|
152
|
+
|
|
153
|
+
if message_type
|
|
154
|
+
index[message_type].each { |offset| yield JSON.parse(read_line_at(offset)) }
|
|
155
|
+
else
|
|
156
|
+
each_parsed_line { |_offset, parsed| yield parsed }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def each_parsed_line
|
|
82
161
|
@file.rewind
|
|
83
162
|
loop do
|
|
84
163
|
offset = @file.pos
|
|
85
164
|
line = @file.gets
|
|
86
165
|
break unless line
|
|
87
166
|
|
|
88
|
-
|
|
89
|
-
|
|
167
|
+
parsed = begin
|
|
168
|
+
JSON.parse(line)
|
|
169
|
+
rescue JSON::ParserError
|
|
170
|
+
nil
|
|
171
|
+
end
|
|
172
|
+
yield(offset, parsed) if parsed && parsed["message_type"]
|
|
90
173
|
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_index
|
|
177
|
+
index = Hash.new { |hash, message_type| hash[message_type] = [] }
|
|
178
|
+
each_parsed_line { |offset, parsed| index[parsed["message_type"]] << offset }
|
|
91
179
|
index
|
|
92
180
|
end
|
|
181
|
+
|
|
182
|
+
# A lazily-transformed view over a set of {#index} offsets, printing
|
|
183
|
+
# like a plain Array (bounded to a preview, unlike one). Returned by
|
|
184
|
+
# {ResticOutput#messages} when given a transform block.
|
|
185
|
+
# #inspect/#pretty_print's preview is memoized, and #each reuses it
|
|
186
|
+
# rather than re-reading/re-transforming those same offsets again
|
|
187
|
+
# when the caller iterates the whole thing.
|
|
188
|
+
#
|
|
189
|
+
# @private
|
|
190
|
+
class LazyList
|
|
191
|
+
include Enumerable
|
|
192
|
+
|
|
193
|
+
# Items #inspect/#pretty_print show before truncating with "...".
|
|
194
|
+
# One louder than ActiveRecord::Relation#inspect's own preview size.
|
|
195
|
+
INSPECT_PREVIEW_SIZE = 11
|
|
196
|
+
|
|
197
|
+
# @param output [Yobi::ResticOutput]
|
|
198
|
+
# @param offsets [Array<Integer>]
|
|
199
|
+
# @param transform [Proc] applied to each offset's raw parsed Hash
|
|
200
|
+
def initialize(output, offsets, transform)
|
|
201
|
+
@output = output
|
|
202
|
+
@offsets = offsets
|
|
203
|
+
@transform = transform
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @yieldparam item [Object]
|
|
207
|
+
# @return [Enumerator] if no block is given
|
|
208
|
+
def each
|
|
209
|
+
return enum_for(:each) unless block_given?
|
|
210
|
+
|
|
211
|
+
preview.each { |item| yield item }
|
|
212
|
+
@offsets[preview.size..].each { |offset| yield transform_at(offset) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The number of messages, known upfront from {#index} - unlike
|
|
216
|
+
# +Enumerable#count+, doesn't read/transform a single one to answer.
|
|
217
|
+
#
|
|
218
|
+
# @return [Integer]
|
|
219
|
+
def size
|
|
220
|
+
@offsets.size
|
|
221
|
+
end
|
|
222
|
+
alias_method :length, :size
|
|
223
|
+
|
|
224
|
+
# @return [String]
|
|
225
|
+
def inspect
|
|
226
|
+
shown = preview.first(INSPECT_PREVIEW_SIZE).map(&:inspect).join(", ")
|
|
227
|
+
shown += ", ..." if preview.size > INSPECT_PREVIEW_SIZE
|
|
228
|
+
"[#{shown}]"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# @return [void]
|
|
232
|
+
def pretty_print(q)
|
|
233
|
+
q.group(1, "[", "]") do
|
|
234
|
+
q.seplist(preview.first(INSPECT_PREVIEW_SIZE)) { |item| q.pp item }
|
|
235
|
+
q.text ", ..." if preview.size > INSPECT_PREVIEW_SIZE
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
private
|
|
240
|
+
|
|
241
|
+
# @return [Array<Object>]
|
|
242
|
+
def preview
|
|
243
|
+
@preview ||= @offsets.first(INSPECT_PREVIEW_SIZE + 1).map { |offset| transform_at(offset) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def transform_at(offset)
|
|
247
|
+
@transform.call(JSON.parse(@output.read_line_at(offset)))
|
|
248
|
+
end
|
|
249
|
+
end
|
|
93
250
|
end
|
|
94
251
|
end
|
data/lib/yobi/snapshot.rb
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "time"
|
|
4
|
-
require "delegate"
|
|
5
4
|
|
|
6
5
|
module Yobi
|
|
7
6
|
# One snapshot, as Restic reports it. Shared across {Yobi::Repository#snapshots},
|
|
8
7
|
# {Yobi::Repository#ls}, and {Yobi::Repository#forget}'s keep/remove entries.
|
|
9
|
-
class Snapshot <
|
|
8
|
+
class Snapshot < Yobi::FancyHash
|
|
10
9
|
# @return [String]
|
|
11
10
|
def id
|
|
12
11
|
self["id"]
|
|
@@ -41,5 +40,85 @@ module Yobi
|
|
|
41
40
|
def parent_id
|
|
42
41
|
self["parent"]
|
|
43
42
|
end
|
|
43
|
+
|
|
44
|
+
# @return [Yobi::SnapshotSummary]
|
|
45
|
+
def summary
|
|
46
|
+
@summary ||= SnapshotSummary.new(self["summary"] || {})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The `"summary"` field of a {Yobi::Snapshot}, the stats recorded when it
|
|
51
|
+
# was created.
|
|
52
|
+
# https://restic.readthedocs.io/en/stable/075_scripting.html#snapshotsummary-object
|
|
53
|
+
class SnapshotSummary < Yobi::FancyHash
|
|
54
|
+
# @return [Time, nil]
|
|
55
|
+
def backup_start
|
|
56
|
+
@backup_start ||= Time.parse(self["backup_start"]) if self["backup_start"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Time, nil]
|
|
60
|
+
def backup_end
|
|
61
|
+
@backup_end ||= Time.parse(self["backup_end"]) if self["backup_end"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Integer]
|
|
65
|
+
def files_new
|
|
66
|
+
self["files_new"] || 0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
def files_changed
|
|
71
|
+
self["files_changed"] || 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Integer]
|
|
75
|
+
def files_unmodified
|
|
76
|
+
self["files_unmodified"] || 0
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @return [Integer]
|
|
80
|
+
def dirs_new
|
|
81
|
+
self["dirs_new"] || 0
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Integer]
|
|
85
|
+
def dirs_changed
|
|
86
|
+
self["dirs_changed"] || 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Integer]
|
|
90
|
+
def dirs_unmodified
|
|
91
|
+
self["dirs_unmodified"] || 0
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [Integer]
|
|
95
|
+
def data_blobs
|
|
96
|
+
self["data_blobs"] || 0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Integer]
|
|
100
|
+
def tree_blobs
|
|
101
|
+
self["tree_blobs"] || 0
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Integer]
|
|
105
|
+
def data_added
|
|
106
|
+
self["data_added"] || 0
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @return [Integer]
|
|
110
|
+
def data_added_packed
|
|
111
|
+
self["data_added_packed"] || 0
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @return [Integer]
|
|
115
|
+
def total_files_processed
|
|
116
|
+
self["total_files_processed"] || 0
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @return [Integer]
|
|
120
|
+
def total_bytes_processed
|
|
121
|
+
self["total_bytes_processed"] || 0
|
|
122
|
+
end
|
|
44
123
|
end
|
|
45
124
|
end
|
data/lib/yobi/version.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# A Ruby library for the Restic backup
|
|
3
|
+
# A Ruby library for the Restic backup program, wrapping the `restic` CLI in
|
|
4
4
|
# plain Ruby objects instead of shelling out to flags and raw JSON by hand.
|
|
5
5
|
module Yobi
|
|
6
6
|
# @return [String]
|
|
7
|
-
VERSION = "0.
|
|
7
|
+
VERSION = "0.2.0"
|
|
8
8
|
end
|
data/lib/yobi.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "yobi/version"
|
|
4
|
+
require_relative "yobi/fancy_hash"
|
|
4
5
|
require_relative "yobi/errors"
|
|
5
6
|
require_relative "yobi/argv_builder"
|
|
6
7
|
require_relative "yobi/restic_output"
|
|
7
8
|
require_relative "yobi/io_handle"
|
|
8
|
-
require_relative "yobi/
|
|
9
|
+
require_relative "yobi/mount_handle"
|
|
9
10
|
require_relative "yobi/restic"
|
|
10
11
|
require_relative "yobi/snapshot"
|
|
11
12
|
require_relative "yobi/repository"
|