mirror of
https://github.com/restic/restic.git
synced 2026-06-17 22:24:17 +00:00
Merge pull request #21850 from MichaelEischer/misc-cleanups
Cleanup `ui/*` package and minor cleanups in `test` and `walker`
This commit is contained in:
@@ -533,8 +533,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts global.Options, te
|
||||
}
|
||||
defer unlock()
|
||||
|
||||
progressReporter := backup.NewProgress(printer,
|
||||
ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
|
||||
progressReporter := backup.NewProgress(printer, gopts.Quiet, gopts.JSON, term.CanUpdateStatus())
|
||||
defer progressReporter.Done()
|
||||
|
||||
// rejectByNameFuncs collect functions that can reject items from the backup based on path only
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -57,7 +58,7 @@ func (opts *CacheOptions) AddFlags(f *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func runCache(opts CacheOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
if len(args) > 0 {
|
||||
return errors.Fatal("the cache command expects no arguments, only options - please see `restic help cache` for usage and flags")
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
var catAllowedCmds = []string{"config", "index", "snapshot", "key", "masterkey", "lock", "pack", "blob", "tree"}
|
||||
@@ -67,7 +68,7 @@ func validateCatArgs(args []string) error {
|
||||
}
|
||||
|
||||
func runCat(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
if err := validateCatArgs(args); err != nil {
|
||||
return err
|
||||
|
||||
@@ -231,7 +231,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts global.Options, args
|
||||
|
||||
var printer progress.Printer
|
||||
if !gopts.JSON {
|
||||
printer = ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer = progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
} else {
|
||||
printer = newJSONErrorPrinter(term)
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
gopts := global.Options{CacheDir: tmpDirBase}
|
||||
cleanup := prepareCheckCache(testCase.opts, &gopts, &progress.NoopPrinter{})
|
||||
cleanup := prepareCheckCache(testCase.opts, &gopts, progress.NewNoopPrinter())
|
||||
files, err := os.ReadDir(tmpDirBase)
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -234,7 +234,7 @@ func TestPrepareCheckCache(t *testing.T) {
|
||||
|
||||
func TestPrepareDefaultCheckCache(t *testing.T) {
|
||||
gopts := global.Options{CacheDir: ""}
|
||||
cleanup := prepareCheckCache(CheckOptions{}, &gopts, &progress.NoopPrinter{})
|
||||
cleanup := prepareCheckCache(CheckOptions{}, &gopts, progress.NewNoopPrinter())
|
||||
_, err := os.ReadDir(gopts.CacheDir)
|
||||
rtest.OK(t, err)
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ func collectAllSnapshots(ctx context.Context, opts CopyOptions,
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, opts CopyOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
secondaryGopts, isFromRepo, err := opts.SecondaryRepoOptions.FillGlobalOpts(ctx, gopts, "destination")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunCopy(t testing.TB, srcGopts global.Options, dstGopts global.Options) {
|
||||
@@ -97,7 +97,7 @@ func TestCopy(t *testing.T) {
|
||||
|
||||
func testPackAndBlobCounts(t testing.TB, gopts global.Options) (countTreePacks int, countDataPacks int, countBlobs int) {
|
||||
rtest.OK(t, withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func registerDebugCommand(cmd *cobra.Command, globalOptions *global.Options) {
|
||||
@@ -117,7 +118,7 @@ func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io
|
||||
}
|
||||
|
||||
func runDebugDump(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
@@ -158,7 +159,7 @@ func runDebugDump(ctx context.Context, gopts global.Options, args []string, term
|
||||
}
|
||||
|
||||
func runDebugExamine(ctx context.Context, gopts global.Options, opts DebugExamineOptions, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
if opts.ExtractPack && gopts.NoLock {
|
||||
return fmt.Errorf("--extract-pack and --no-lock are mutually exclusive")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -358,7 +359,7 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts global.Options, args [
|
||||
return errors.Fatalf("specify two snapshot IDs")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -136,7 +137,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts global.Options, args [
|
||||
return errors.Fatal("no file and no snapshot ID specified")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
switch opts.Archive {
|
||||
case "tar", "zip":
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
@@ -599,7 +600,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts global.Options, args [
|
||||
return errors.Fatal("wrong number of arguments")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
var err error
|
||||
pat := findPattern{pattern: args}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunFind(t testing.TB, wantJSON bool, opts FindOptions, gopts global.Options, pattern string) []byte {
|
||||
@@ -169,7 +169,7 @@ func TestFindPackfile(t *testing.T) {
|
||||
|
||||
// do all the testing wrapped inside withTermStatus()
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -228,7 +228,7 @@ func TestFindPackID(t *testing.T) {
|
||||
dataPackID := restic.ID{}
|
||||
treePackID := restic.ID{}
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -189,7 +190,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for forget command")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -116,7 +116,7 @@ func runGenerate(opts generateOptions, gopts global.Options, args []string, term
|
||||
return errors.Fatal("the generate command expects no arguments, only options - please see `restic help generate` for usage and flags")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
cmdRoot := newRootCommand(&global.Options{})
|
||||
|
||||
if opts.ManDir != "" {
|
||||
|
||||
@@ -60,7 +60,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts global.Options, args [
|
||||
return errors.Fatal("the init command expects no arguments, only options - please see `restic help init` for usage and flags")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
var version uint
|
||||
switch opts.RepositoryVersion {
|
||||
|
||||
@@ -57,14 +57,14 @@ func TestInitCopyChunkerParams(t *testing.T) {
|
||||
|
||||
var repo *repository.Repository
|
||||
err = withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
repo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
var otherRepo *repository.Repository
|
||||
err = withTermStatus(t, env2.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
otherRepo, err = global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
otherRepo, err = global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
return err
|
||||
})
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -60,7 +60,7 @@ func runKeyAdd(ctx context.Context, gopts global.Options, opts KeyAddOptions, ar
|
||||
return fmt.Errorf("the key add command expects no arguments, only options - please see `restic help key add` for usage and flags")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithAppendLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -63,7 +63,7 @@ func testRunKeyAddNewKeyUserHost(t testing.TB, gopts global.Options) {
|
||||
rtest.OK(t, err)
|
||||
|
||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err := global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 2, "")
|
||||
rtest.OK(t, err)
|
||||
@@ -105,7 +105,7 @@ func testRunKeyPasswdUserHost(t testing.TB, newPassword string, gopts global.Opt
|
||||
|
||||
gopts.Password = testKeyNewPassword
|
||||
_ = withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
repo, err := global.OpenRepository(ctx, gopts, &progress.NoopPrinter{})
|
||||
repo, err := global.OpenRepository(ctx, gopts, progress.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
key, err := repository.SearchKey(ctx, repo, testKeyNewPassword, 1, "")
|
||||
rtest.OK(t, err)
|
||||
|
||||
@@ -46,7 +46,7 @@ func runKeyList(ctx context.Context, gopts global.Options, args []string, term u
|
||||
return fmt.Errorf("the key list command expects no arguments, only options - please see `restic help key list` for usage and flags")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -55,7 +55,7 @@ func runKeyPasswd(ctx context.Context, gopts global.Options, opts KeyPasswdOptio
|
||||
return fmt.Errorf("the key passwd command expects no arguments, only options - please see `restic help key passwd` for usage and flags")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -43,7 +43,7 @@ func runKeyRemove(ctx context.Context, gopts global.Options, args []string, term
|
||||
return fmt.Errorf("key remove expects one argument as the key id")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -44,7 +45,7 @@ Exit status is 12 if the password is incorrect.
|
||||
}
|
||||
|
||||
func runList(ctx context.Context, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.Fatal("type not specified")
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunList(t testing.TB, gopts global.Options, tpe string) restic.IDs {
|
||||
@@ -61,7 +61,7 @@ func testListSnapshots(t testing.TB, gopts global.Options, expected int) restic.
|
||||
// extract blob set from repository index
|
||||
func testListBlobs(t testing.TB, gopts global.Options) (blobSetFromIndex restic.IDSet) {
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/walker"
|
||||
)
|
||||
|
||||
@@ -305,7 +306,7 @@ type toSortOutput struct {
|
||||
}
|
||||
|
||||
func runLs(ctx context.Context, opts LsOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
termPrinter := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
termPrinter := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
if len(args) == 0 {
|
||||
return errors.Fatal("no snapshot ID specified, specify snapshot ID or use special ID 'latest'")
|
||||
|
||||
@@ -135,7 +135,7 @@ func applyMigrations(ctx context.Context, opts MigrateOptions, gopts global.Opti
|
||||
}
|
||||
|
||||
func runMigrate(ctx context.Context, opts MigrateOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
|
||||
"github.com/restic/restic/internal/fuse"
|
||||
|
||||
@@ -119,7 +120,7 @@ func (opts *MountOptions) AddFlags(f *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func runMount(ctx context.Context, opts MountOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
if opts.TimeTemplate == "" {
|
||||
return errors.Fatal("time template string cannot be empty")
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -131,7 +131,7 @@ func checkSnapshots(t testing.TB, gopts global.Options, mountpoint string, snaps
|
||||
}
|
||||
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -175,7 +175,7 @@ func runPrune(ctx context.Context, opts PruneOptions, gopts global.Options, term
|
||||
return errors.Fatal("--no-lock is only applicable in combination with --dry-run for prune command")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun && gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -48,7 +48,7 @@ func runRecover(ctx context.Context, gopts global.Options, term ui.Terminal) err
|
||||
return err
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -70,7 +71,7 @@ func newRebuildIndexCommand(globalOptions *global.Options) *cobra.Command {
|
||||
}
|
||||
|
||||
func runRebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts global.Options, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -52,7 +53,7 @@ func runRepairPacks(ctx context.Context, gopts global.Options, term ui.Terminal,
|
||||
return errors.Fatal("no ids specified")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/walker"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -78,7 +79,7 @@ func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func runRepairSnapshots(ctx context.Context, gopts global.Options, opts RepairOptions, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithExclusiveLock(ctx, gopts, opts.DryRun, printer)
|
||||
if err != nil {
|
||||
|
||||
@@ -167,7 +167,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts global.Options,
|
||||
return err
|
||||
}
|
||||
|
||||
progress := restoreui.NewProgress(printer, ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()))
|
||||
progress := restoreui.NewProgress(printer, gopts.Quiet, gopts.JSON, term.CanUpdateStatus())
|
||||
res := restorer.NewRestorer(repo, sn, restorer.Options{
|
||||
DryRun: opts.DryRun,
|
||||
Sparse: opts.Sparse,
|
||||
|
||||
@@ -298,7 +298,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts global.Options,
|
||||
return errors.Fatal("exclude and include patterns are mutually exclusive")
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
|
||||
var (
|
||||
repo *repository.Repository
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func testRunRewriteExclude(t testing.TB, gopts global.Options, excludes []string, forget bool, metadata snapshotMetadataArgs) {
|
||||
@@ -81,7 +82,7 @@ func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *data
|
||||
|
||||
var snapshots []*data.Snapshot
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -157,7 +158,7 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
|
||||
|
||||
var snapshots []*data.Snapshot
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -205,7 +206,7 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
||||
// replace snapshot by one without a summary
|
||||
var oldSummary *data.SnapshotSummary
|
||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/selfupdate"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -84,7 +85,7 @@ func runSelfUpdate(ctx context.Context, opts SelfUpdateOptions, gopts global.Opt
|
||||
}
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(false, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(false, gopts.Verbosity, term)
|
||||
printer.P("writing restic to %v", opts.Output)
|
||||
|
||||
v, err := selfupdate.DownloadLatestStableRelease(ctx, opts.Output, global.Version, printer.P)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -71,7 +72,7 @@ func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts global.Options, args []string, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+11
-87
@@ -7,8 +7,6 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/restic/chunker"
|
||||
"github.com/restic/restic/internal/crypto"
|
||||
@@ -19,6 +17,7 @@ import (
|
||||
"github.com/restic/restic/internal/restorer"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
statsui "github.com/restic/restic/internal/ui/stats"
|
||||
"github.com/restic/restic/internal/ui/table"
|
||||
"github.com/restic/restic/internal/walker"
|
||||
|
||||
@@ -104,7 +103,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
|
||||
return err
|
||||
}
|
||||
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock, printer)
|
||||
if err != nil {
|
||||
@@ -141,13 +140,8 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
|
||||
snapshots = append(snapshots, sn)
|
||||
}
|
||||
|
||||
statsProgress := newStatsProgress(term, !gopts.JSON, uint64(len(snapshots)))
|
||||
|
||||
updater := progress.NewUpdater(ui.CalculateProgressInterval(!gopts.Quiet, gopts.JSON, term.CanUpdateStatus()), func(runtime time.Duration, final bool) {
|
||||
statsProgress.printProgress(runtime, final)
|
||||
})
|
||||
|
||||
defer updater.Done()
|
||||
statsProgress := statsui.NewProgress(term, gopts.Quiet, gopts.JSON, uint64(len(snapshots)))
|
||||
defer statsProgress.Done()
|
||||
|
||||
for _, sn := range snapshots {
|
||||
err = statsWalkSnapshot(ctx, sn, repo, opts, stats, statsProgress)
|
||||
@@ -175,7 +169,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
|
||||
}
|
||||
}
|
||||
stats.TotalBlobCount++
|
||||
statsProgress.update(0, 1, uint64(pbs[0].Length))
|
||||
statsProgress.Update(0, 1, uint64(pbs[0].Length))
|
||||
}
|
||||
if stats.TotalCompressedBlobsSize > 0 {
|
||||
stats.CompressionRatio = float64(stats.TotalCompressedBlobsUncompressedSize) / float64(stats.TotalCompressedBlobsSize)
|
||||
@@ -186,7 +180,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
|
||||
}
|
||||
}
|
||||
// stop progress bar to prevent mangled output
|
||||
updater.Done()
|
||||
statsProgress.Done()
|
||||
|
||||
if gopts.JSON {
|
||||
err = json.NewEncoder(gopts.Term.OutputWriter()).Encode(stats)
|
||||
@@ -221,8 +215,8 @@ func runStats(ctx context.Context, opts StatsOptions, gopts global.Options, args
|
||||
return nil
|
||||
}
|
||||
|
||||
func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer, progress *statsProgress) error {
|
||||
progress.processSnapshot()
|
||||
func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer, progress *statsui.Progress) error {
|
||||
progress.ProcessSnapshot()
|
||||
if snapshot.Tree == nil {
|
||||
return fmt.Errorf("snapshot %s has nil tree", snapshot.ID().Str())
|
||||
}
|
||||
@@ -246,7 +240,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic
|
||||
return nil
|
||||
}
|
||||
|
||||
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}], progress *statsProgress) walker.WalkFunc {
|
||||
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}], progress *statsui.Progress) walker.WalkFunc {
|
||||
return func(parentTreeID restic.ID, npath string, node *data.Node, nodeErr error) error {
|
||||
if nodeErr != nil {
|
||||
return nodeErr
|
||||
@@ -254,7 +248,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
progress.update(1, 0, uint64(node.Size))
|
||||
progress.Update(1, 0, uint64(node.Size))
|
||||
if opts.countMode == countModeUniqueFilesByContents || opts.countMode == countModeBlobsPerFile {
|
||||
// only count this file if we haven't visited it before
|
||||
fid := makeFileIDByContents(node)
|
||||
@@ -274,7 +268,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
|
||||
// ensure we have this file (by path) in our map; in this
|
||||
// mode, a file is unique by both contents and path
|
||||
nodePath := filepath.Join(npath, node.Name)
|
||||
progress.update(0, 1, 0)
|
||||
progress.Update(0, 1, 0)
|
||||
if _, ok := stats.fileBlobs[nodePath]; !ok {
|
||||
stats.fileBlobs[nodePath] = restic.NewIDSet()
|
||||
stats.TotalFileCount++
|
||||
@@ -371,76 +365,6 @@ type statsContainer struct {
|
||||
// independent of references to files
|
||||
blobs restic.AssociatedBlobSet
|
||||
}
|
||||
type statsProgress struct {
|
||||
term ui.Terminal
|
||||
m sync.Mutex
|
||||
snapshotCount uint64
|
||||
show bool
|
||||
|
||||
processedSnapshotCount uint64
|
||||
processedFileCount uint64
|
||||
processedBlobCount uint64
|
||||
processedSize uint64
|
||||
}
|
||||
|
||||
func newStatsProgress(term ui.Terminal, show bool, snapshotCount uint64) *statsProgress {
|
||||
return &statsProgress{
|
||||
term: term,
|
||||
show: show,
|
||||
snapshotCount: snapshotCount,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statsProgress) printProgress(runtime time.Duration, final bool) {
|
||||
if !s.show {
|
||||
return
|
||||
}
|
||||
s.m.Lock()
|
||||
|
||||
progressBase := s.processedSnapshotCount
|
||||
if progressBase > 0 && !final {
|
||||
progressBase--
|
||||
}
|
||||
|
||||
status := fmt.Sprintf("[%s] %s %d / %d snapshots", ui.FormatDuration(runtime), ui.FormatPercent(progressBase, s.snapshotCount), s.processedSnapshotCount, s.snapshotCount)
|
||||
|
||||
if s.processedFileCount > 0 {
|
||||
status += fmt.Sprintf(", %v files", s.processedFileCount)
|
||||
}
|
||||
|
||||
if s.processedBlobCount > 0 {
|
||||
status += fmt.Sprintf(", %d blobs", s.processedBlobCount)
|
||||
}
|
||||
|
||||
status += fmt.Sprintf(", %s", ui.FormatBytes(s.processedSize))
|
||||
s.m.Unlock()
|
||||
|
||||
if final {
|
||||
s.term.SetStatus(nil)
|
||||
s.term.Print(status)
|
||||
} else {
|
||||
s.term.SetStatus([]string{status})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statsProgress) update(fileCount uint64, blobCount uint64, size uint64) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
s.processedFileCount += fileCount
|
||||
s.processedBlobCount += blobCount
|
||||
s.processedSize += size
|
||||
}
|
||||
|
||||
func (s *statsProgress) processSnapshot() {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
s.processedSnapshotCount++
|
||||
s.processedFileCount = 0
|
||||
s.processedBlobCount = 0
|
||||
s.processedSize = 0
|
||||
}
|
||||
|
||||
// fileID is a 256-bit hash that distinguishes unique files.
|
||||
type fileID [32]byte
|
||||
|
||||
@@ -2,10 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func TestSizeHistogramNew(t *testing.T) {
|
||||
@@ -62,38 +60,3 @@ func TestSizeHistogramString(t *testing.T) {
|
||||
rtest.Equals(t, "Count: 3\nTotal Size: 11 B\nSize Count\n-------------------\n 0 - 0 Byte 1\n 1 - 9 Byte 1\n10 - 42 Byte 1\n-------------------\n", h.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestStatsProgress(t *testing.T) {
|
||||
term := &ui.MockTerminal{}
|
||||
|
||||
progress := newStatsProgress(term, true, 2)
|
||||
progress.printProgress(0*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:00] 0.00% 0 / 2 snapshots, 0 B"}, term.Output)
|
||||
|
||||
progress.processSnapshot()
|
||||
progress.update(1, 2, 3)
|
||||
progress.printProgress(5*time.Second, false)
|
||||
// Output differs from the previous one because the progress is based on the number of processed snapshots,
|
||||
// 1/2 snapshots means processing the snapshot 1 currently
|
||||
rtest.Equals(t, []string{"[0:05] 0.00% 1 / 2 snapshots, 1 files, 2 blobs, 3 B"}, term.Output)
|
||||
|
||||
progress.processSnapshot()
|
||||
progress.printProgress(10*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:10] 50.00% 2 / 2 snapshots, 0 B"}, term.Output)
|
||||
|
||||
progress.update(4, 5, 6)
|
||||
progress.printProgress(15*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:15] 50.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
|
||||
|
||||
progress.printProgress(20*time.Second, true)
|
||||
rtest.Equals(t, []string{"[0:20] 100.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
|
||||
}
|
||||
|
||||
func TestStatsProgressJSON(t *testing.T) {
|
||||
term := &ui.MockTerminal{}
|
||||
|
||||
progress := newStatsProgress(term, false, 2)
|
||||
progress.printProgress(0*time.Second, false)
|
||||
// JSON output is not available yet, so just make sure to not break normal json output
|
||||
rtest.Equals(t, nil, term.Output)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func newTagCommand(globalOptions *global.Options) *cobra.Command {
|
||||
@@ -120,7 +121,7 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *data.Snaps
|
||||
}
|
||||
|
||||
func runTag(ctx context.Context, opts TagOptions, gopts global.Options, term ui.Terminal, args []string) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
|
||||
if len(opts.SetTags) == 0 && len(opts.AddTags) == 0 && len(opts.RemoveTags) == 0 {
|
||||
return errors.Fatal("nothing to do!")
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -47,7 +48,7 @@ func (opts *UnlockOptions) AddFlags(f *pflag.FlagSet) {
|
||||
}
|
||||
|
||||
func runUnlock(ctx context.Context, opts UnlockOptions, gopts global.Options, term ui.Terminal) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, term)
|
||||
repo, err := global.OpenRepository(ctx, gopts, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ Exit status is 1 if there was any error.
|
||||
`,
|
||||
DisableAutoGenTag: true,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
printer := ui.NewProgressPrinter(globalOptions.JSON, globalOptions.Verbosity, globalOptions.Term)
|
||||
printer := progress.NewTerminalPrinter(globalOptions.JSON, globalOptions.Verbosity, globalOptions.Term)
|
||||
|
||||
if globalOptions.JSON {
|
||||
type jsonVersion struct {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend"
|
||||
@@ -23,7 +22,7 @@ import (
|
||||
"github.com/restic/restic/internal/repository"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
)
|
||||
|
||||
@@ -244,7 +243,7 @@ func testSetupBackupData(t testing.TB, env *testEnvironment) string {
|
||||
func listPacks(gopts global.Options, t *testing.T) restic.IDSet {
|
||||
var packs restic.IDSet
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -263,7 +262,7 @@ func listPacks(gopts global.Options, t *testing.T) restic.IDSet {
|
||||
func listTreePacks(gopts global.Options, t *testing.T) restic.IDSet {
|
||||
var treePacks restic.IDSet
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, r, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -294,7 +293,7 @@ func captureBackend(gopts *global.Options) func() backend.Backend {
|
||||
func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) {
|
||||
be := captureBackend(&gopts)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, _, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -310,7 +309,7 @@ func removePacks(gopts global.Options, t testing.TB, remove restic.IDSet) {
|
||||
func removePacksExcept(gopts global.Options, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
||||
be := captureBackend(&gopts)
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, r, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -371,7 +370,7 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
|
||||
func testLoadSnapshot(t testing.TB, gopts global.Options, id restic.ID) *data.Snapshot {
|
||||
var snapshot *data.Snapshot
|
||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
@@ -429,18 +428,10 @@ func withTermStatus(t testing.TB, gopts global.Options, callback func(ctx contex
|
||||
}
|
||||
|
||||
func withTermStatusRaw(stdin io.ReadCloser, stdout, stderr io.Writer, gopts global.Options, callback func(ctx context.Context, gopts global.Options) error) error {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
var wg sync.WaitGroup
|
||||
|
||||
term := termstatus.New(stdin, stdout, stderr, gopts.Quiet)
|
||||
term, cancel := termstatus.Setup(stdin, stdout, stderr, gopts.Quiet)
|
||||
gopts.Term = term
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
term.Run(ctx)
|
||||
}()
|
||||
|
||||
defer wg.Wait()
|
||||
defer cancel()
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
return callback(ctx, gopts)
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/restic/restic/internal/global"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
func TestCheckRestoreNoLock(t *testing.T) {
|
||||
@@ -165,7 +165,7 @@ func TestFindListOnce(t *testing.T) {
|
||||
|
||||
var snapshotIDs restic.IDSet
|
||||
rtest.OK(t, withTermStatus(t, env.gopts, func(ctx context.Context, gopts global.Options) error {
|
||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
printer := progress.NewTerminalPrinter(gopts.JSON, gopts.Verbosity, gopts.Term)
|
||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||
rtest.OK(t, err)
|
||||
defer unlock()
|
||||
|
||||
@@ -70,10 +70,10 @@ func TestPruneMaxUnusedDuplicate(t *testing.T) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, &progress.NoopPrinter{})
|
||||
}, progress.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
|
||||
rsize := plan.Stats().Size
|
||||
remainingUnusedSize := rsize.Duplicate + rsize.Unused - rsize.Remove - rsize.Repackrm
|
||||
|
||||
@@ -41,10 +41,10 @@ func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, &progress.NoopPrinter{})
|
||||
}, progress.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
|
||||
repo = repository.TestOpenBackend(t, be)
|
||||
repository.TestCheckRepo(t, repo)
|
||||
@@ -165,9 +165,9 @@ func TestPruneSmall(t *testing.T) {
|
||||
usedBlobs.Insert(blob)
|
||||
}
|
||||
return nil
|
||||
}, &progress.NoopPrinter{})
|
||||
}, progress.NewNoopPrinter())
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, plan.Execute(context.TODO(), &progress.NoopPrinter{}))
|
||||
rtest.OK(t, plan.Execute(context.TODO(), progress.NewNoopPrinter()))
|
||||
|
||||
stats := plan.Stats()
|
||||
rtest.Equals(t, stats.Size.Used/blobSize, uint64(numBlobsCreated), fmt.Sprintf("total size of blobs should be %d but is %d",
|
||||
|
||||
@@ -162,7 +162,7 @@ func repack(t *testing.T, repo restic.Repository, be backend.Backend, packs rest
|
||||
func rebuildAndReloadIndex(t *testing.T, repo *repository.Repository) {
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
|
||||
ReadAllPacks: true,
|
||||
}, &progress.NoopPrinter{}))
|
||||
}, progress.NewNoopPrinter()))
|
||||
|
||||
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func testRebuildIndex(t *testing.T, readAllPacks bool, damage func(t *testing.T,
|
||||
repo = repository.TestOpenBackend(t, be)
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{
|
||||
ReadAllPacks: readAllPacks,
|
||||
}, &progress.NoopPrinter{}))
|
||||
}, progress.NewNoopPrinter()))
|
||||
|
||||
repository.TestCheckRepo(t, repo)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
|
||||
buf, err := backendtest.LoadAll(context.TODO(), be, h)
|
||||
rtest.OK(t, err)
|
||||
rtest.OK(t, be.Remove(context.TODO(), h))
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, &progress.NoopPrinter{}))
|
||||
rtest.OK(t, repository.RepairIndex(context.TODO(), repo, repository.RepairIndexOptions{}, progress.NewNoopPrinter()))
|
||||
|
||||
rtest.OK(t, be.Save(context.TODO(), h, backend.NewByteReader(buf, be.Hasher())))
|
||||
|
||||
@@ -130,7 +130,7 @@ func testRepairBrokenPack(t *testing.T, version uint) {
|
||||
|
||||
toRepair, damagedBlobs := test.damage(t, random, repo, be, packsBefore)
|
||||
|
||||
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, &progress.NoopPrinter{}))
|
||||
rtest.OK(t, repository.RepairPacks(context.TODO(), repo, toRepair, progress.NewNoopPrinter()))
|
||||
// reload index
|
||||
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
||||
|
||||
|
||||
@@ -989,7 +989,7 @@ func TestRestorerSparseOverwrite(t *testing.T) {
|
||||
|
||||
type printerMock struct {
|
||||
s restoreui.State
|
||||
progress.NoopPrinter
|
||||
progress.Printer
|
||||
}
|
||||
|
||||
func (p *printerMock) Update(_ restoreui.State, _ time.Duration) {
|
||||
@@ -1102,8 +1102,8 @@ func TestRestorerOverwriteBehavior(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
mock := &printerMock{}
|
||||
progress := restoreui.NewProgress(mock, 0)
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
tempdir := saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: test.Overwrite, Progress: progress})
|
||||
|
||||
for filename, content := range test.Files {
|
||||
@@ -1154,8 +1154,8 @@ func TestRestorerOverwritePartial(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
mock := &printerMock{}
|
||||
progress := restoreui.NewProgress(mock, 0)
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
saveSnapshotsAndOverwrite(t, baseSnapshot, overwriteSnapshot, Options{}, Options{Overwrite: OverwriteAlways, Progress: progress})
|
||||
progress.Finish()
|
||||
rtest.Equals(t, restoreui.State{
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/restic/restic/internal/repository"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
restoreui "github.com/restic/restic/internal/ui/restore"
|
||||
)
|
||||
|
||||
@@ -87,8 +88,8 @@ func testRestorerProgressBar(t *testing.T, dryRun bool) {
|
||||
},
|
||||
}, noopGetGenericAttributes)
|
||||
|
||||
mock := &printerMock{}
|
||||
progress := restoreui.NewProgress(mock, 0)
|
||||
mock := &printerMock{Printer: progress.NewNoopPrinter()}
|
||||
progress := restoreui.NewProgress(mock, true, false, true)
|
||||
res := NewRestorer(repo, sn, Options{Progress: progress, DryRun: dryRun})
|
||||
|
||||
tempdir := rtest.TempDir(t)
|
||||
|
||||
@@ -164,10 +164,10 @@ func isFile(fi os.FileInfo) bool {
|
||||
return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
|
||||
}
|
||||
|
||||
// ResetReadOnly recursively resets the read-only flag recursively for dir.
|
||||
// resetReadOnly recursively resets the read-only flag recursively for dir.
|
||||
// This is mainly used for tests on Windows, which is unable to delete a file
|
||||
// set read-only.
|
||||
func ResetReadOnly(t testing.TB, dir string) {
|
||||
func resetReadOnly(t testing.TB, dir string) {
|
||||
t.Helper()
|
||||
err := filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||||
if fi == nil {
|
||||
@@ -194,7 +194,7 @@ func ResetReadOnly(t testing.TB, dir string) {
|
||||
// afterwards uses os.RemoveAll() to remove the path.
|
||||
func RemoveAll(t testing.TB, path string) {
|
||||
t.Helper()
|
||||
ResetReadOnly(t, path)
|
||||
resetReadOnly(t, path)
|
||||
err := os.RemoveAll(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
|
||||
@@ -14,11 +14,8 @@ var (
|
||||
RunIntegrationTest = getBoolVar("RESTIC_TEST_INTEGRATION", true)
|
||||
RunFuseTest = getBoolVar("RESTIC_TEST_FUSE", true)
|
||||
TestSFTPPath = getStringVar("RESTIC_TEST_SFTPPATH", "/usr/lib/ssh:/usr/lib/openssh:/usr/libexec")
|
||||
TestWalkerPath = getStringVar("RESTIC_TEST_PATH", ".")
|
||||
BenchArchiveDirectory = getStringVar("RESTIC_BENCH_DIR", ".")
|
||||
TestS3Server = getStringVar("RESTIC_TEST_S3_SERVER", "")
|
||||
TestRESTServer = getStringVar("RESTIC_TEST_REST_SERVER", "")
|
||||
TestIntegrationDisallowSkip = getStringVar("RESTIC_TEST_DISALLOW_SKIP", "")
|
||||
testIntegrationDisallowSkip = getStringVar("RESTIC_TEST_DISALLOW_SKIP", "")
|
||||
)
|
||||
|
||||
func getStringVar(name, defaultValue string) string {
|
||||
@@ -49,7 +46,7 @@ func getBoolVar(name string, defaultValue bool) bool {
|
||||
// names that must be run. If name is in this list, the test is marked as
|
||||
// failed.
|
||||
func SkipDisallowed(t testing.TB, name string) {
|
||||
for _, s := range strings.Split(TestIntegrationDisallowSkip, ",") {
|
||||
for _, s := range strings.Split(testIntegrationDisallowSkip, ",") {
|
||||
if s == name {
|
||||
t.Fatalf("test %v is in list of tests that need to run ($RESTIC_TEST_DISALLOW_SKIP)", name)
|
||||
}
|
||||
|
||||
+15
-15
@@ -10,8 +10,8 @@ import (
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// JSONProgress reports progress for the `backup` command in JSON.
|
||||
type JSONProgress struct {
|
||||
// jsonProgress reports progress for the `backup` command in JSON.
|
||||
type jsonProgress struct {
|
||||
progress.Printer
|
||||
|
||||
term ui.Terminal
|
||||
@@ -19,27 +19,27 @@ type JSONProgress struct {
|
||||
}
|
||||
|
||||
// assert that Backup implements the ProgressPrinter interface
|
||||
var _ ProgressPrinter = &JSONProgress{}
|
||||
var _ ProgressPrinter = &jsonProgress{}
|
||||
|
||||
// NewJSONProgress returns a new backup progress reporter.
|
||||
func NewJSONProgress(term ui.Terminal, verbosity uint) *JSONProgress {
|
||||
return &JSONProgress{
|
||||
Printer: ui.NewProgressPrinter(true, verbosity, term),
|
||||
func NewJSONProgress(term ui.Terminal, verbosity uint) ProgressPrinter {
|
||||
return &jsonProgress{
|
||||
Printer: progress.NewTerminalPrinter(true, verbosity, term),
|
||||
term: term,
|
||||
v: verbosity,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *JSONProgress) print(status interface{}) {
|
||||
func (b *jsonProgress) print(status interface{}) {
|
||||
b.term.Print(ui.ToJSONString(status))
|
||||
}
|
||||
|
||||
func (b *JSONProgress) error(status interface{}) {
|
||||
func (b *jsonProgress) error(status interface{}) {
|
||||
b.term.Error(ui.ToJSONString(status))
|
||||
}
|
||||
|
||||
// Update updates the status lines.
|
||||
func (b *JSONProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
|
||||
func (b *jsonProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
|
||||
status := statusUpdate{
|
||||
MessageType: "status",
|
||||
SecondsElapsed: uint64(time.Since(start) / time.Second),
|
||||
@@ -65,7 +65,7 @@ func (b *JSONProgress) Update(total, processed Counter, errors uint, currentFile
|
||||
|
||||
// ScannerError is the error callback function for the scanner, it prints the
|
||||
// error in verbose mode and returns nil.
|
||||
func (b *JSONProgress) ScannerError(item string, err error) error {
|
||||
func (b *jsonProgress) ScannerError(item string, err error) error {
|
||||
b.error(errorUpdate{
|
||||
MessageType: "error",
|
||||
Error: errorObject{err.Error()},
|
||||
@@ -76,7 +76,7 @@ func (b *JSONProgress) ScannerError(item string, err error) error {
|
||||
}
|
||||
|
||||
// Error is the error callback function for the archiver, it prints the error and returns nil.
|
||||
func (b *JSONProgress) Error(item string, err error) error {
|
||||
func (b *jsonProgress) Error(item string, err error) error {
|
||||
b.error(errorUpdate{
|
||||
MessageType: "error",
|
||||
Error: errorObject{err.Error()},
|
||||
@@ -88,7 +88,7 @@ func (b *JSONProgress) Error(item string, err error) error {
|
||||
|
||||
// CompleteItem is the status callback function for the archiver when a
|
||||
// file/dir has been saved successfully.
|
||||
func (b *JSONProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
|
||||
func (b *jsonProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
|
||||
if b.v < 2 {
|
||||
return
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func (b *JSONProgress) CompleteItem(messageType, item string, s archiver.ItemSta
|
||||
}
|
||||
|
||||
// ReportTotal sets the total stats up to now
|
||||
func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
func (b *jsonProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
if b.v >= 2 {
|
||||
b.print(verboseUpdate{
|
||||
MessageType: "verbose_status",
|
||||
@@ -163,7 +163,7 @@ func (b *JSONProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
}
|
||||
|
||||
// Finish prints the finishing messages.
|
||||
func (b *JSONProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool) {
|
||||
func (b *jsonProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, dryRun bool) {
|
||||
id := ""
|
||||
// empty if snapshot creation was skipped
|
||||
if !snapshotID.IsNull() {
|
||||
@@ -192,7 +192,7 @@ func (b *JSONProgress) Finish(snapshotID restic.ID, summary *archiver.Summary, d
|
||||
}
|
||||
|
||||
// Reset no-op
|
||||
func (b *JSONProgress) Reset() {
|
||||
func (b *jsonProgress) Reset() {
|
||||
}
|
||||
|
||||
type statusUpdate struct {
|
||||
|
||||
@@ -45,7 +45,11 @@ type Progress struct {
|
||||
printer ProgressPrinter
|
||||
}
|
||||
|
||||
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
|
||||
func NewProgress(printer ProgressPrinter, quiet, json, canUpdateStatus bool) *Progress {
|
||||
return newProgress(printer, progress.CalculateProgressInterval(!quiet, json, canUpdateStatus))
|
||||
}
|
||||
|
||||
func newProgress(printer ProgressPrinter, interval time.Duration) *Progress {
|
||||
p := &Progress{
|
||||
start: time.Now(),
|
||||
currentFiles: make(map[string]struct{}),
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
type mockPrinter struct {
|
||||
sync.Mutex
|
||||
progress.NoopPrinter
|
||||
progress.Printer
|
||||
dirUnchanged, fileNew bool
|
||||
id restic.ID
|
||||
}
|
||||
@@ -48,8 +48,8 @@ func (p *mockPrinter) Reset() {}
|
||||
func TestProgress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prnt := &mockPrinter{}
|
||||
prog := NewProgress(prnt, time.Millisecond)
|
||||
prnt := &mockPrinter{Printer: progress.NewNoopPrinter()}
|
||||
prog := newProgress(prnt, time.Millisecond)
|
||||
|
||||
prog.StartFile("foo")
|
||||
prog.CompleteBlob(1024)
|
||||
|
||||
+13
-13
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// TextProgress reports progress for the `backup` command.
|
||||
type TextProgress struct {
|
||||
// textProgress reports progress for the `backup` command.
|
||||
type textProgress struct {
|
||||
progress.Printer
|
||||
|
||||
term ui.Terminal
|
||||
@@ -20,19 +20,19 @@ type TextProgress struct {
|
||||
}
|
||||
|
||||
// assert that Backup implements the ProgressPrinter interface
|
||||
var _ ProgressPrinter = &TextProgress{}
|
||||
var _ ProgressPrinter = &textProgress{}
|
||||
|
||||
// NewTextProgress returns a new backup progress reporter.
|
||||
func NewTextProgress(term ui.Terminal, verbosity uint) *TextProgress {
|
||||
return &TextProgress{
|
||||
Printer: ui.NewProgressPrinter(false, verbosity, term),
|
||||
func NewTextProgress(term ui.Terminal, verbosity uint) ProgressPrinter {
|
||||
return &textProgress{
|
||||
Printer: progress.NewTerminalPrinter(false, verbosity, term),
|
||||
term: term,
|
||||
verbosity: verbosity,
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the status lines.
|
||||
func (b *TextProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
|
||||
func (b *textProgress) Update(total, processed Counter, errors uint, currentFiles map[string]struct{}, start time.Time, secs uint64) {
|
||||
var status string
|
||||
if total.Files == 0 && total.Dirs == 0 {
|
||||
// no total count available yet
|
||||
@@ -74,7 +74,7 @@ func (b *TextProgress) Update(total, processed Counter, errors uint, currentFile
|
||||
|
||||
// ScannerError is the error callback function for the scanner, it prints the
|
||||
// error in verbose mode and returns nil.
|
||||
func (b *TextProgress) ScannerError(_ string, err error) error {
|
||||
func (b *textProgress) ScannerError(_ string, err error) error {
|
||||
if b.verbosity >= 2 {
|
||||
b.E("scan: %v\n", err)
|
||||
}
|
||||
@@ -82,14 +82,14 @@ func (b *TextProgress) ScannerError(_ string, err error) error {
|
||||
}
|
||||
|
||||
// Error is the error callback function for the archiver, it prints the error and returns nil.
|
||||
func (b *TextProgress) Error(_ string, err error) error {
|
||||
func (b *textProgress) Error(_ string, err error) error {
|
||||
b.E("error: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteItem is the status callback function for the archiver when a
|
||||
// file/dir has been saved successfully.
|
||||
func (b *TextProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
|
||||
func (b *textProgress) CompleteItem(messageType, item string, s archiver.ItemStats, d time.Duration) {
|
||||
item = ui.Quote(item)
|
||||
|
||||
switch messageType {
|
||||
@@ -115,7 +115,7 @@ func (b *TextProgress) CompleteItem(messageType, item string, s archiver.ItemSta
|
||||
}
|
||||
|
||||
// ReportTotal sets the total stats up to now
|
||||
func (b *TextProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
func (b *textProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
b.V("scan finished in %.3fs: %v files, %s",
|
||||
time.Since(start).Seconds(),
|
||||
s.Files, ui.FormatBytes(s.Bytes),
|
||||
@@ -123,14 +123,14 @@ func (b *TextProgress) ReportTotal(start time.Time, s archiver.ScanStats) {
|
||||
}
|
||||
|
||||
// Reset status
|
||||
func (b *TextProgress) Reset() {
|
||||
func (b *textProgress) Reset() {
|
||||
if b.term.CanUpdateStatus() {
|
||||
b.term.SetStatus(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Finish prints the finishing messages.
|
||||
func (b *TextProgress) Finish(id restic.ID, summary *archiver.Summary, dryRun bool) {
|
||||
func (b *textProgress) Finish(id restic.ID, summary *archiver.Summary, dryRun bool) {
|
||||
b.P("\n")
|
||||
b.P("Files: %5d new, %5d changed, %5d unmodified\n", summary.Files.New, summary.Files.Changed, summary.Files.Unchanged)
|
||||
b.P("Dirs: %5d new, %5d changed, %5d unmodified\n", summary.Dirs.New, summary.Dirs.Changed, summary.Dirs.Unchanged)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Message reports progress with messages of different verbosity.
|
||||
type Message struct {
|
||||
term Terminal
|
||||
v uint
|
||||
}
|
||||
|
||||
// NewMessage returns a message progress reporter with underlying terminal
|
||||
// term.
|
||||
func NewMessage(term Terminal, verbosity uint) *Message {
|
||||
return &Message{
|
||||
term: term,
|
||||
v: verbosity,
|
||||
}
|
||||
}
|
||||
|
||||
// E reports an error. This message is always printed to stderr.
|
||||
func (m *Message) E(msg string, args ...interface{}) {
|
||||
m.term.Error(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
// S prints a message, this is should only be used for very important messages
|
||||
// that are not errors. The message is even printed if --quiet is specified.
|
||||
func (m *Message) S(msg string, args ...interface{}) {
|
||||
m.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
// PT prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified)
|
||||
// and stdout points to a terminal.
|
||||
// This is used for informational messages.
|
||||
func (m *Message) PT(msg string, args ...interface{}) {
|
||||
if m.term.OutputIsTerminal() && m.v >= 1 {
|
||||
m.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// P prints a message if verbosity >= 1 (neither --quiet nor --verbose is specified).
|
||||
// This is used for normal messages which are not errors.
|
||||
func (m *Message) P(msg string, args ...interface{}) {
|
||||
if m.v >= 1 {
|
||||
m.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// V prints a message if verbosity >= 2 (equivalent to --verbose), this is used for
|
||||
// verbose messages.
|
||||
func (m *Message) V(msg string, args ...interface{}) {
|
||||
if m.v >= 2 {
|
||||
m.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// VV prints a message if verbosity >= 3 (equivalent to --verbose=2), this is used for
|
||||
// debug messages.
|
||||
func (m *Message) VV(msg string, args ...interface{}) {
|
||||
if m.v >= 3 {
|
||||
m.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// CalculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
|
||||
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
|
||||
// for non-interactive terminals or when run using the --quiet flag
|
||||
func CalculateProgressInterval(show bool, json bool, canUpdateStatus bool) time.Duration {
|
||||
interval := time.Second / 10
|
||||
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
|
||||
if err == nil && fps > 0 {
|
||||
if fps > 60 {
|
||||
fps = 60
|
||||
}
|
||||
interval = time.Duration(float64(time.Second) / fps)
|
||||
} else if !json && !canUpdateStatus || !show {
|
||||
interval = 0
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
// newProgressMax returns a progress.Counter that prints to terminal if provided.
|
||||
func newProgressMax(show bool, max uint64, description string, term Terminal) *progress.Counter {
|
||||
if !show {
|
||||
return nil
|
||||
}
|
||||
interval := CalculateProgressInterval(show, false, term.CanUpdateStatus())
|
||||
|
||||
return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
|
||||
var status string
|
||||
if max == 0 {
|
||||
status = fmt.Sprintf("[%s] %d %s",
|
||||
FormatDuration(d), v, description)
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] %s %d / %d %s",
|
||||
FormatDuration(d), FormatPercent(v, max), v, max, description)
|
||||
}
|
||||
|
||||
if final {
|
||||
term.SetStatus(nil)
|
||||
term.Print(status)
|
||||
} else {
|
||||
term.SetStatus([]string{status})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type progressPrinter struct {
|
||||
term Terminal
|
||||
Message
|
||||
show bool
|
||||
}
|
||||
|
||||
func (t *progressPrinter) NewCounter(description string) *progress.Counter {
|
||||
return newProgressMax(t.show, 0, description, t.term)
|
||||
}
|
||||
|
||||
func (t *progressPrinter) NewCounterTerminalOnly(description string) *progress.Counter {
|
||||
return newProgressMax(t.show && t.term.OutputIsTerminal(), 0, description, t.term)
|
||||
}
|
||||
|
||||
func NewProgressPrinter(json bool, verbosity uint, term Terminal) progress.Printer {
|
||||
if json {
|
||||
verbosity = 0
|
||||
}
|
||||
return &progressPrinter{
|
||||
term: term,
|
||||
Message: *NewMessage(term, verbosity),
|
||||
show: verbosity > 0,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package progress
|
||||
|
||||
import "testing"
|
||||
|
||||
// A Printer can can return a new counter or print messages
|
||||
// at different log levels.
|
||||
// It must be safe to call its methods from concurrent goroutines.
|
||||
@@ -34,72 +32,32 @@ type Printer interface {
|
||||
VV(msg string, args ...interface{})
|
||||
}
|
||||
|
||||
// NoopPrinter discards all messages
|
||||
type NoopPrinter struct{}
|
||||
// noopPrinter discards all messages.
|
||||
type noopPrinter struct{}
|
||||
|
||||
var _ Printer = (*NoopPrinter)(nil)
|
||||
var _ Printer = (*noopPrinter)(nil)
|
||||
|
||||
func (*NoopPrinter) NewCounter(_ string) *Counter {
|
||||
// NewNoopPrinter returns a Printer that discards all messages.
|
||||
func NewNoopPrinter() Printer {
|
||||
return &noopPrinter{}
|
||||
}
|
||||
|
||||
func (*noopPrinter) NewCounter(_ string) *Counter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NoopPrinter) NewCounterTerminalOnly(_ string) *Counter {
|
||||
func (*noopPrinter) NewCounterTerminalOnly(_ string) *Counter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NoopPrinter) E(_ string, _ ...interface{}) {}
|
||||
func (*noopPrinter) E(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*NoopPrinter) S(_ string, _ ...interface{}) {}
|
||||
func (*noopPrinter) S(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*NoopPrinter) PT(_ string, _ ...interface{}) {}
|
||||
func (*noopPrinter) PT(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*NoopPrinter) P(_ string, _ ...interface{}) {}
|
||||
func (*noopPrinter) P(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*NoopPrinter) V(_ string, _ ...interface{}) {}
|
||||
func (*noopPrinter) V(_ string, _ ...interface{}) {}
|
||||
|
||||
func (*NoopPrinter) VV(_ string, _ ...interface{}) {}
|
||||
|
||||
// TestPrinter prints messages during testing
|
||||
type TestPrinter struct {
|
||||
t testing.TB
|
||||
}
|
||||
|
||||
func NewTestPrinter(t testing.TB) *TestPrinter {
|
||||
return &TestPrinter{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Printer = (*TestPrinter)(nil)
|
||||
|
||||
func (p *TestPrinter) NewCounter(_ string) *Counter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TestPrinter) NewCounterTerminalOnly(_ string) *Counter {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TestPrinter) E(msg string, args ...interface{}) {
|
||||
p.t.Logf("error: "+msg, args...)
|
||||
}
|
||||
|
||||
func (p *TestPrinter) S(msg string, args ...interface{}) {
|
||||
p.t.Logf("stdout: "+msg, args...)
|
||||
}
|
||||
|
||||
func (p *TestPrinter) PT(msg string, args ...interface{}) {
|
||||
p.t.Logf("stdout(terminal): "+msg, args...)
|
||||
}
|
||||
|
||||
func (p *TestPrinter) P(msg string, args ...interface{}) {
|
||||
p.t.Logf("print: "+msg, args...)
|
||||
}
|
||||
|
||||
func (p *TestPrinter) V(msg string, args ...interface{}) {
|
||||
p.t.Logf("verbose: "+msg, args...)
|
||||
}
|
||||
|
||||
func (p *TestPrinter) VV(msg string, args ...interface{}) {
|
||||
p.t.Logf("verbose2: "+msg, args...)
|
||||
}
|
||||
func (*noopPrinter) VV(_ string, _ ...interface{}) {}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
// CalculateProgressInterval returns the interval configured via RESTIC_PROGRESS_FPS
|
||||
// or if unset returns an interval for 60fps on interactive terminals and 0 (=disabled)
|
||||
// for non-interactive terminals or when run using the --quiet flag
|
||||
func CalculateProgressInterval(show bool, json bool, canUpdateStatus bool) time.Duration {
|
||||
interval := time.Second / 10
|
||||
fps, err := strconv.ParseFloat(os.Getenv("RESTIC_PROGRESS_FPS"), 64)
|
||||
if err == nil && fps > 0 {
|
||||
if fps > 60 {
|
||||
fps = 60
|
||||
}
|
||||
interval = time.Duration(float64(time.Second) / fps)
|
||||
} else if !json && !canUpdateStatus || !show {
|
||||
interval = 0
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
// newProgressMax returns a progress.Counter that prints to terminal if provided.
|
||||
func newProgressMax(show bool, max uint64, description string, term ui.Terminal) *Counter {
|
||||
if !show {
|
||||
return nil
|
||||
}
|
||||
interval := CalculateProgressInterval(show, false, term.CanUpdateStatus())
|
||||
|
||||
return NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
|
||||
var status string
|
||||
if max == 0 {
|
||||
status = fmt.Sprintf("[%s] %d %s",
|
||||
ui.FormatDuration(d), v, description)
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] %s %d / %d %s",
|
||||
ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
|
||||
}
|
||||
|
||||
if final {
|
||||
term.SetStatus(nil)
|
||||
term.Print(status)
|
||||
} else {
|
||||
term.SetStatus([]string{status})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type terminalPrinter struct {
|
||||
term ui.Terminal
|
||||
v uint
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) NewCounter(description string) *Counter {
|
||||
return newProgressMax(t.v > 0, 0, description, t.term)
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) NewCounterTerminalOnly(description string) *Counter {
|
||||
return newProgressMax(t.v > 0 && t.term.OutputIsTerminal(), 0, description, t.term)
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) E(msg string, args ...interface{}) {
|
||||
t.term.Error(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) S(msg string, args ...interface{}) {
|
||||
t.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) PT(msg string, args ...interface{}) {
|
||||
if t.term.OutputIsTerminal() && t.v >= 1 {
|
||||
t.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) P(msg string, args ...interface{}) {
|
||||
if t.v >= 1 {
|
||||
t.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) V(msg string, args ...interface{}) {
|
||||
if t.v >= 2 {
|
||||
t.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *terminalPrinter) VV(msg string, args ...interface{}) {
|
||||
if t.v >= 3 {
|
||||
t.term.Print(fmt.Sprintf(msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func NewTerminalPrinter(json bool, verbosity uint, term ui.Terminal) Printer {
|
||||
if json {
|
||||
verbosity = 0
|
||||
}
|
||||
return &terminalPrinter{
|
||||
term: term,
|
||||
v: verbosity,
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type jsonPrinter struct {
|
||||
|
||||
func NewJSONProgress(terminal ui.Terminal, verbosity uint) ProgressPrinter {
|
||||
return &jsonPrinter{
|
||||
Printer: ui.NewProgressPrinter(true, verbosity, terminal),
|
||||
Printer: progress.NewTerminalPrinter(true, verbosity, terminal),
|
||||
terminal: terminal,
|
||||
verbosity: verbosity,
|
||||
}
|
||||
|
||||
@@ -53,7 +53,11 @@ const (
|
||||
ActionDeleted ItemAction = "deleted"
|
||||
)
|
||||
|
||||
func NewProgress(printer ProgressPrinter, interval time.Duration) *Progress {
|
||||
func NewProgress(printer ProgressPrinter, quiet, json, canUpdateStatus bool) *Progress {
|
||||
return newProgress(printer, progress.CalculateProgressInterval(!quiet, json, canUpdateStatus))
|
||||
}
|
||||
|
||||
func newProgress(printer ProgressPrinter, interval time.Duration) *Progress {
|
||||
p := &Progress{
|
||||
progressInfoMap: make(map[string]progressInfoEntry),
|
||||
started: time.Now(),
|
||||
|
||||
@@ -37,7 +37,7 @@ type mockPrinter struct {
|
||||
trace printerTrace
|
||||
items itemTrace
|
||||
errors errorTrace
|
||||
progress.NoopPrinter
|
||||
progress.Printer
|
||||
}
|
||||
|
||||
const mockFinishDuration = 42 * time.Second
|
||||
@@ -57,8 +57,8 @@ func (p *mockPrinter) Finish(progress State, _ time.Duration) {
|
||||
}
|
||||
|
||||
func testProgress(fn func(progress *Progress) bool) (printerTrace, itemTrace, errorTrace) {
|
||||
printer := &mockPrinter{}
|
||||
progress := NewProgress(printer, 0)
|
||||
printer := &mockPrinter{Printer: progress.NewNoopPrinter()}
|
||||
progress := newProgress(printer, 0)
|
||||
final := fn(progress)
|
||||
progress.update(0, final)
|
||||
trace := append(printerTrace{}, printer.trace...)
|
||||
|
||||
@@ -16,7 +16,7 @@ type textPrinter struct {
|
||||
|
||||
func NewTextProgress(terminal ui.Terminal, verbosity uint) ProgressPrinter {
|
||||
return &textPrinter{
|
||||
Printer: ui.NewProgressPrinter(false, verbosity, terminal),
|
||||
Printer: progress.NewTerminalPrinter(false, verbosity, terminal),
|
||||
terminal: terminal,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/ui"
|
||||
"github.com/restic/restic/internal/ui/progress"
|
||||
)
|
||||
|
||||
// Progress reports progress for the stats command.
|
||||
type Progress struct {
|
||||
progress.Updater
|
||||
|
||||
term ui.Terminal
|
||||
m sync.Mutex
|
||||
snapshotCount uint64
|
||||
show bool
|
||||
|
||||
processedSnapshotCount uint64
|
||||
processedFileCount uint64
|
||||
processedBlobCount uint64
|
||||
processedSize uint64
|
||||
}
|
||||
|
||||
// NewProgress returns a new stats progress reporter.
|
||||
func NewProgress(term ui.Terminal, quiet, json bool, snapshotCount uint64) *Progress {
|
||||
p := newProgress(term, !json, snapshotCount)
|
||||
p.Updater = *progress.NewUpdater(
|
||||
progress.CalculateProgressInterval(!quiet, json, term.CanUpdateStatus()),
|
||||
p.printProgress,
|
||||
)
|
||||
return p
|
||||
}
|
||||
|
||||
func newProgress(term ui.Terminal, show bool, snapshotCount uint64) *Progress {
|
||||
return &Progress{
|
||||
term: term,
|
||||
snapshotCount: snapshotCount,
|
||||
show: show,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Progress) printProgress(runtime time.Duration, final bool) {
|
||||
if !p.show {
|
||||
return
|
||||
}
|
||||
p.m.Lock()
|
||||
|
||||
progressBase := p.processedSnapshotCount
|
||||
if progressBase > 0 && !final {
|
||||
progressBase--
|
||||
}
|
||||
|
||||
status := fmt.Sprintf("[%s] %s %d / %d snapshots", ui.FormatDuration(runtime), ui.FormatPercent(progressBase, p.snapshotCount), p.processedSnapshotCount, p.snapshotCount)
|
||||
|
||||
if p.processedFileCount > 0 {
|
||||
status += fmt.Sprintf(", %v files", p.processedFileCount)
|
||||
}
|
||||
|
||||
if p.processedBlobCount > 0 {
|
||||
status += fmt.Sprintf(", %d blobs", p.processedBlobCount)
|
||||
}
|
||||
|
||||
status += fmt.Sprintf(", %s", ui.FormatBytes(p.processedSize))
|
||||
p.m.Unlock()
|
||||
|
||||
if final {
|
||||
p.term.SetStatus(nil)
|
||||
p.term.Print(status)
|
||||
} else {
|
||||
p.term.SetStatus([]string{status})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Progress) Update(fileCount uint64, blobCount uint64, size uint64) {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
p.processedFileCount += fileCount
|
||||
p.processedBlobCount += blobCount
|
||||
p.processedSize += size
|
||||
}
|
||||
|
||||
func (p *Progress) ProcessSnapshot() {
|
||||
p.m.Lock()
|
||||
defer p.m.Unlock()
|
||||
|
||||
p.processedSnapshotCount++
|
||||
p.processedFileCount = 0
|
||||
p.processedBlobCount = 0
|
||||
p.processedSize = 0
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
func TestStatsProgress(t *testing.T) {
|
||||
term := &ui.MockTerminal{}
|
||||
|
||||
progress := newProgress(term, true, 2)
|
||||
progress.printProgress(0*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:00] 0.00% 0 / 2 snapshots, 0 B"}, term.Output)
|
||||
|
||||
progress.ProcessSnapshot()
|
||||
progress.Update(1, 2, 3)
|
||||
progress.printProgress(5*time.Second, false)
|
||||
// Output differs from the previous one because the progress is based on the number of processed snapshots,
|
||||
// 1/2 snapshots means processing the snapshot 1 currently
|
||||
rtest.Equals(t, []string{"[0:05] 0.00% 1 / 2 snapshots, 1 files, 2 blobs, 3 B"}, term.Output)
|
||||
|
||||
progress.ProcessSnapshot()
|
||||
progress.printProgress(10*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:10] 50.00% 2 / 2 snapshots, 0 B"}, term.Output)
|
||||
|
||||
progress.Update(4, 5, 6)
|
||||
progress.printProgress(15*time.Second, false)
|
||||
rtest.Equals(t, []string{"[0:15] 50.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
|
||||
|
||||
progress.printProgress(20*time.Second, true)
|
||||
rtest.Equals(t, []string{"[0:20] 100.00% 2 / 2 snapshots, 4 files, 5 blobs, 6 B"}, term.Output)
|
||||
}
|
||||
|
||||
func TestStatsProgressJSON(t *testing.T) {
|
||||
term := &ui.MockTerminal{}
|
||||
|
||||
progress := newProgress(term, false, 2)
|
||||
progress.printProgress(0*time.Second, false)
|
||||
// JSON output is not available yet, so just make sure to not break normal json output
|
||||
rtest.Equals(t, nil, term.Output)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// Terminal is used to write messages and display status lines which can be
|
||||
// updated. See termstatus.Terminal for a concrete implementation.
|
||||
// updated. See termstatus.Setup for a concrete implementation.
|
||||
type Terminal interface {
|
||||
// Print writes a line to the terminal. Appends a newline if not present.
|
||||
Print(line string)
|
||||
|
||||
@@ -9,16 +9,16 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
tty "github.com/restic/restic/internal/terminal"
|
||||
"github.com/restic/restic/internal/ui"
|
||||
)
|
||||
|
||||
var _ ui.Terminal = &Terminal{}
|
||||
var _ ui.Terminal = &terminal{}
|
||||
|
||||
// Terminal is used to write messages and display status lines which can be
|
||||
// terminal is used to write messages and display status lines which can be
|
||||
// updated. When the output is redirected to a file, the status lines are not
|
||||
// printed.
|
||||
type Terminal struct {
|
||||
type terminal struct {
|
||||
rd io.ReadCloser
|
||||
inFd uintptr
|
||||
wr io.Writer
|
||||
@@ -58,7 +58,15 @@ type fder interface {
|
||||
}
|
||||
|
||||
// Setup creates a new termstatus.
|
||||
// The returned function must be called to shut down the termstatus,
|
||||
// The returned function must be called to shut down the termstatus.
|
||||
//
|
||||
// A goroutine is started to update the
|
||||
// terminal. It is terminated when ctx is cancelled. When stdout is redirected to
|
||||
// a file (e.g. via shell output redirection) or is just an io.Writer (not the
|
||||
// open *os.File for stdout), no status lines are printed. The status lines and
|
||||
// normal output (via Print/Printf) are written to stdout, error messages are
|
||||
// written to stderr. If quiet is set to true, no status messages
|
||||
// are printed even if the terminal supports it.
|
||||
//
|
||||
// Expected usage:
|
||||
// ```
|
||||
@@ -66,12 +74,12 @@ type fder interface {
|
||||
// defer cancel()
|
||||
// // do stuff
|
||||
// ```
|
||||
func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (*Terminal, func()) {
|
||||
func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (ui.Terminal, func()) {
|
||||
var wg sync.WaitGroup
|
||||
// only shutdown once cancel is called to ensure that no output is lost
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
term := New(stdin, stdout, stderr, quiet)
|
||||
term := new(stdin, stdout, stderr, quiet)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -89,15 +97,8 @@ func Setup(stdin io.ReadCloser, stdout, stderr io.Writer, quiet bool) (*Terminal
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new Terminal for wr. A goroutine is started to update the
|
||||
// terminal. It is terminated when ctx is cancelled. When wr is redirected to
|
||||
// a file (e.g. via shell output redirection) or is just an io.Writer (not the
|
||||
// open *os.File for stdout), no status lines are printed. The status lines and
|
||||
// normal output (via Print/Printf) are written to wr, error messages are
|
||||
// written to errWriter. If disableStatus is set to true, no status messages
|
||||
// are printed even if the terminal supports it.
|
||||
func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool) *Terminal {
|
||||
t := &Terminal{
|
||||
func new(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool) *terminal {
|
||||
t := &terminal{
|
||||
rd: rd,
|
||||
wr: wr,
|
||||
errWriter: errWriter,
|
||||
@@ -111,22 +112,22 @@ func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool
|
||||
}
|
||||
|
||||
if d, ok := rd.(fder); ok {
|
||||
if terminal.InputIsTerminal(d.Fd()) {
|
||||
if tty.InputIsTerminal(d.Fd()) {
|
||||
t.inFd = d.Fd()
|
||||
t.inputIsTerminal = true
|
||||
}
|
||||
}
|
||||
|
||||
if d, ok := wr.(fder); ok {
|
||||
if terminal.CanUpdateStatus(d.Fd()) {
|
||||
if tty.CanUpdateStatus(d.Fd()) {
|
||||
// only use the fancy status code when we're running on a real terminal.
|
||||
t.canUpdateStatus = true
|
||||
t.fd = d.Fd()
|
||||
t.clearCurrentLine = terminal.ClearCurrentLine(t.fd)
|
||||
t.moveCursorUp = terminal.MoveCursorUp(t.fd)
|
||||
t.moveCursorDown = terminal.MoveCursorDown(t.fd)
|
||||
t.clearCurrentLine = tty.ClearCurrentLine(t.fd)
|
||||
t.moveCursorUp = tty.MoveCursorUp(t.fd)
|
||||
t.moveCursorDown = tty.MoveCursorDown(t.fd)
|
||||
}
|
||||
if terminal.OutputIsTerminal(d.Fd()) {
|
||||
if tty.OutputIsTerminal(d.Fd()) {
|
||||
t.outputIsTerminal = true
|
||||
}
|
||||
}
|
||||
@@ -135,19 +136,19 @@ func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool
|
||||
}
|
||||
|
||||
// InputIsTerminal returns whether the input is a terminal.
|
||||
func (t *Terminal) InputIsTerminal() bool {
|
||||
func (t *terminal) InputIsTerminal() bool {
|
||||
return t.inputIsTerminal
|
||||
}
|
||||
|
||||
// InputRaw returns the input reader.
|
||||
func (t *Terminal) InputRaw() io.ReadCloser {
|
||||
func (t *terminal) InputRaw() io.ReadCloser {
|
||||
return t.rd
|
||||
}
|
||||
|
||||
func (t *Terminal) ReadPassword(ctx context.Context, prompt string) (string, error) {
|
||||
func (t *terminal) ReadPassword(ctx context.Context, prompt string) (string, error) {
|
||||
if t.InputIsTerminal() {
|
||||
t.Flush()
|
||||
return terminal.ReadPassword(ctx, int(t.inFd), t.errWriter, prompt)
|
||||
return tty.ReadPassword(ctx, int(t.inFd), t.errWriter, prompt)
|
||||
}
|
||||
if t.OutputIsTerminal() {
|
||||
t.Print("reading repository password from stdin")
|
||||
@@ -166,13 +167,13 @@ func readPassword(in io.Reader) (password string, err error) {
|
||||
}
|
||||
|
||||
// CanUpdateStatus return whether the status output is updated in place.
|
||||
func (t *Terminal) CanUpdateStatus() bool {
|
||||
func (t *terminal) CanUpdateStatus() bool {
|
||||
return t.canUpdateStatus
|
||||
}
|
||||
|
||||
// OutputWriter returns a output writer that is safe for concurrent use with
|
||||
// other output methods. Output is only shown after a line break.
|
||||
func (t *Terminal) OutputWriter() io.Writer {
|
||||
func (t *terminal) OutputWriter() io.Writer {
|
||||
t.outputWriterOnce.Do(func() {
|
||||
t.outputWriter = newLineWriter(t.Print)
|
||||
})
|
||||
@@ -182,19 +183,19 @@ func (t *Terminal) OutputWriter() io.Writer {
|
||||
// OutputRaw returns the raw output writer. Should only be used if there is no
|
||||
// other option. Must not be used in combination with Print, Error, SetStatus
|
||||
// or any other method that writes to the terminal.
|
||||
func (t *Terminal) OutputRaw() io.Writer {
|
||||
func (t *terminal) OutputRaw() io.Writer {
|
||||
t.Flush()
|
||||
return t.wr
|
||||
}
|
||||
|
||||
// OutputIsTerminal returns whether the output is a terminal.
|
||||
func (t *Terminal) OutputIsTerminal() bool {
|
||||
func (t *terminal) OutputIsTerminal() bool {
|
||||
return t.outputIsTerminal
|
||||
}
|
||||
|
||||
// Run updates the screen. It should be run in a separate goroutine. When
|
||||
// ctx is cancelled, the status lines are cleanly removed.
|
||||
func (t *Terminal) Run(ctx context.Context) {
|
||||
func (t *terminal) Run(ctx context.Context) {
|
||||
defer close(t.closed)
|
||||
if t.canUpdateStatus {
|
||||
t.run(ctx)
|
||||
@@ -205,12 +206,12 @@ func (t *Terminal) Run(ctx context.Context) {
|
||||
}
|
||||
|
||||
// run listens on the channels and updates the terminal screen.
|
||||
func (t *Terminal) run(ctx context.Context) {
|
||||
func (t *terminal) run(ctx context.Context) {
|
||||
var status []string
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !terminal.IsProcessBackground(t.fd) {
|
||||
if !tty.IsProcessBackground(t.fd) {
|
||||
t.writeStatus([]string{}, false)
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ func (t *Terminal) run(ctx context.Context) {
|
||||
msg.barrier <- struct{}{}
|
||||
continue
|
||||
}
|
||||
if terminal.IsProcessBackground(t.fd) {
|
||||
if tty.IsProcessBackground(t.fd) {
|
||||
// ignore all messages, do nothing, we are in the background process group
|
||||
continue
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func (t *Terminal) run(ctx context.Context) {
|
||||
case stat := <-t.status:
|
||||
status = append(status[:0], stat.lines...)
|
||||
|
||||
if terminal.IsProcessBackground(t.fd) {
|
||||
if tty.IsProcessBackground(t.fd) {
|
||||
// ignore all messages, do nothing, we are in the background process group
|
||||
continue
|
||||
}
|
||||
@@ -256,13 +257,13 @@ func (t *Terminal) run(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) logWriteErr(err error) {
|
||||
func (t *terminal) logWriteErr(err error) {
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(t.errWriter, "write failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) writeStatus(status []string, skipUnchanged bool) {
|
||||
func (t *terminal) writeStatus(status []string, skipUnchanged bool) {
|
||||
var unchanged []bool
|
||||
if skipUnchanged {
|
||||
if slices.Equal(status, t.lastStatus) {
|
||||
@@ -321,7 +322,7 @@ func findUnchangedLines(curr, last []string) []bool {
|
||||
|
||||
// runWithoutStatus listens on the channels and just prints out the messages,
|
||||
// without status lines.
|
||||
func (t *Terminal) runWithoutStatus(ctx context.Context) {
|
||||
func (t *terminal) runWithoutStatus(ctx context.Context) {
|
||||
var lastStatus []string
|
||||
for {
|
||||
select {
|
||||
@@ -358,7 +359,7 @@ func (t *Terminal) runWithoutStatus(ctx context.Context) {
|
||||
}
|
||||
|
||||
// Flush waits for all pending messages to be printed.
|
||||
func (t *Terminal) Flush() {
|
||||
func (t *terminal) Flush() {
|
||||
ch := make(chan struct{})
|
||||
defer close(ch)
|
||||
select {
|
||||
@@ -371,7 +372,7 @@ func (t *Terminal) Flush() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) print(line string, isErr bool) {
|
||||
func (t *terminal) print(line string, isErr bool) {
|
||||
// make sure the line ends with a line break
|
||||
if len(line) == 0 || line[len(line)-1] != '\n' {
|
||||
line += "\n"
|
||||
@@ -384,12 +385,12 @@ func (t *Terminal) print(line string, isErr bool) {
|
||||
}
|
||||
|
||||
// Print writes a line to the terminal.
|
||||
func (t *Terminal) Print(line string) {
|
||||
func (t *terminal) Print(line string) {
|
||||
t.print(line, false)
|
||||
}
|
||||
|
||||
// Error writes an error to the terminal.
|
||||
func (t *Terminal) Error(line string) {
|
||||
func (t *terminal) Error(line string) {
|
||||
t.print(line, true)
|
||||
}
|
||||
|
||||
@@ -409,11 +410,11 @@ func sanitizeLines(lines []string, width int) []string {
|
||||
// SetStatus updates the status lines.
|
||||
// The lines should not contain newlines; this method adds them.
|
||||
// Pass nil or an empty array to remove the status lines.
|
||||
func (t *Terminal) SetStatus(lines []string) {
|
||||
func (t *terminal) SetStatus(lines []string) {
|
||||
// only truncate interactive status output
|
||||
var width int
|
||||
if t.canUpdateStatus {
|
||||
width = terminal.Width(t.fd)
|
||||
width = tty.Width(t.fd)
|
||||
if width <= 0 {
|
||||
// use 80 columns by default
|
||||
width = 80
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
tty "github.com/restic/restic/internal/terminal"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
@@ -17,9 +17,9 @@ func TestSetStatus(t *testing.T) {
|
||||
buf, term, cancel := setupStatusTest()
|
||||
|
||||
const (
|
||||
cl = terminal.PosixControlClearLine
|
||||
home = terminal.PosixControlMoveCursorHome
|
||||
up = terminal.PosixControlMoveCursorUp
|
||||
cl = tty.PosixControlClearLine
|
||||
home = tty.PosixControlMoveCursorHome
|
||||
up = tty.PosixControlMoveCursorUp
|
||||
|
||||
clearLn = home + cl
|
||||
)
|
||||
@@ -55,10 +55,10 @@ func TestSetStatusUnchangedLines(t *testing.T) {
|
||||
buf, term, cancel := setupStatusTest()
|
||||
|
||||
const (
|
||||
cl = terminal.PosixControlClearLine
|
||||
home = terminal.PosixControlMoveCursorHome
|
||||
up = terminal.PosixControlMoveCursorUp
|
||||
down = terminal.PosixControlMoveCursorDown
|
||||
cl = tty.PosixControlClearLine
|
||||
home = tty.PosixControlMoveCursorHome
|
||||
up = tty.PosixControlMoveCursorUp
|
||||
down = tty.PosixControlMoveCursorDown
|
||||
|
||||
clearLn = home + cl
|
||||
stepDown = home + down
|
||||
@@ -82,15 +82,15 @@ func TestSetStatusUnchangedLines(t *testing.T) {
|
||||
rtest.Equals(t, exp, buf.String())
|
||||
}
|
||||
|
||||
func setupStatusTest() (*bytes.Buffer, *Terminal, context.CancelFunc) {
|
||||
func setupStatusTest() (*bytes.Buffer, *terminal, context.CancelFunc) {
|
||||
buf := &bytes.Buffer{}
|
||||
term := New(nil, buf, buf, false)
|
||||
term := new(nil, buf, buf, false)
|
||||
|
||||
term.canUpdateStatus = true
|
||||
term.fd = ^uintptr(0)
|
||||
term.clearCurrentLine = terminal.PosixClearCurrentLine
|
||||
term.moveCursorUp = terminal.PosixMoveCursorUp
|
||||
term.moveCursorDown = terminal.PosixMoveCursorDown
|
||||
term.clearCurrentLine = tty.PosixClearCurrentLine
|
||||
term.moveCursorUp = tty.PosixMoveCursorUp
|
||||
term.moveCursorDown = tty.PosixMoveCursorDown
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go term.Run(ctx)
|
||||
@@ -101,8 +101,8 @@ func TestPrint(t *testing.T) {
|
||||
buf, term, cancel := setupStatusTest()
|
||||
|
||||
const (
|
||||
cl = terminal.PosixControlClearLine
|
||||
home = terminal.PosixControlMoveCursorHome
|
||||
cl = tty.PosixControlClearLine
|
||||
home = tty.PosixControlMoveCursorHome
|
||||
)
|
||||
|
||||
term.Print("test")
|
||||
@@ -148,7 +148,7 @@ func TestReadPassword(t *testing.T) {
|
||||
|
||||
func TestReadPasswordTerminal(t *testing.T) {
|
||||
expected := "password"
|
||||
term := New(io.NopCloser(strings.NewReader(expected)), io.Discard, io.Discard, false)
|
||||
term := new(io.NopCloser(strings.NewReader(expected)), io.Discard, io.Discard, false)
|
||||
pw, err := term.ReadPassword(context.Background(), "test")
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, expected, pw)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package walker
|
||||
Reference in New Issue
Block a user