Skip to content

Commit 5dbc8a4

Browse files
authored
refactor(general): minor cleanups (kopia#4003)
Followups to kopia#3655 * wrap fs.Reader * nit: remove unnecessary intermediate variable * nit: rename local variable * cleanup: move restore.Progress interface to cli pkg * move cliRestoreProgress to a separate file * refactor(general): replace switch with if/else for clarity Removes a tautology for `err == nil`, which was guaranteed to be true in the second case statement for the switch. Replacing the switch statement with and if/else block is clearer. * initialize restoreProgress in restore command * fix: use error.Wrapf with format string and args Simplify SetCounters signature: Pass arguments in a `restore.Stats` struct. `SetCounters(s restore.Stats)` Simplifies call sites and implementation. In this case it makes sense to pass all the values using the restore.Stats struct as it simplifies the calls. However, this pattern should be avoided in general as it essentially makes all the arguments "optional". This makes it easy to miss setting a value and simply passing 0 (the default value), thus it becomes error prone. In this particular case, the struct is being passed through verbatim, thus eliminating the risk of missing a value, at least in the current state of the code.
1 parent d37de83 commit 5dbc8a4

8 files changed

Lines changed: 165 additions & 199 deletions

File tree

‎cli/app.go‎

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/kopia/kopia/repo/blob"
2424
"github.com/kopia/kopia/repo/logging"
2525
"github.com/kopia/kopia/repo/maintenance"
26-
"github.com/kopia/kopia/snapshot/restore"
2726
"github.com/kopia/kopia/snapshot/snapshotmaintenance"
2827
)
2928

