diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index afe58b7..59528fe 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -115,6 +115,30 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { } defer func() { _ = blobCache.Close() }() + // 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(v.Stderr), + progressbar.OptionShowBytes(true), + progressbar.OptionShowCount(), + progressbar.OptionSetWidth(40), + progressbar.OptionThrottle(100*time.Millisecond), + progressbar.OptionOnCompletion(func() { + v.printfStderr("\n") + }), + progressbar.OptionSetRenderBlankState(true), + ) + } + for i, file := range files { if v.ctx.Err() != nil { return v.ctx.Err() @@ -124,11 +148,19 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { log.Error("Failed to restore file", "path", file.Path, "error", err) result.FilesFailed++ result.FailedFiles = append(result.FailedFiles, file.Path.String()) - // Continue with other files + // Update progress bar even on failure + if bar != nil { + _ = bar.Add64(file.Size) + } 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)), @@ -137,6 +169,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error { } } + if bar != nil { + _ = bar.Finish() + } + result.Duration = time.Since(startTime) log.Info("Restore complete",