yobi 0.3.0 → 1.0.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.
@@ -2,14 +2,13 @@
2
2
 
3
3
  module Yobi
4
4
  # A live Restic process streaming raw bytes to its own stdout, returned by
5
- # {Yobi::Restic#run_dump} when no block is given. Satisfies Rack's Body
6
- # contract (`#each`, `#close`).
5
+ # Yobi::Restic#run_dump when no block is given. Satisfies Rack's Body
6
+ # contract (+#each+, +#close+).
7
7
  #
8
- # Unlike the block form, nothing closes this automatically. Call {#close}
9
- # yourself once done reading, in an `ensure`, so it still runs if reading
8
+ # Unlike the block form, nothing closes this automatically. Call #close
9
+ # yourself once done reading, in an +ensure+, so it still runs if reading
10
10
  # raises:
11
11
  #
12
- # @example
13
12
  # handle = repo.dump(snapshot_id: "latest", file: "/etc/hosts")
14
13
  # begin
15
14
  # IO.copy_stream(handle.io, "/tmp/hosts")
@@ -17,13 +16,12 @@ module Yobi
17
16
  # handle.close
18
17
  # end
19
18
  class IOHandle
20
- # @return [IO] the readable end of the pipe Restic's stdout is wired to
19
+ # The readable end of the pipe Restic's stdout is wired to.
21
20
  attr_reader :io
22
- # @return [Integer] the Restic process's pid
21
+ # The Restic process's pid.
23
22
  attr_reader :pid
24
23
 
25
- # @private
26
- def initialize(io, pid:, output:, argv:)
24
+ def initialize(io, pid:, output:, argv:) # :nodoc:
27
25
  @io = io
28
26
  @pid = pid
29
27
  @output = output
@@ -31,20 +29,17 @@ module Yobi
31
29
  @closed = false
32
30
  end
33
31
 
34
- # @return [Boolean]
32
+ # +true+ once #close has run.
35
33
  def closed?
36
34
  @closed
37
35
  end
38
36
 
39
- # @return [String]
40
37
  def inspect
41
38
  "#<#{self.class} pid=#{pid} closed=#{closed?}>"
42
39
  end
43
40
 
44
41
  # Reaps the Restic process and raises based on its exit code. Safe to
45
42
  # call more than once.
46
- #
47
- # @return [void]
48
43
  def close
49
44
  return if @closed
50
45
 
@@ -54,10 +49,7 @@ module Yobi
54
49
  Restic.dispatch(exit_code: status.exitstatus, output: @output, argv: @argv)
55
50
  end
56
51
 
57
- # Yields binary-safe chunks of {#io} until EOF, then calls {#close}.
58
- #
59
- # @yieldparam chunk [String]
60
- # @return [void]
52
+ # Yields binary-safe chunks of #io until EOF, then calls #close.
61
53
  def each
62
54
  loop do
63
55
  yield @io.readpartial(64 * 1024)
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yobi
4
- # A live Restic mount process, returned by {Yobi::Restic#run_mount} once
4
+ # A live Restic mount process, returned by Yobi::Restic#run_mount once
5
5
  # Restic has reported itself ready. The mounted filesystem itself is
6
- # browsed with ordinary file I/O at {#mountpoint}; this object only
6
+ # browsed with ordinary file I/O at #mountpoint; this object only
7
7
  # manages the Restic process's lifetime.
8
8
  class MountHandle
9
- # @return [String] the path the repository is mounted at
9
+ # The path the repository is mounted at.
10
10
  attr_reader :mountpoint
11
11
 
12
- # @private
13
- def initialize(wait_thr:, mountpoint:, pipe:, output:, argv:)
12
+ def initialize(wait_thr:, mountpoint:, pipe:, output:, argv:) # :nodoc:
14
13
  @wait_thr = wait_thr
15
14
  @mountpoint = mountpoint
16
15
  @pipe = pipe
@@ -19,17 +18,16 @@ module Yobi
19
18
  @stopped = false
20
19
  end
21
20
 
22
- # @return [Integer] the Restic process's pid
21
+ # The Restic process's pid.
23
22
  def pid
24
23
  @wait_thr.pid
25
24
  end
26
25
 
27
- # @return [Boolean]
26
+ # +true+ once #stop has run.
28
27
  def stopped?
29
28
  @stopped
30
29
  end
31
30
 
