diff --git a/progress/progress.go b/progress/progress.go index f7d9d01..d00b4a0 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -114,13 +114,10 @@ func (r *Reporter) render(final bool) { r.redraw(r.block(stats), final) return } - // Not a TTY: no cursor control, so emit a single line sparingly (and always + // Not a TTY: no cursor control, so emit the summary line sparingly (and always // the final one) to keep redirected output and logs readable. var line string - switch { - case len(stats) == 1: - line = r.lineOne(stats[0]) - case len(stats) > 1: + if len(stats) > 0 { line = r.summary(stats) } if line != "" && (final || now.Sub(r.lastLog) >= logEvery) { @@ -129,52 +126,18 @@ func (r *Reporter) render(final bool) { } } -func (r *Reporter) lineOne(s download.Stat) string { - dl, ul := r.rates(s) - seeding := s.Status == download.Seeding - var b strings.Builder - fmt.Fprintf(&b, "[%s ", elide(s.Name, nameW)) - // While seeding, report the share ratio in place of the size block and drop - // the DL: field; otherwise show completed/total (or bare completed). - if seeding { - fmt.Fprintf(&b, "SEED(%.1f)", ratio(s.Uploaded, s.Completed)) - } else if s.Total > 0 { - fmt.Fprintf(&b, "%s/%s(%d%%)", humanSize(s.Completed, r.human), humanSize(s.Total, r.human), percent(s.Completed, s.Total)) - } else { - b.WriteString(humanSize(s.Completed, r.human)) - } - // CN is always shown (including HTTP); SD is shown for any torrent. - fmt.Fprintf(&b, " CN:%d", s.Conns) - if s.IsBT { - fmt.Fprintf(&b, " SD:%d", s.Seeders) - } - if !seeding { - fmt.Fprintf(&b, " DL:%s", humanSize(dl, r.human)) - } - if seeding || ul > 0 { - fmt.Fprintf(&b, " UL:%s", humanSize(ul, r.human)) - } - if !seeding && s.Total > 0 && dl > 0 { - fmt.Fprintf(&b, " ETA:%s", secfmt(etaDuration(s.Total-s.Completed, dl))) - } - b.WriteByte(']') - return b.String() -} - // nameW is the fixed rune width of the name column in the table, matching // elide's cap so a padded name lines the columns up. const nameW = 20 -// block builds the lines of the live display: the single-download dashboard for -// one download, a summary + column-header + one row each for several, or a -// one-line summary when the table would not fit the window. +// block builds the lines of the live display: a summary line, the column header, +// and one row per download — or just the one-line summary when the table would +// not fit the window. A single download renders the same way as several. func (r *Reporter) block(stats []download.Stat) []string { - switch { - case len(stats) == 0: + if len(stats) == 0 { return nil - case len(stats) == 1: - return []string{r.lineOne(stats[0])} - case len(stats)+3 > r.height: + } + if len(stats)+3 > r.height { // The block is repainted in place by moving the cursor up over it, which // breaks if printing the last row reaches the screen's bottom row and // scrolls the frame out from under the cursor. term height counts the row @@ -182,14 +145,13 @@ func (r *Reporter) block(stats []download.Stat) []string { // (len+2 lines) must still leave the bottom row free: fall back to the // single summary line once len+2 would fill to the bottom. return []string{r.summary(stats)} - default: - out := make([]string, 0, len(stats)+2) - out = append(out, r.summary(stats), tableHeader()) - for _, s := range stats { - out = append(out, r.tableRow(s)) - } - return out } + out := make([]string, 0, len(stats)+2) + out = append(out, r.summary(stats), tableHeader()) + for _, s := range stats { + out = append(out, r.tableRow(s)) + } + return out } // redraw repaints the block in place: move to the top of the previous frame, diff --git a/progress/progress_test.go b/progress/progress_test.go index 3f14cd2..e08d7e7 100644 --- a/progress/progress_test.go +++ b/progress/progress_test.go @@ -78,42 +78,6 @@ func TestRatesKeyedByID(t *testing.T) { } } -// TestLineOneSeeding: while seeding, render SEED(ratio), drop DL:, keep UL:. -func TestLineOneSeeding(t *testing.T) { - r := newReporter(true) - s := download.Stat{ - ID: "h", Name: "f", IsBT: true, Status: download.Seeding, - Total: 1000, Completed: 1000, Uploaded: 1500, Conns: 3, Seeders: 2, - } - line := r.lineOne(s) - if !strings.Contains(line, "SEED(1.5)") { - t.Errorf("missing SEED(1.5): %q", line) - } - if strings.Contains(line, "DL:") { - t.Errorf("DL: should be dropped while seeding: %q", line) - } - if !strings.Contains(line, "UL:") { - t.Errorf("UL: should be kept while seeding: %q", line) - } -} - -// TestLineOneCNSD: CN always shown; SD shown for torrents (even with 0 seeders), -// absent for HTTP. -func TestLineOneCNSD(t *testing.T) { - r := newReporter(true) - bt := r.lineOne(download.Stat{ID: "h", Name: "f", IsBT: true, Total: 10, Completed: 1, Conns: 4, Seeders: 0}) - if !strings.Contains(bt, "CN:4") || !strings.Contains(bt, "SD:0") { - t.Errorf("torrent line missing CN/SD: %q", bt) - } - http := r.lineOne(download.Stat{ID: "h2", Name: "f", IsBT: false, Total: 10, Completed: 1, Conns: 1}) - if !strings.Contains(http, "CN:1") { - t.Errorf("http line missing CN: %q", http) - } - if strings.Contains(http, "SD:") { - t.Errorf("http line should not show SD: %q", http) - } -} - func TestPctField(t *testing.T) { if got := pctField(download.Stat{Status: download.Active, Total: 100, Completed: 50}); got != " 50%" { t.Errorf("active 50%% = %q, want ' 50%%'", got) @@ -183,6 +147,10 @@ func TestBlockTableAndFallback(t *testing.T) { if got := r.block(stats); len(got) != 4 { // summary + header + 2 rows t.Errorf("table block = %d lines, want 4:\n%v", len(got), got) } + // A single download renders the same way as several: summary + header + 1 row. + if got := r.block(stats[:1]); len(got) != 3 { + t.Errorf("single-download block = %d lines, want 3 (summary+header+row):\n%v", len(got), got) + } r.height = 5 // 4 lines fit with the bottom row left free if got := r.block(stats); len(got) != 4 { t.Errorf("just-fits block = %d lines, want 4:\n%v", len(got), got)