diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index e3b46fa35..363b35370 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -175,11 +175,14 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, return err } - var progress *restoreui.Progress - if !gopts.Quiet && !gopts.JSON { - progress = restoreui.NewProgress(restoreui.NewTextPrinter(term), calculateProgressInterval(!gopts.Quiet, gopts.JSON)) + var printer restoreui.ProgressPrinter + if gopts.JSON { + printer = restoreui.NewJSONProgress(term) + } else { + printer = restoreui.NewTextProgress(term) } + progress := restoreui.NewProgress(printer, calculateProgressInterval(!gopts.Quiet, gopts.JSON)) res := restorer.NewRestorer(repo, sn, opts.Sparse, progress) totalErrors := 0 @@ -237,23 +240,25 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, res.SelectFilter = selectIncludeFilter } - Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target) + if !gopts.JSON { + Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target) + } err = res.RestoreTo(ctx, opts.Target) if err != nil { return err } - if progress != nil { - progress.Finish() - } + progress.Finish() if totalErrors > 0 { return errors.Fatalf("There were %d errors\n", totalErrors) } if opts.Verify { - Verbosef("verifying files in %s\n", opts.Target) + if !gopts.JSON { + Verbosef("verifying files in %s\n", opts.Target) + } var count int t0 := time.Now() count, err = res.VerifyFiles(ctx, opts.Target) @@ -263,8 +268,11 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, if totalErrors > 0 { return errors.Fatalf("There were %d errors\n", totalErrors) } - Verbosef("finished verifying %d files in %s (took %s)\n", count, opts.Target, - time.Since(t0).Round(time.Millisecond)) + + if !gopts.JSON { + Verbosef("finished verifying %d files in %s (took %s)\n", count, opts.Target, + time.Since(t0).Round(time.Millisecond)) + } } return nil diff --git a/internal/ui/backup/json.go b/internal/ui/backup/json.go index e7c9274a4..10f0e91fa 100644 --- a/internal/ui/backup/json.go +++ b/internal/ui/backup/json.go @@ -1,8 +1,6 @@ package backup import ( - "bytes" - "encoding/json" "sort" "time" @@ -32,21 +30,12 @@ func NewJSONProgress(term *termstatus.Terminal, verbosity uint) *JSONProgress { } } -func toJSONString(status interface{}) string { - buf := new(bytes.Buffer) - err := json.NewEncoder(buf).Encode(status) - if err != nil { - panic(err) - } - return buf.String() -} - func (b *JSONProgress) print(status interface{}) { - b.term.Print(toJSONString(status)) + b.term.Print(ui.ToJSONString(status)) } func (b *JSONProgress) error(status interface{}) { - b.term.Error(toJSONString(status)) + b.term.Error(ui.ToJSONString(status)) } // Update updates the status lines. diff --git a/internal/ui/format.go b/internal/ui/format.go index 13d02f9e3..34c97703a 100644 --- a/internal/ui/format.go +++ b/internal/ui/format.go @@ -1,6 +1,8 @@ package ui import ( + "bytes" + "encoding/json" "fmt" "time" ) @@ -53,3 +55,12 @@ func FormatSeconds(sec uint64) string { } return fmt.Sprintf("%d:%02d", min, sec) } + +func ToJSONString(status interface{}) string { + buf := new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(status) + if err != nil { + panic(err) + } + return buf.String() +} diff --git a/internal/ui/restore/json.go b/internal/ui/restore/json.go new file mode 100644 index 000000000..0ff0a89cd --- /dev/null +++ b/internal/ui/restore/json.go @@ -0,0 +1,69 @@ +package restore + +import ( + "time" + + "github.com/restic/restic/internal/ui" +) + +type jsonPrinter struct { + terminal term +} + +func NewJSONProgress(terminal term) ProgressPrinter { + return &jsonPrinter{ + terminal: terminal, + } +} + +func (t *jsonPrinter) print(status interface{}) { + t.terminal.Print(ui.ToJSONString(status)) +} + +func (t *jsonPrinter) Update(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { + status := statusUpdate{ + MessageType: "status", + SecondsElapsed: uint64(duration / time.Second), + TotalFiles: filesTotal, + FilesDone: filesFinished, + TotalBytes: allBytesTotal, + BytesDone: allBytesWritten, + } + + if allBytesTotal > 0 { + status.PercentDone = float64(allBytesWritten) / float64(allBytesTotal) + } + + t.print(status) +} + +func (t *jsonPrinter) Finish(filesFinished, filesTotal, allBytesWritten, allBytesTotal uint64, duration time.Duration) { + status := summaryOutput{ + MessageType: "summary", + SecondsElapsed: uint64(duration / time.Second), + TotalFiles: filesTotal, + FilesDone: filesFinished, + TotalBytes: allBytesTotal, + BytesDone: allBytesWritten, + } + t.print(status) +} + +type statusUpdate struct { + MessageType string `json:"message_type"` // "status" + SecondsElapsed uint64 `json:"seconds_elapsed,omitempty"` + PercentDone float64 `json:"percent_done"` + TotalFiles uint64 `json:"total_files,omitempty"` + FilesDone uint64 `json:"files_done,omitempty"` + TotalBytes uint64 `json:"total_bytes,omitempty"` + BytesDone uint64 `json:"bytes_done,omitempty"` +} + +type summaryOutput struct { + MessageType string `json:"message_type"` // "summary" + SecondsElapsed uint64 `json:"seconds_elapsed,omitempty"` + TotalFiles uint64 `json:"total_files,omitempty"` + FilesDone uint64 `json:"files_done,omitempty"` + TotalBytes uint64 `json:"total_bytes,omitempty"` + BytesDone uint64 `json:"bytes_done,omitempty"` +} diff --git a/internal/ui/restore/text.go b/internal/ui/restore/text.go index 2f73f7cfe..e6465eed0 100644 --- a/internal/ui/restore/text.go +++ b/internal/ui/restore/text.go @@ -11,7 +11,7 @@ type textPrinter struct { terminal term } -func NewTextPrinter(terminal term) ProgressPrinter { +func NewTextProgress(terminal term) ProgressPrinter { return &textPrinter{ terminal: terminal, } diff --git a/internal/ui/restore/text_test.go b/internal/ui/restore/text_test.go index 0dd32e686..2a8c90878 100644 --- a/internal/ui/restore/text_test.go +++ b/internal/ui/restore/text_test.go @@ -21,21 +21,21 @@ func (m *mockTerm) SetStatus(lines []string) { func TestPrintUpdate(t *testing.T) { term := &mockTerm{} - printer := NewTextPrinter(term) + printer := NewTextProgress(term) printer.Update(3, 11, 29, 47, 5*time.Second) test.Equals(t, []string{"[0:05] 61.70% 3 files 29 B, total 11 files 47 B"}, term.output) } func TestPrintSummaryOnSuccess(t *testing.T) { term := &mockTerm{} - printer := NewTextPrinter(term) + printer := NewTextProgress(term) printer.Finish(11, 11, 47, 47, 5*time.Second) test.Equals(t, []string{"Summary: Restored 11 Files (47 B) in 0:05"}, term.output) } func TestPrintSummaryOnErrors(t *testing.T) { term := &mockTerm{} - printer := NewTextPrinter(term) + printer := NewTextProgress(term) printer.Finish(3, 11, 29, 47, 5*time.Second) test.Equals(t, []string{"Summary: Restored 3 / 11 Files (29 B / 47 B) in 0:05"}, term.output) }