// Package download defines the core abstraction of the program: a Download is // a thing you can Run and ask for a Stat snapshot. Where a single-threaded // Command/event-poll reactor would express this, we use one // goroutine per Download blocking on real I/O. Following Rob Pike, the data // (Stat) and the interface are kept small; the algorithms fall out of them. package download import "context" // Status is the coarse lifecycle state of a download, using the // active/waiting/complete/error vocabulary. type Status int const ( Waiting Status = iota Active Seeding Complete Errored ) func (s Status) String() string { switch s { case Waiting: return "waiting" case Active: return "active" case Seeding: return "seeding" case Complete: return "complete" case Errored: return "error" default: return "unknown" } } // Stat is a snapshot of a download's progress. Every field is a plain value so // a renderer running in another goroutine can copy it without sharing memory. // Completed and Uploaded are cumulative byte counters; the renderer derives // speeds from successive snapshots rather than each download tracking its own. type Stat struct { Name string // ID is a process-unique stable identity for this download. Name can // collide (two pre-metadata magnets both show their source string), so the // renderer keys its per-download speed samples on ID, not Name. ID string IsBT bool // true for BitTorrent; progress always shows CN and shows SD for torrents Status Status Total int64 // total bytes, or -1 if not yet known Completed int64 // bytes downloaded so far Uploaded int64 // bytes uploaded (BitTorrent), 0 otherwise Conns int // active connections / peers Seeders int // connected seeders (BitTorrent only) } // A Download is one logical job: a URL, a torrent, a magnet. Run blocks in its // own goroutine until the work finishes, fails, or ctx is cancelled. Stat may // be called concurrently at any time and must not block; implementations back // it with atomics or a briefly-held lock. type Download interface { Name() string Run(ctx context.Context) error Stat() Stat } // Seeder is an optional capability of a Download whose work continues after its // content is fully downloaded — a torrent that keeps running to seed. // SeedingStarted is closed at that download->seed transition. The engine uses it // to free the concurrency slot the moment a download stops downloading: a // seed-only torrent is no longer a download, so it must not count against // --max-concurrent-downloads (-j) and block a queued download. HTTP downloads do // not implement this, so they hold their slot until they finish. type Seeder interface { SeedingStarted() <-chan struct{} }