From d2fb677aa3244a244ee937d80ad06a3d8adb3b0a Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 8 Feb 2026 08:35:13 -0800 Subject: [PATCH] feat: add progress bar to restore operation Add an interactive progress bar (using schollz/progressbar) to the file restore loop, matching the existing pattern in verify. Shows bytes restored with ETA when output is a terminal, falls back to structured log progress every 100 files otherwise. Fixes #20 --- internal/vaultik/restore.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index 015c533..104ef1a 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -111,6 +111,30 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { result := &RestoreResult{} blobCache := make(map[string][]byte) // Cache downloaded and decrypted blobs + // Calculate total bytes for progress bar + var totalBytesExpected int64 + for _, file := range files { + totalBytesExpected += file.Size + } + + // Create progress bar if output is a terminal + var bar *progressbar.ProgressBar + if isTerminal() { + bar = progressbar.NewOptions64( + totalBytesExpected, + progressbar.OptionSetDescription("Restoring"), + progressbar.OptionSetWriter(os.Stderr), + progressbar.OptionShowBytes(true), + progressbar.OptionShowCount(), + progressbar.OptionSetWidth(40), + progressbar.OptionThrottle(100*time.Millisecond), + progressbar.OptionOnCompletion(func() { + fmt.Fprint(os.Stderr, "\n") + }), + progressbar.OptionSetRenderBlankState(true), + ) + } + for i, file := range files { if v.ctx.Err() != nil { return v.ctx.Err() @@ -119,10 +143,14 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { if err := v.restoreFile(v.ctx, repos, file, opts.TargetDir, identity, chunkToBlobMap, blobCache, result); err != nil { log.Error("Failed to restore file", "path", file.Path, "error", err) // Continue with other files - continue } - // Progress logging + // Update progress bar + if bar != nil { + _ = bar.Add64(file.Size) + } + + // Progress logging (for non-terminal or structured logs) if (i+1)%100 == 0 || i+1 == len(files) { log.Info("Restore progress", "files", fmt.Sprintf("%d/%d", i+1, len(files)), @@ -131,6 +159,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { } } + if bar != nil { + _ = bar.Finish() + } + result.Duration = time.Since(startTime) log.Info("Restore complete",