Add atomic writes, humanized sizes, debug logging, and -v/-q per-command

- Atomic writes for mfer gen: writes to temp file, renames on success,
  cleans up temp on error/interrupt. Prevents empty manifests on Ctrl-C.
- Humanized byte sizes using dustin/go-humanize (e.g., "10 MiB" not "10485760")
- Progress lines clear when done (using ANSI escape \r\033[K])
- Debug logging when files are added to manifest (mfer gen -vv)
- Move -v/-q flags from global to per-command for better UX
- Add tests for atomic write behavior with failing filesystem mock
This commit is contained in:
2025-12-17 15:57:20 -08:00
parent 444a4c8f45
commit c218fe56e9
12 changed files with 276 additions and 86 deletions

View File

@@ -13,6 +13,7 @@ import (
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/multiformats/go-multihash"
"github.com/urfave/cli/v2"
"sneak.berlin/go/mfer/internal/log"
@@ -89,12 +90,12 @@ func (mfa *CLIApp) fetchManifestOperation(ctx *cli.Context) error {
for p := range progress {
rate := formatBitrate(p.BytesPerSec * 8)
if p.ETA > 0 {
log.Infof("%s: %d/%d bytes, %s, ETA %s",
p.Path, p.BytesRead, p.TotalBytes,
log.Infof("%s: %s/%s, %s, ETA %s",
p.Path, humanize.IBytes(uint64(p.BytesRead)), humanize.IBytes(uint64(p.TotalBytes)),
rate, p.ETA.Round(time.Second))
} else {
log.Infof("%s: %d/%d bytes, %s",
p.Path, p.BytesRead, p.TotalBytes, rate)
log.Infof("%s: %s/%s, %s",
p.Path, humanize.IBytes(uint64(p.BytesRead)), humanize.IBytes(uint64(p.TotalBytes)), rate)
}
}
}()
@@ -129,9 +130,9 @@ func (mfa *CLIApp) fetchManifestOperation(ctx *cli.Context) error {
elapsed := time.Since(startTime)
avgBytesPerSec := float64(totalBytes) / elapsed.Seconds()
avgRate := formatBitrate(avgBytesPerSec * 8)
log.Infof("downloaded %d files (%.1f MB) in %.1fs (%s avg)",
log.Infof("downloaded %d files (%s) in %.1fs (%s avg)",
len(files),
float64(totalBytes)/1e6,
humanize.IBytes(uint64(totalBytes)),
elapsed.Seconds(),
avgRate)