From 4345da2f0c3c04ed8e445ca4a79fc602ba384ff7 Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 8 Feb 2026 09:08:58 -0800 Subject: [PATCH] feat: add progress bar to restore operation Adds a byte-based progress bar to file restoration, matching the existing pattern used by the verify operation. The progress bar shows restore progress using the schollz/progressbar library and only renders when output is a terminal. Closes #20 --- internal/vaultik/restore.go | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index 015c533..f9b36ae 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -111,26 +111,59 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { result := &RestoreResult{} blobCache := make(map[string][]byte) // Cache downloaded and decrypted blobs - for i, file := range files { + // Calculate total bytes for progress bar + var totalBytes int64 + for _, file := range files { + totalBytes += file.Size + } + + _, _ = fmt.Fprintf(v.Stdout, "Restoring %d files (%s)...\n", + len(files), + humanize.Bytes(uint64(totalBytes)), + ) + + // Create progress bar if output is a terminal + var bar *progressbar.ProgressBar + if isTerminal() { + bar = progressbar.NewOptions64( + totalBytes, + 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 _, file := range files { if v.ctx.Err() != nil { return v.ctx.Err() } 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 + // Update progress bar even on failure + if bar != nil { + _ = bar.Add64(file.Size) + } continue } - // Progress logging - if (i+1)%100 == 0 || i+1 == len(files) { - log.Info("Restore progress", - "files", fmt.Sprintf("%d/%d", i+1, len(files)), - "bytes", humanize.Bytes(uint64(result.BytesRestored)), - ) + // Update progress bar + if bar != nil { + _ = bar.Add64(file.Size) } } + if bar != nil { + _ = bar.Finish() + } + result.Duration = time.Since(startTime) log.Info("Restore complete",