@@ -87,7 +86,7 @@ type appServices interface {
8786
advancedCommand(ctx context.Context)
8887
repositoryConfigFileName() string
8988
getProgress() *cliProgress
90-
getRestoreProgress() restore.Progress
89+
getRestoreProgress() RestoreProgress
9190

9291
stdout() io.Writer
9392
Stderr() io.Writer
@@ -120,7 +119,7 @@ type App struct {
120119
enableAutomaticMaintenance bool
121120
pf profileFlags
122121
progress *cliProgress
123-
restoreProgress restore.Progress
122+
restoreProgress RestoreProgress
124123
initialUpdateCheckDelay time.Duration
125124
updateCheckInterval time.Duration
126125
updateAvailableNotifyInterval time.Duration
@@ -186,11 +185,11 @@ func (c *App) getProgress() *cliProgress {
186185
}
187186

188187
// SetRestoreProgress is used to set custom restore progress, purposed to be used in tests.
189-
func (c *App) SetRestoreProgress(p restore.Progress) {
188+
func (c *App) SetRestoreProgress(p RestoreProgress) {
190189
c.restoreProgress = p
191190
}
192191

193-
func (c *App) getRestoreProgress() restore.Progress {
192+
func (c *App) getRestoreProgress() RestoreProgress {
194193
return c.restoreProgress
195194
}
196195

@@ -293,10 +292,6 @@ func (c *App) setup(app *kingpin.Application) {
293292
c.pf.setup(app)
294293
c.progress.setup(c, app)
295294

296-
if rp, ok := c.restoreProgress.(*cliRestoreProgress); ok {
297-
rp.setup(c, app)
298-
}
299-
300295
c.blob.setup(c, app)
301296
c.benchmark.setup(c, app)
302297
c.cache.setup(c, app)
@@ -325,8 +320,7 @@ type commandParent interface {
325320
// NewApp creates a new instance of App.
326321
func NewApp() *App {
327322
return &App{
328-
progress: &cliProgress{},
329-
restoreProgress: &cliRestoreProgress{},
323+
progress: &cliProgress{},
330324
cliStorageProviders: []StorageProvider{
331325
{"from-config", "the provided configuration file", func() StorageFlags { return &storageFromConfigFlags{} }},
332326

‎cli/cli_progress.go‎

Lines changed: 0 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -259,124 +259,4 @@ func (p *cliProgress) Finish() {
259259
}
260260
}
261261

262-
type cliRestoreProgress struct {
263-
restoredCount atomic.Int32
264-
enqueuedCount atomic.Int32
265-
skippedCount atomic.Int32
266-
ignoredErrorsCount atomic.Int32
267-
268-
restoredTotalFileSize atomic.Int64
269-
enqueuedTotalFileSize atomic.Int64
270-
skippedTotalFileSize atomic.Int64
271-
272-
progressUpdateInterval time.Duration
273-
enableProgress bool
274-
275-
svc appServices
276-
outputThrottle timetrack.Throttle
277-
outputMutex sync.Mutex
278-
out textOutput
279-
eta timetrack.Estimator
280-
281-
// +checklocks:outputMutex
282-
lastLineLength int
283-
}
284-
285-
func (p *cliRestoreProgress) setup(svc appServices, _ *kingpin.Application) {
286-
cp := svc.getProgress()
287-
if cp == nil {
288-
return
289-
}
290-
291-
p.progressUpdateInterval = cp.progressUpdateInterval
292-
p.enableProgress = cp.enableProgress
293-
p.out = cp.out
294-
p.svc = svc
295-
296-
p.eta = timetrack.Start()
297-
}
298-
299-
func (p *cliRestoreProgress) SetCounters(
300-
enqueuedCount, restoredCount, skippedCount, ignoredErrors int32,
301-
enqueuedBytes, restoredBytes, skippedBytes int64,
302-
) {
303-
p.enqueuedCount.Store(enqueuedCount)
304-
p.enqueuedTotalFileSize.Store(enqueuedBytes)
305-
306-
p.restoredCount.Store(restoredCount)
307-
p.restoredTotalFileSize.Store(restoredBytes)
308-
309-
p.skippedCount.Store(skippedCount)
310-
p.skippedTotalFileSize.Store(skippedBytes)
311-
312-
p.ignoredErrorsCount.Store(ignoredErrors)
313-
314-
p.maybeOutput()
315-
}
316-
317-
func (p *cliRestoreProgress) Flush() {
318-
p.outputThrottle.Reset()
319-
p.output("\n")
320-
}
321-
322-
func (p *cliRestoreProgress) maybeOutput() {
323-
if p.outputThrottle.ShouldOutput(p.svc.getProgress().progressUpdateInterval) {
324-
p.output("")
325-
}
326-
}
327-
328-
func (p *cliRestoreProgress) output(suffix string) {
329-
if !p.svc.getProgress().enableProgress {
330-
return
331-
}
332-
333-
p.outputMutex.Lock()
334-
defer p.outputMutex.Unlock()
335-
336-
restoredCount := p.restoredCount.Load()
337-
enqueuedCount := p.enqueuedCount.Load()
338-
skippedCount := p.skippedCount.Load()
339-
ignoredCount := p.ignoredErrorsCount.Load()
340-
341-
restoredSize := p.restoredTotalFileSize.Load()
342-
enqueuedSize := p.enqueuedTotalFileSize.Load()
343-
skippedSize := p.skippedTotalFileSize.Load()
344-
345-
if restoredSize == 0 {
346-
return
347-
}
348-
349-
var maybeRemaining, maybeSkipped, maybeErrors string
350-
if est, ok := p.eta.Estimate(float64(restoredSize), float64(enqueuedSize)); ok {
351-
maybeRemaining = fmt.Sprintf(" %v (%.1f%%) remaining %v",
352-
units.BytesPerSecondsString(est.SpeedPerSecond),
353-
est.PercentComplete,
354-
est.Remaining)
355-
}
356-
357-
if skippedCount > 0 {
358-
maybeSkipped = fmt.Sprintf(", skipped %v (%v)", skippedCount, units.BytesString(skippedSize))
359-
}
360-
361-
if ignoredCount > 0 {
362-
maybeErrors = fmt.Sprintf(", ignored %v errors", ignoredCount)
363-
}
364-
365-
line := fmt.Sprintf("Processed %v (%v) of %v (%v)%v%v%v.",
366-
restoredCount+skippedCount, units.BytesString(restoredSize),
367-
enqueuedCount, units.BytesString(enqueuedSize),
368-
maybeSkipped, maybeErrors, maybeRemaining,
369-
)
370-
371-
var extraSpaces string
372-
373-
if len(line) < p.lastLineLength {
374-
// add extra spaces to wipe over previous line if it was longer than current
375-
extraSpaces = strings.Repeat(" ", p.lastLineLength-len(line))
376-
}
377-
378-
p.lastLineLength = len(line)
379-
p.out.printStderr("\r%v%v%v", line, extraSpaces, suffix)
380-
}
381-
382262
var _ snapshotfs.UploadProgress = (*cliProgress)(nil)

‎cli/command_restore.go‎

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/kopia/kopia/fs"
1919
"github.com/kopia/kopia/fs/localfs"
2020
"github.com/kopia/kopia/internal/clock"
21+
"github.com/kopia/kopia/internal/timetrack"
2122
"github.com/kopia/kopia/internal/units"
2223
"github.com/kopia/kopia/repo"
2324
"github.com/kopia/kopia/repo/object"
@@ -95,6 +96,12 @@ followed by the path of the directory for the contents to be restored.
9596
unlimitedDepth = math.MaxInt32
9697
)
9798

99+
// RestoreProgress is invoked to report progress during a restore.
100+
type RestoreProgress interface {
101+
SetCounters(s restore.Stats)
102+
Flush()
103+
}
104+
98105
type restoreSourceTarget struct {
99106
source string
100107
target string
@@ -366,6 +373,21 @@ func (c *commandRestore) setupPlaceholderExpansion(ctx context.Context, rep repo
366373
return rootEntry, nil
367374
}
368375

376+
func (c *commandRestore) getRestoreProgress() RestoreProgress {
377+
if rp := c.svc.getRestoreProgress(); rp != nil {
378+
return rp
379+
}
380+
381+
pf := c.svc.getProgress().progressFlags
382+
383+
return &cliRestoreProgress{
384+
enableProgress: pf.enableProgress,
385+
out: pf.out,
386+
progressUpdateInterval: pf.progressUpdateInterval,
387+
eta: timetrack.Start(),
388+
}
389+
}
390+
369391
func (c *commandRestore) run(ctx context.Context, rep repo.Repository) error {
370392
output, oerr := c.restoreOutput(ctx, rep)
371393
if oerr != nil {
@@ -396,17 +418,9 @@ func (c *commandRestore) run(ctx context.Context, rep repo.Repository) error {
396418
rootEntry = re
397419
}
398420

399-
restoreProgress := c.svc.getRestoreProgress()
421+
restoreProgress := c.getRestoreProgress()
400422
progressCallback := func(ctx context.Context, stats restore.Stats) {
401-
restoreProgress.SetCounters(
402-
stats.EnqueuedFileCount+stats.EnqueuedDirCount+stats.EnqueuedSymlinkCount,
403-
stats.RestoredFileCount+stats.RestoredDirCount+stats.RestoredSymlinkCount,
404-
stats.SkippedCount,
405-
stats.IgnoredErrorCount,
406-
stats.EnqueuedTotalFileSize,
407-
stats.RestoredTotalFileSize,
408-
stats.SkippedTotalFileSize,
409-
)
423+
restoreProgress.SetCounters(stats)
410424
}
411425

412426
st, err := restore.Entry(ctx, rep, output, rootEntry, restore.Options{

‎cli/restore_progress.go‎

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"sync"
7+
"sync/atomic"
8+
"time"
9+
10+
"github.com/kopia/kopia/internal/timetrack"
11+
"github.com/kopia/kopia/internal/units"
12+
"github.com/kopia/kopia/snapshot/restore"
13+
)
14+
15+
type cliRestoreProgress struct {
16+
restoredCount atomic.Int32
17+
enqueuedCount atomic.Int32
18+
skippedCount atomic.Int32
19+
ignoredErrorsCount atomic.Int32
20+
21+
restoredTotalFileSize atomic.Int64
22+
enqueuedTotalFileSize atomic.Int64
23+
skippedTotalFileSize atomic.Int64
24+
25+
progressUpdateInterval time.Duration
26+
enableProgress bool
27+
28+
outputThrottle timetrack.Throttle
29+
outputMutex sync.Mutex
30+
out textOutput // +checklocksignore: outputMutex just happens to be held always.
31+
eta timetrack.Estimator // +checklocksignore: outputMutex just happens to be held always.
32+
33+
// +checklocks:outputMutex
34+
lastLineLength int
35+
}
36+
37+
func (p *cliRestoreProgress) SetCounters(s restore.Stats) {
38+
p.enqueuedCount.Store(s.EnqueuedFileCount + s.EnqueuedDirCount + s.EnqueuedSymlinkCount)
39+
p.enqueuedTotalFileSize.Store(s.EnqueuedTotalFileSize)
40+
41+
p.restoredCount.Store(s.RestoredFileCount + s.RestoredDirCount + s.RestoredSymlinkCount)
42+
p.restoredTotalFileSize.Store(s.RestoredTotalFileSize)
43+
44+
p.skippedCount.Store(s.SkippedCount)
45+
p.skippedTotalFileSize.Store(s.SkippedTotalFileSize)
46+
47+
p.ignoredErrorsCount.Store(s.IgnoredErrorCount)
48+
49+
p.maybeOutput()
50+
}
51+
52+
func (p *cliRestoreProgress) Flush() {
53+
p.outputThrottle.Reset()
54+
p.output("\n")
55+
}
56+
57+
func (p *cliRestoreProgress) maybeOutput() {
58+
if p.outputThrottle.ShouldOutput(p.progressUpdateInterval) {
59+
p.output("")
60+
}
61+
}
62+
63+
func (p *cliRestoreProgress) output(suffix string) {
64+
if !p.enableProgress {
65+
return
66+
}
67+
68+
// ensure the counters are not going back in an output line compared to the previous one
69+
p.outputMutex.Lock()
70+
defer p.outputMutex.Unlock()
71+
72+
restoredCount := p.restoredCount.Load()
73+
enqueuedCount := p.enqueuedCount.Load()
74+
skippedCount := p.skippedCount.Load()
75+
ignoredCount := p.ignoredErrorsCount.Load()
76+
77+
restoredSize := p.restoredTotalFileSize.Load()
78+
enqueuedSize := p.enqueuedTotalFileSize.Load()
79+
skippedSize := p.skippedTotalFileSize.Load()
80+
81+
if restoredSize == 0 {
82+
return
83+
}
84+
85+
var maybeRemaining, maybeSkipped, maybeErrors string
86+
if est, ok := p.eta.Estimate(float64(restoredSize), float64(enqueuedSize)); ok {
87+
maybeRemaining = fmt.Sprintf(" %v (%.1f%%) remaining %v",
88+
units.BytesPerSecondsString(est.SpeedPerSecond),
89+
est.PercentComplete,
90+
est.Remaining)
91+
}
92+
93+
if skippedCount > 0 {
94+
maybeSkipped = fmt.Sprintf(", skipped %v (%v)", skippedCount, units.BytesString(skippedSize))
95+
}
96+
97+
if ignoredCount > 0 {
98+
maybeErrors = fmt.Sprintf(", ignored %v errors", ignoredCount)
99+
}
100+
101+
line := fmt.Sprintf("Processed %v (%v) of %v (%v)%v%v%v.",
102+
restoredCount+skippedCount, units.BytesString(restoredSize),
103+
enqueuedCount, units.BytesString(enqueuedSize),
104+
maybeSkipped, maybeErrors, maybeRemaining,
105+
)
106+
107+
var extraSpaces string
108+
109+
if len(line) < p.lastLineLength {
110+
// add extra spaces to wipe over previous line if it was longer than current
111+
extraSpaces = strings.Repeat(" ", p.lastLineLength-len(line))
112+
}
113+
114+
p.lastLineLength = len(line)
115+
p.out.printStderr("\r%v%v%v", line, extraSpaces, suffix)
116+
}

0 commit comments

Comments
 (0)