From 6e671af6a5f8aa78d1df2506f87c2d4c96fc6d10 Mon Sep 17 00:00:00 2001
From: Matt Joiner <anacrolix@gmail.com>
Date: Wed, 9 Apr 2025 15:03:06 +1000
Subject: [PATCH] cmd/torrent: Switch to a status output more suited for lots
 of torrents

---
 cmd/torrent/download.go | 63 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 60 insertions(+), 3 deletions(-)

diff --git a/cmd/torrent/download.go b/cmd/torrent/download.go
index 0a7f991f..bec6f489 100644
--- a/cmd/torrent/download.go
+++ b/cmd/torrent/download.go
@@ -25,6 +25,62 @@ import (
 	"github.com/anacrolix/torrent/storage"
 )
 
+func clientStatusWriter(ctx context.Context, cl *torrent.Client) {
+	start := time.Now()
+	lastStats := cl.Stats()
+	var lastLine string
+	var lastPrint time.Time
+	interval := 3 * time.Second
+	ticker := time.Tick(interval)
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-ticker:
+		}
+		stats := cl.Stats()
+		ts := cl.Torrents()
+		var completeBytes int64
+		var totalBytes int64
+		var infos int
+		for _, t := range ts {
+			if t.Info() != nil {
+				infos++
+				completeBytes += t.BytesCompleted()
+				totalBytes += t.Info().TotalLength()
+			}
+		}
+		getRate := func(a func(*torrent.ClientStats) *torrent.Count) int64 {
+			byteRate := int64(time.Second)
+			byteRate *= a(&stats).Int64() - a(&lastStats).Int64()
+			byteRate /= int64(interval)
+			return byteRate
+		}
+		uploadRate := getRate(func(s *torrent.ClientStats) *torrent.Count {
+			return &s.BytesWrittenData
+		})
+		downloadRate := getRate(func(s *torrent.ClientStats) *torrent.Count {
+			return &s.BytesReadUsefulData
+		})
+		line := fmt.Sprintf(
+			"%v torrents, %v infos, %s/%s ready, upload %s, download %s/s",
+			len(ts),
+			infos,
+			humanize.Bytes(uint64(completeBytes)),
+			humanize.Bytes(uint64(totalBytes)),
+			humanize.Bytes(uint64(uploadRate)),
+			humanize.Bytes(uint64(downloadRate)),
+		)
+		if line != lastLine || time.Since(lastPrint) >= time.Minute {
+			lastLine = line
+			lastPrint = time.Now()
+			fmt.Fprintf(os.Stdout, "%s: %s\n", time.Since(start), line)
+		}
+		lastStats = stats
+	}
+}
+
+// Keeping this for now for reference in case I do per-torrent deltas in client status updates.
 func torrentBar(t *torrent.Torrent, pieceStates bool) {
 	go func() {
 		start := time.Now()
@@ -144,9 +200,6 @@ func addTorrents(
 			err = fmt.Errorf("error writing chunk for %v: %w", t, err)
 			fatalErr(err)
 		})
-		if flags.Progress {
-			torrentBar(t, flags.PieceStates)
-		}
 		t.AddPeers(testPeers)
 		wg.Add(1)
 		go func() {
@@ -354,6 +407,10 @@ func downloadErr(ctx context.Context, flags downloadFlags) error {
 	}
 	defer client.Close()
 
+	if flags.Progress {
+		go clientStatusWriter(ctx, client)
+	}
+
 	// Write status on the root path on the default HTTP muxer. This will be bound to localhost
 	// somewhere if GOPPROF is set, thanks to the envpprof import.
 	http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
-- 
2.51.0