32
- # @return [String]
33
31
  def inspect
34
32
  "#<#{self.class} pid=#{pid} mountpoint=#{mountpoint.inspect} stopped=#{stopped?}>"
35
33
  end
@@ -37,9 +35,7 @@ module Yobi
37
35
  # Sends the Restic process SIGINT, waits for it to unmount and exit,
38
36
  # then raises based on its exit code. Safe to call more than once, and
39
37
  # safe to call after the mount has already been stopped externally
40
- # (e.g. via `kill -INT` or the OS's own `umount`/`fusermount`).
41
- #
42
- # @return [void]
38
+ # (e.g. via +kill -INT+ or the OS's own +umount+/+fusermount+).
43
39
  def stop
44
40
  return if @stopped
45
41
 
@@ -5,41 +5,46 @@ require "shellwords"
5
5
 
6
6
  module Yobi
7
7
  class Repository
8
- # `restic backup`: creates a new snapshot from a source path.
8
+ # +restic backup+: creates a new snapshot from a source path.
9
9
  #
10
- # @param source [String, Array] a path to back up, or a
11
- # `[:stdin_from_command, command]`/`[:stdin_from_command, command, filename]`
12
- # tuple. Restic spawns and executes `command` itself (a String,
13
- # tokenized with `Shellwords.split`, or an Array of already-discrete
14
- # arguments), capturing its stdout as the backup content
15
- # @param excludes [Array<String>, String] exclude files matching these glob patterns
16
- # @param exclude_files [Array<String>, String] path(s) to file(s) listing exclude patterns, one per line
17
- # @param exclude_if_present [Array<String>, String] skip a directory containing a marker file with this name (optionally `name:content`)
18
- # @param exclude_larger_than [String, nil] skip files larger than this size, e.g. `"1G"`
19
- # @param files_from [Array<String>, String] read the files/dirs to back up from a file, one per line
20
- # @param files_from_raw [Array<String>, String] like `files_from`, NUL-separated
21
- # @param files_from_verbatim [Array<String>, String] like `files_from`, paths taken literally with no glob expansion
22
- # @param iexcludes [Array<String>, String] like `excludes`, case-insensitive
23
- # @param iexclude_files [Array<String>, String] like `exclude_files`, case-insensitive
24
- # @param tags [Array<String>, String] tags to attach to the new snapshot
25
- # @param dry_run [Boolean] report what would happen without doing it
26
- # @param exclude_caches [Boolean] skip directories containing a CACHEDIR.TAG marker
27
- # @param exclude_cloud_files [Boolean] skip files not fully present on disk (e.g. OneDrive placeholders)
28
- # @param force [Boolean] back up unchanged files instead of skipping them
29
- # @param group_by [String, nil] grouping used to find the parent snapshot, e.g. `"host,paths"`
30
- # @param host [String, nil] hostname to record on the new snapshot, instead of the OS hostname
31
- # @param ignore_ctime [Boolean] relax change detection to ignore ctime
32
- # @param ignore_inode [Boolean] relax change detection to ignore inode number
33
- # @param no_scan [Boolean] skip the pre-backup scan (disables percentage progress)
34
- # @param one_file_system [Boolean] don't cross filesystem boundaries
35
- # @param parent [String, nil] snapshot ID to use as the parent instead of the latest one
36
- # @param read_concurrency [Integer, nil] number of concurrent file reads
37
- # @param skip_if_unchanged [Boolean] don't create a snapshot if nothing changed
38
- # @param time [String, nil] timestamp to record instead of now
39
- # @param verbose [Boolean] stream a {Yobi::BackupVerboseStatus} per file to the block
40
- # @param with_atime [Boolean] also store files' access times
41
- # @yieldparam message [Yobi::BackupStatus, Yobi::BackupError, Yobi::BackupVerboseStatus, Yobi::BackupSummary]
42
- # @return [Yobi::BackupOutcome]
10
+ # +source:+ is either a path to back up, or a
11
+ # +[:stdin_from_command, command]+ / +[:stdin_from_command, command, filename]+
12
+ # tuple. Restic spawns and executes +command+ itself (a String tokenized
13
+ # with +Shellwords.split+, or an Array of already-discrete arguments),
14
+ # capturing its stdout as the backup content.
15
+ #
16
+ # +excludes:+, +exclude_files:+, +exclude_if_present:+, +iexcludes:+ and
17
+ # +iexclude_files:+ each accept a single value or an Array. +i+-prefixed
18
+ # variants are case-insensitive.
19
+ #
20
+ # +exclude_larger_than:+ skips files larger than a given size (e.g.
21
+ # +"1G"+). +exclude_caches:+ skips directories containing a
22
+ # +CACHEDIR.TAG+ marker. +exclude_cloud_files:+ skips files not fully
23
+ # present on disk (e.g. OneDrive placeholders).
24
+ #
25
+ # +files_from:+, +files_from_raw:+ and +files_from_verbatim:+ read the
26
+ # files/dirs to back up from a file, one per line - +_raw+ NUL-separated,
27
+ # +_verbatim+ taken literally with no glob expansion.
28
+ #
29
+ # +tags:+ attaches tags to the new snapshot. +host:+ overrides the OS
30
+ # hostname recorded on it. +time:+ overrides its creation timestamp.
31
+ # +parent:+ pins the parent snapshot ID; +group_by:+ (e.g. +"host,paths"+)
32
+ # picks the grouping used to find the parent otherwise.
33
+ #
34
+ # +dry_run:+ reports what would happen without doing it. +force:+ backs
35
+ # up unchanged files instead of skipping them. +skip_if_unchanged:+
36
+ # doesn't create a snapshot if nothing changed. +ignore_ctime:+ and
37
+ # +ignore_inode:+ relax change detection. +with_atime:+ also stores
38
+ # files' access times.
39
+ #
40
+ # +one_file_system:+ doesn't cross filesystem boundaries. +no_scan:+
41
+ # skips the pre-backup scan (which disables percentage progress).
42
+ # +read_concurrency:+ sets the number of concurrent file reads.
43
+ #
44
+ # When +verbose: true+, a Yobi::BackupVerboseStatus is streamed to the
45
+ # block per file. The block also receives Yobi::BackupStatus,
46
+ # Yobi::BackupError, and Yobi::BackupSummary messages as they arrive.
47
+ # Returns a Yobi::BackupOutcome.
43
48
  def backup(source:, excludes: [], exclude_files: [], exclude_if_present: [], exclude_larger_than: nil,
44
49
  files_from: [], files_from_raw: [], files_from_verbatim: [], iexcludes: [], iexclude_files: [],
45
50
  tags: [], dry_run: false, exclude_caches: false, exclude_cloud_files: false, force: false,
@@ -107,16 +112,7 @@ module Yobi
107
112
  end
108
113
  end
109
114
 
110
- # Classifies a raw backup message Hash by its own message_type, wrapping
111
- # it in the matching typed class. Used both by {Yobi::Repository#backup}
112
- # (as the {Yobi::ResticOutput} `transform:` for a live streaming run) and
113
- # by {Yobi::BackupOutcome}'s own post-hoc accessors.
114
- #
115
- # @private
116
- module BackupMessageWrapper
117
- # @param raw [Hash]
118
- # @return [Yobi::BackupStatus, Yobi::BackupError, Yobi::BackupVerboseStatus, Yobi::BackupSummary, Hash]
119
- # the raw Hash itself for a message_type this version of Yobi doesn't recognize
115
+ module BackupMessageWrapper # :nodoc:
120
116
  def self.call(raw)
121
117
  case raw["message_type"]
122
118
  when "status" then BackupStatus.new(raw)
@@ -128,36 +124,31 @@ module Yobi
128
124
  end
129
125
  end
130
126
 
131
- # The outcome of one {Yobi::Repository#backup} call.
127
+ # The outcome of one Yobi::Repository#backup call.
132
128
  class BackupOutcome
133
- # @private
134
- COMMAND_OUTPUT_LINE_PATTERN = /\Asubprocess [^:]+: (.*)/
129
+ COMMAND_OUTPUT_LINE_PATTERN = /\Asubprocess [^:]+: (.*)/ # :nodoc:
135
130
 
136
- # @return [Yobi::ResticOutput]
131
+ # The Yobi::ResticOutput backing this outcome.
137
132
  attr_reader :output
138
133
 
139
- # @private
140
- def initialize(execution)
134
+ def initialize(execution) # :nodoc:
141
135
  @output = execution[:output]
142
136
  end
143
137
 
144
- # @return [Enumerable<Yobi::BackupError>]
138
+ # Every Yobi::BackupError message from the run.
145
139
  def errors
146
140
  @errors ||= output.messages("error")
147
141
  end
148
142
 
149
143
  # Every message from the run, in file order, each wrapped in its own
150
- # {Yobi::BackupStatus}/{Yobi::BackupError}/{Yobi::BackupVerboseStatus}/{Yobi::BackupSummary}.
151
- #
152
- # @return [Enumerable<Yobi::BackupStatus, Yobi::BackupError, Yobi::BackupVerboseStatus, Yobi::BackupSummary>]
144
+ # Yobi::BackupStatus / Yobi::BackupError / Yobi::BackupVerboseStatus /
145
+ # Yobi::BackupSummary.
153
146
  def messages
154
147
  @messages ||= output.messages
155
148
  end
156
149
 
157
- # The `source: [:stdin_from_command, ...]` subprocess's own stderr, if any, de-prefixed.
158
- #
159
- # @yieldparam line [String]
160
- # @return [Enumerator] if no block is given
150
+ # The +source: [:stdin_from_command, ...]+ subprocess's own stderr, if
151
+ # any, de-prefixed.
161
152
  def command_output
162
153
  return enum_for(:command_output) unless block_given?
163
154
 
@@ -167,13 +158,12 @@ module Yobi
167
158
  end
168
159
  end
169
160
 
170
- # @return [Yobi::BackupSummary] Restic's own `"summary"` fields
161
+ # The Yobi::BackupSummary of Restic's own +"summary"+ fields.
171
162
  def summary
172
163
  @summary ||= output.messages("summary").first || BackupSummary.new({})
173
164
  end
174
165
  alias_method :report, :summary
175
166
 
176
- # @return [void]
177
167
  def pretty_print(q)
178
168
  q.object_group(self) do
179
169
  q.breakable
@@ -184,119 +174,98 @@ module Yobi
184
174
  end
185
175
  end
186
176
 
187
- # The `"summary"` message from a backup run, the final result once the
188
- # command finishes. Dispatched to {Yobi::Repository#backup}'s block, and
189
- # also what {Yobi::BackupOutcome#summary} returns.
190
- # https://restic.readthedocs.io/en/stable/075_scripting.html#summary
177
+ # The +"summary"+ message from a backup run, the final result once the
178
+ # command finishes. Dispatched to Yobi::Repository#backup's block, and
179
+ # also what Yobi::BackupOutcome#summary returns. See
180
+ # https://restic.readthedocs.io/en/stable/075_scripting.html#summary.
191
181
  class BackupSummary < Yobi::FancyHash
192
- # @return [Time, nil]
193
182
  def backup_start
194
183
  @backup_start ||= Time.parse(self["backup_start"]) if self["backup_start"]
195
184
  end
196
185
 
197
- # @return [Time, nil]
198
186
  def backup_end
199
187
  @backup_end ||= Time.parse(self["backup_end"]) if self["backup_end"]
200
188
  end
201
189
  end
202
190
 
203
- # One `"status"` message from a live backup run.
204
- # https://restic.readthedocs.io/en/stable/075_scripting.html#status
191
+ # One +"status"+ message from a live backup run. See
192
+ # https://restic.readthedocs.io/en/stable/075_scripting.html#status.
205
193
  class BackupStatus < Yobi::FancyHash
206
- # @return [Float, nil]
207
194
  def percent_done
208
195
  self["percent_done"]
209
196
  end
210
197
 
211
- # @return [Integer, nil]
212
198
  def total_files
213
199
  self["total_files"]
214
200
  end
215
201
 
216
- # @return [Integer, nil]
217
202
  def files_done
218
203
  self["files_done"]
219
204
  end
220
205
 
221
- # @return [Integer, nil]
222
206
  def total_bytes
223
207
  self["total_bytes"]
224
208
  end
225
209
 
226
- # @return [Integer, nil]
227
210
  def bytes_done
228
211
  self["bytes_done"]
229
212
  end
230
213
 
231
- # @return [Array<String>]
232
214
  def current_files
233
215
  self["current_files"] || []
234
216
  end
235
217
 
236
- # @return [Integer]
237
218
  def error_count
238
219
  self["error_count"] || 0
239
220
  end
240
221
  end
241
222
 
242
- # One `"error"` message from a backup run.
243
- # https://restic.readthedocs.io/en/stable/075_scripting.html#error
223
+ # One +"error"+ message from a backup run.
244
224
  class BackupError < Yobi::FancyHash
245
- # @return [String, nil]
246
225
  def message
247
226
  dig("error", "message")
248
227
  end
249
228
 
250
- # @return [String, nil]
251
229
  def during
252
230
  self["during"]
253
231
  end
254
232
 
255
- # @return [String, nil]
256
233
  def item
257
234
  self["item"]
258
235
  end
259
236
  end
260
237
 
261
- # One `"verbose_status"` message from a backup run, one per file. Only
262
- # emitted when `verbose: true` is passed to {Yobi::Repository#backup}.
238
+ # One +"verbose_status"+ message from a backup run, one per file. Only
239
+ # emitted when +verbose: true+ is passed to Yobi::Repository#backup.
263
240
  class BackupVerboseStatus < Yobi::FancyHash
264
- # @return [String]
265
241
  def action
266
242
  self["action"]
267
243
  end
268
244
 
269
- # @return [String]
270
245
  def item
271
246
  self["item"]
272
247
  end
273
248
 
274
- # @return [Integer]
275
249
  def duration
276
250
  self["duration"]
277
251
  end
278
252
 
279
- # @return [Integer]
280
253
  def data_size
281
254
  self["data_size"]
282
255
  end
283
256
 
284
- # @return [Integer]
285
257
  def data_size_in_repo
286
258
  self["data_size_in_repo"]
287
259
  end
288
260
 
289
- # @return [Integer]
290
261
  def metadata_size
291
262
  self["metadata_size"]
292
263
  end
293
264
 
294
- # @return [Integer]
295
265
  def metadata_size_in_repo
296
266
  self["metadata_size_in_repo"]
297
267
  end
298
268
 
299
- # @return [Integer]
300
269
  def total_files
301
270
  self["total_files"]
302
271
  end
@@ -1,55 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Yobi
6
4
  class Repository
7
- # `restic cat config`: the repository's own config document.
8
- #
9
- # @return [Hash]
10
- # @raise [Yobi::RepositoryNotFound, Yobi::AuthenticationFailed]
5
+ # +restic cat config+: the repository's own config document, as a Hash.
6
+ # Raises Yobi::RepositoryNotFound or Yobi::AuthenticationFailed.
11
7
  def cat_config
12
8
  execution = run_restic(build_argv("cat", "config"))
13
- JSON.parse(execution[:output].to_s)
9
+ parse_json_output(execution)
14
10
  end
15
11
  alias_method :config, :cat_config
16
12
 
17
- # `restic cat snapshot ID`: one snapshot's own raw stored record.
18
- #
19
- # @param id [String, Yobi::Snapshot]
20
- # @return [Hash]
13
+ # +restic cat snapshot ID+: one snapshot's own raw stored record, as a
14
+ # Hash. +id+ can be a String or a Yobi::Snapshot.
21
15
  def cat_snapshot(id)
22
16
  id = id.id if id.is_a?(Yobi::Snapshot)
23
17
  execution = run_restic(build_argv("cat", "snapshot", id))
24
- JSON.parse(execution[:output].to_s)
18
+ parse_json_output(execution)
25
19
  end
26
20
 
27
- # `restic cat index ID`: one index file's own raw contents. IDs come
28
- # from `list(:index)`.
29
- #
30
- # @param id [String]
31
- # @return [Hash]
21
+ # +restic cat index ID+: one index file's own raw contents, as a Hash.
22
+ # IDs come from +list(:index)+.
32
23
  def cat_index(id)
33
24
  execution = run_restic(build_argv("cat", "index", id))
34
- JSON.parse(execution[:output].to_s)
25
+ parse_json_output(execution)
35
26
  end
36
27
 
37
- # `restic cat key ID`: one key's own raw stored record.
38
- #
39
- # @param id [String, Yobi::Key]
40
- # @return [Hash]
28
+ # +restic cat key ID+: one key's own raw stored record, as a Hash. +id+
29
+ # can be a String or a Yobi::Key.
41
30
  def cat_key(id)
42
31
  id = id.id if id.is_a?(Yobi::Key)
43
32
  execution = run_restic(build_argv("cat", "key", id))
44
- JSON.parse(execution[:output].to_s)
33
+ parse_json_output(execution)
45
34
  end
46
35
 
47
- # `restic cat tree snapshot:subfolder`: the raw tree object at a
48
- # snapshot's root, or at `subfolder` within it.
49
- #
50
- # @param snapshot_id [String, Yobi::Snapshot] also accepts Restic's own `"snapshotID:subfolder"` form directly
51
- # @param subfolder [String, nil]
52
- # @return [Hash]
36
+ # +restic cat tree snapshot:subfolder+: the raw tree object at a
37
+ # snapshot's root, or at +subfolder+ within it. +snapshot_id+ can be a
38
+ # String, a Yobi::Snapshot, or Restic's own +"snapshotID:subfolder"+
39
+ # form directly.
53
40
  def cat_tree(snapshot_id, subfolder: nil)
54
41
  snapshot_id = snapshot_id.id if snapshot_id.is_a?(Yobi::Snapshot)
55
42
  target = if subfolder.nil?
@@ -58,40 +45,31 @@ module Yobi
58
45
  "#{snapshot_id}:#{subfolder}"
59
46
  end
60
47
  execution = run_restic(build_argv("cat", "tree", target))
61
- JSON.parse(execution[:output].to_s)
48
+ parse_json_output(execution)
62
49
  end
63
50
 
64
- # `restic cat pack ID`: one pack file's raw, still-encrypted bytes.
65
- # IDs come from `list(:packs)`. Same block/handle shape as {#dump}.
66
- #
67
- # @param id [String]
68
- # @yieldparam io [IO]
69
- # @return [Yobi::IOHandle] if no block is given
51
+ # +restic cat pack ID+: one pack file's raw, still-encrypted bytes. IDs
52
+ # come from +list(:packs)+. Same block/handle shape as #dump: without a
53
+ # block, returns a Yobi::IOHandle.
70
54
  def cat_pack(id, &block)
71
55
  run_restic_dump(build_argv("cat", "pack", id), &block)
72
56
  end
73
57
 
74
- # `restic cat blob ID`: one data blob's raw, decrypted bytes. IDs come
75
- # from {#cat_tree}'s own node `"content"` arrays. Same block/handle
76
- # shape as {#dump}.
77
- #
78
- # @param id [String]
79
- # @yieldparam io [IO]
80
- # @return [Yobi::IOHandle] if no block is given
58
+ # +restic cat blob ID+: one data blob's raw, decrypted bytes. IDs come
59
+ # from #cat_tree's own node +"content"+ arrays. Same block/handle shape
60
+ # as #dump.
81
61
  def cat_blob(id, &block)
82
62
  run_restic_dump(build_argv("cat", "blob", id), &block)
83
63
  end
84
64
 
85
- # `restic cat masterkey`: this repository's own encryption/MAC key
86
- # material. Extremely sensitive: this is the actual key, not a
87
- # redacted reference to it, and there is no operation that rotates it;
88
- # every other key/password management method here only manages
89
- # different ways to unlock this same master key.
90
- #
91
- # @return [Hash]
65
+ # +restic cat masterkey+: this repository's own encryption/MAC key
66
+ # material. Extremely sensitive: this is the actual key, not a redacted
67
+ # reference to it, and there is no operation that rotates it; every
68
+ # other key/password management method here only manages different ways
69
+ # to unlock this same master key.
92
70
  def cat_masterkey_and_game_over_if_this_leaks
93
71
  execution = run_restic(build_argv("cat", "masterkey"))
94
- JSON.parse(execution[:output].to_s)
72
+ parse_json_output(execution)
95
73
  end
96
74
  end
97
75
  end
@@ -2,15 +2,16 @@
2
2
 
3
3
  module Yobi
4
4
  class Repository
5
- # `restic check`: tests the repository for errors.
5
+ # +restic check+: tests the repository for errors.
6
6
  #
7
- # @param hosts [Array<String>, String] filter by hostname(s)
8
- # @param paths [Array<String>, String] filter by originally backed-up path(s)
9
- # @param read_data [Boolean] read and verify pack file contents, not just structure
10
- # @param read_data_subset [String, nil] read and verify only a subset of packs, e.g. `"5%"`
11
- # @param tags [Array<String>, String] filter by tag(s)
12
- # @param with_cache [Boolean] use the local cache
13
- # @return [Yobi::CheckOutcome]
7
+ # +hosts:+, +paths:+ and +tags:+ each accept a single value or an Array
8
+ # to filter by.
9
+ #
10
+ # +read_data:+ reads and verifies pack file contents, not just structure.
11
+ # +read_data_subset:+ narrows that to a subset of packs, e.g. +"5%"+.
12
+ # +with_cache:+ uses the local cache.
13
+ #
14
+ # Returns a Yobi::CheckOutcome.
14
15
  def check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false)
