progress: barra di avanzamento per download ed estrazione

Aggiunge src/progress.{c,h}: barra a una riga attiva solo quando stdout e' un
terminale (con output rediretto/pipeline non stampa nulla, quindi l'output resta
identico al client Python: parita' 30/30 invariata).

- due fasi: "Scaricamento" (byte da Content-Length) ed "Estrazione" (byte
  compressi consumati + membri processati: nuovo contatore in gz_reader e
  callback tar_progress_fn/tar_extract_cb)
- controllo con CELLAR_PROGRESS=auto|bar|plain|off (default auto)
- nessun codice ANSI, solo '\r' e riempimento con spazi; glifi ASCII se la
  locale non e' UTF-8, larghezza da TIOCGWINSZ e misurata in colonne (i glifi
  UTF-8 sono multi-byte), ridisegno throttled, velocita' a media mobile, ETA
- percorsi d'errore: progress_abort() chiude la riga senza riepilogo; il file
  parziale resta come nel client Python
- tests/progress_test.sh: pipe silenziosa, PTY via script (barra, ETA, riepilogo,
  nessun ANSI, righe entro la larghezza), modalita' plain e off, integrita' del
  file scaricato e della bottiglia installata (13/13 verdi)
- tests/parity_test.sh resta 30/30; mock_server.py con --throttle per i test
This commit is contained in:
Matteo Benedetto
2026-09-20 17:46:29 +02:00
parent 6f5b224b2f
commit 5f82187c67
42 changed files with 764 additions and 14 deletions
+42 -3
View File
@@ -81,6 +81,37 @@ cellar-cli --server http://10.8.0.3:8080 list # override del server
Exit code: `0` ok, `1` errore HTTP/rete/file, `2` argomenti non validi (come argparse).
## Barra di avanzamento
Durante `install`, `wizard-install` e `download` viene mostrata una barra su una riga,
con due fasi (`Scaricamento` ed `Estrazione`):
```
Scaricamento [████████████░░░░░░░░░░░░] 58% 6.7 MB/11.5 MB 3.0 MB/s ETA 00:01
Estrazione [████████████████████████] 100% 11.5 MB/11.5 MB 178 MB/s membri: 20
```
- **attiva solo se stdout è un terminale**: con output rediretto, in pipeline, in cron o
nei log **non stampa nulla** — per questo l'output resta identico al client Python;
- controllo con la variabile d'ambiente **`CELLAR_PROGRESS`**:
- `auto` (default) → barra su terminale, silenzio altrove;
- `bar` → barra anche con output rediretto;
- `plain` → una riga ogni 10% (per log e cron, senza disegno);
- `off` → disattivata;
- **nessun codice ANSI**: solo `\r` e riempimento con spazi, quindi funziona anche su
console vecchie e seriali; glifi ASCII (`#`/`-`) se la locale non è UTF-8, blocchi
Unicode (`█`/`░`) se lo è;
- larghezza adattata al terminale (`TIOCGWINSZ`, fallback 80 colonne, max 200) e misurata
in **colonne**, non in byte (i glifi UTF-8 sono multi-byte);
- ridisegno limitato a ~8 volte al secondo, velocità con media mobile, ETA mostrata solo
quando la stima è affidabile;
- `CELLAR_PROGRESS_DEBUG=1` stampa su stderr modalità/larghezza calcolate (diagnostica).
```bash
CELLAR_PROGRESS=plain cellar-cli install 'Gioco' # output adatto a un log
CELLAR_PROGRESS=off cellar-cli install 'Gioco' # nessuna barra
```
## Retrocompatibilità (le scelte che contano)
- **Binario statico** (`-static`): nessuna dipendenza da glibc a runtime, quindi gira su
@@ -128,6 +159,7 @@ sei casi di errore di argomenti, più verifiche sugli artefatti:
| Confronto nomi case-insensitive | ASCII (`tolower`) invece di `str.lower()` Unicode |
| EOF su una richiesta interattiva | Python solleva `EOFError` (exit 1); qui si stampa `EOFError: EOF when reading a line` e si esce **1**. Nei prompt con default (nome archivio, descrizione…) su EOF si applica il default, così i wizard restano usabili da script |
| Upload multipart | In **streaming** (il client Python legge l'intero archivio in RAM) |
| Barra di avanzamento | Aggiunta (il CLI Python non stampa nulla durante download/estrazione). Attiva solo su terminale, quindi l'output rediretto resta identico all'originale |
| TLS | Non supportato (niente OpenSSL): serve `http://`, oppure un reverse proxy TLS. Il server Cellar è HTTP |
| Timeout di rete | Connect 15 s, I/O 300 s (urllib non ha timeout) |
| Redirect | Seguiti come `urllib.request`: GET/HEAD su 301/302/303/307/308, POST→GET su 301/302/303 |
@@ -135,11 +167,17 @@ sei casi di errore di argomenti, più verifiche sugli artefatti:
## Test e verifica
```bash
make test # parità (usa la build ASan)
C_BIN=dist/cellar-cli tests/parity_test.sh # parità con il binario statico
make verify # file/ldd/ABI/simboli GLIBC/syscall/smoke test
make test # parità + barra (build ASan)
C_BIN=dist/cellar-cli tests/parity_test.sh # parità con il binario statico
C_BIN=dist/cellar-cli tests/progress_test.sh # barra: pipe silenziosa, PTY, plain, off
make verify # file/ldd/ABI/simboli GLIBC/syscall/smoke test
```
`tests/progress_test.sh` usa un server mock con throttling (`--throttle` byte/s) e
`script -qec` per allocare un PTY: verifica che su pipe non ci sia alcun output di
progresso, che su terminale compaiano le due fasi con ETA e riepilogo, che nessuna riga
superi la larghezza del terminale e che non ci siano codici ANSI.
## Struttura
```
@@ -154,6 +192,7 @@ src/config.{c,h} ~/.cellar.conf (INI in stile configparser)
src/bottle.{c,h} record archivi, scansione bottiglie, bottle.yml, backup
src/ui.{c,h} tabelle e JSON con larghezze in code point (come le f-string)
src/ops.{c,h} list/upload/download/install
src/progress.{c,h} barra di avanzamento (download + estrazione, env CELLAR_PROGRESS)
src/wizard.{c,h} flussi interattivi
src/main.c CLI e messaggi di errore in stile argparse
third_party/ miniz (unlicense/MIT) + licenza