feat: add download progress bar and install spinner - #212
Merged
Merged
Conversation
Interactive `googet install`/`update` runs print "Installing X..." and then go silent for the whole download and install, which can take minutes for large MSI/EXE packages. Add a small `progress` package with a Cabbie-style download bar and an install spinner. Behavior: - When stderr is not a terminal (services, startup scripts, serial console logging via an agent, CI, pipes), output is byte-identical to before: nothing is rendered and installer output goes straight to stdout/stderr. - On an interactive terminal, downloads show a two-line bar (`Downloading X.goo (30 MiB)` / `|====---| 42% 12 MiB / 30 MiB [00:04]`) and installs show `Installing X... / [00:12]`, ending in `done` or `failed`. - Installer output is never hidden. While the spinner is active, child process output is passed through live, a complete line at a time, after clearing the spinner line. stdout stays stdout and stderr stays stderr, so redirected output (e.g. `googet install foo > install.log`) still holds everything the installer printed. An unterminated line is held (up to 4 KiB) so a spinner frame cannot split it, and is flushed when the spinner stops. - Output is ASCII-only, identical frames are not re-sent, and redraws are throttled (200 ms bar, 250 ms spinner) to keep serial console logs readable. - TERM=dumb and -verbose (INFO logs on stdout) disable rendering. Controls: - New global `-no_progress` flag. - New `noprogress: true` option in googet.conf for a machine-wide opt-out; an explicit `-no_progress=<bool>` overrides it. Also: - `packageHTTP` restarts from byte zero when a Range request is answered with 200 OK (a proxy or server that ignores ranges). Previously the full body was appended to the partial file and the checksum failed. The non-2xx error now includes the HTTP status instead of a nil error. - The file-conflict warning is routed through `progress.Printf` so it is not drawn over the spinner. Adds golang.org/x/term (v0.45.0, compatible with the existing x/sys v0.47.0) for terminal detection.
The merge of master into progress-ui resolved the go.mod conflict by taking master's file, which dropped the golang.org/x/term requirement that the progress package imports. Re-add it at v0.46.0, the version matching master's go 1.26.0 and golang.org/x/sys v0.48.0.
nguyen-phillip
approved these changes
Sep 28, 2026
Address review feedback that negative knobs are less ergonomic. - The -no_progress flag is now -progress (default true); -progress=false disables the download bar and install spinner. - The noprogress googet.conf option is now progress (default true). It is read into a *bool so an absent key keeps the default instead of turning progress off. - An explicit -progress, either value, still overrides the config file.
A spinner line wider than the terminal wraps, and a carriage return then
only returns to the start of the wrapped row, so every frame stacks on a
new line. Read the stderr width with term.GetSize and cut the title so
the widest spinner line ("... failed [mm:ss]") leaves the last column
free. An unknown width (not a console, or GetSize fails) leaves the
title unchanged.
The bar line is up to 71 columns, so on narrower terminals it wraps and each redraw leaves a stale row behind. Size the bar to the stderr width, reserving the widest possible text, and drop the bar cells entirely below 10 so only the percentage and byte counts are redrawn. An unknown width keeps the full 35-cell bar.
The comment said child output is captured and only shown on failure. It is actually passed through a line at a time as it is produced.
An unterminated child output line was held until its newline, 4 KiB, or the spinner stopping, so an installer that printed a partial line and then worked silently showed nothing. The spinner tick now writes out a held line once it has waited partialLineDelay (500ms), bounding the delay to under a second. The child's own stream stays byte-exact.
Move the googet.conf / -progress / -verbose resolution into wantProgress and table-test every combination, including an explicit -progress=true with -verbose.
A panic or future early return could leave the spinner goroutine redrawing. Stop it in a deferred call; a panic renders "failed".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interactive
googet install/updateruns print "Installing X..." and then go silent for the whole download and install, which can take minutes for large MSI/EXE packages. Add a smallprogresspackage with a Cabbie-style download bar and an install spinner.Behavior:
Downloading X.goo (30 MiB)/|====---| 42% 12 MiB / 30 MiB [00:04]) and installs showInstalling X... / [00:12], ending indoneorfailed.googet install foo > install.log) still holds everything the installer printed. An unterminated line is held (up to 4 KiB) so a spinner frame cannot split it, and is flushed when the spinner stops.Controls:
-no_progressflag.noprogress: trueoption in googet.conf for a machine-wide opt-out; an explicit-no_progress=<bool>overrides it.Also:
packageHTTPrestarts from byte zero when a Range request is answered with 200 OK (a proxy or server that ignores ranges). Previously the full body was appended to the partial file and the checksum failed. The non-2xx error now includes the HTTP status instead of a nil error.progress.Printfso it is not drawn over the spinner.Adds golang.org/x/term (v0.45.0, compatible with the existing x/sys v0.47.0) for terminal detection.