15
16
  argv = build_argv("check") do |a|
16
17
  a.repeat_flag(:host, hosts)
@@ -26,16 +27,7 @@ module Yobi
26
27
  end
27
28
  end
28
29
 
29
- # Classifies a raw check message Hash by its own message_type, wrapping
30
- # it in the matching typed class. Used both as the {Yobi::ResticOutput}
31
- # `transform:` for {Yobi::Repository#check} and by {Yobi::CheckOutcome}'s
32
- # own post-hoc accessors.
33
- #
34
- # @private
35
- module CheckMessageWrapper
36
- # @param raw [Hash]
37
- # @return [Yobi::CheckError, Yobi::CheckSummary, Hash]
38
- # the raw Hash itself for a message_type this version of Yobi doesn't recognize
30
+ module CheckMessageWrapper # :nodoc:
39
31
  def self.call(raw)
40
32
  case raw["message_type"]
41
33
  when "error" then CheckError.new(raw)
@@ -45,28 +37,26 @@ module Yobi
45
37
  end
46
38
  end
47
39
 
48
- # The outcome of one {Yobi::Repository#check} call.
40
+ # The outcome of one Yobi::Repository#check call.
49
41
  class CheckOutcome
50
- # @return [Yobi::ResticOutput]
42
+ # The Yobi::ResticOutput backing this outcome.
51
43
  attr_reader :output
52
44
 
53
- # @private
54
- def initialize(execution)
45
+ def initialize(execution) # :nodoc:
55
46
  @output = execution[:output]
56
47
  end
57
48
 
58
- # @return [Yobi::CheckSummary] Restic's own `"summary"` fields
49
+ # The Yobi::CheckSummary of Restic's own +"summary"+ fields.
59
50
  def summary
60
51
  @summary ||= output.messages("summary").first || CheckSummary.new({})
61
52
  end
62
53
  alias_method :report, :summary
63
54
 
64
- # @return [Enumerable<Yobi::CheckError>]
55
+ # Every Yobi::CheckError from the run.
65
56
  def errors
66
57
  @errors ||= output.messages("error")
67
58
  end
68
59
 
69
- # @return [void]
70
60
  def pretty_print(q)
71
61
  q.object_group(self) do
72
62
  q.breakable
@@ -77,34 +67,31 @@ module Yobi
77
67
  end
78
68
  end
79
69
 
80
- # One `"error"` message from a check run.
81
- # https://restic.readthedocs.io/en/stable/075_scripting.html#error
70
+ # One +"error"+ message from a check run.
82
71
  class CheckError < Yobi::FancyHash
83
- # @return [String]
84
72
  def message
85
73
  self["message"]
86
74
  end
87
75
  end
88
76
 
89
- # The `"summary"` message from a check run, the final result once the
90
- # command finishes. Dispatched to {Yobi::CheckOutcome#summary}.
77
+ # The +"summary"+ message from a check run, the final result once the
78
+ # command finishes. Dispatched to Yobi::CheckOutcome#summary.
91
79
  class CheckSummary < Yobi::FancyHash
92
- # @return [Integer]
93
80
  def num_errors
94
81
  self["num_errors"] || 0
95
82
  end
96
83
 
97
- # @return [Array<String>] pack IDs needing `repair_packs`/`repair_snapshots`
84
+ # Pack IDs needing #repair_packs / #repair_snapshots.
98
85
  def broken_packs
99
86
  self["broken_packs"] || []
100
87
  end
101
88
 
102
- # @return [Boolean] whether to run {Yobi::Repository#repair_index}
89
+ # Whether to run Yobi::Repository#repair_index.
103
90
  def suggest_repair_index?
104
91
  self["suggest_repair_index"]
105
92
  end
106
93
 
107
- # @return [Boolean] whether to run {Yobi::Repository#prune}
94
+ # Whether to run Yobi::Repository#prune.
108
95
  def suggest_prune?
109
96
  self["suggest_prune"]
110
97
  end