all: cut bloated HTTP-client and tuning options

This is a personal torrent client, not a web client or daemon, so the
rarely-used HTTP-client knobs and redundant tuning flags are gone — flag,
config field, implementation, and tests removed for each. The option
surface drops from 54 to 28.

Web-client cluster removed: --all-proxy (HTTP_PROXY env still works),
load/save-cookies, --referer, -U/--user-agent, --http-user/--http-passwd,
--conditional-get, --remote-time, --check-certificate, --ca-certificate,
--lowest-speed-limit. Pass --header for a one-off auth/cookie/UA header.

Redundant knobs removed: -x and -k (folded into -s), --connect-timeout,
--retry-wait, per-item --max-download-limit/-u, --bt-max-peers,
--bt-stop-timeout, --disable-ipv6, --auto-save-interval, --human-readable,
--stop, -T (a positional .torrent works). follow-torrent and human-readable
are now unconditional defaults.
This commit is contained in:
2026-06-22 08:30:12 +09:00
parent 2afcb861e4
commit cf9082ff72
15 changed files with 157 additions and 1053 deletions

View File

@@ -28,15 +28,10 @@ import (
// ClientConfig holds the run-wide settings used to build the shared client.
type ClientConfig struct {
Dir string
ListenPortSpec string // raw --listen-port spec ("6881-6999,7000"); empty lets the OS choose.
DHT bool
MaxPeers int // max peer connections per torrent (0 = library default)
ListenPortSpec string // raw --listen-port spec ("6881-6999,7000"); empty lets the OS choose.
DHT bool // --enable-dht
OverallDown *rate.Limiter // global down limit (may be nil)
OverallUp *rate.Limiter // global up limit (may be nil)
DownLimit int64 // per-run bytes/s, 0 = unlimited (used only without a global limiter)
UpLimit int64
UserAgent string
DisableIPv6 bool // force IPv4-only (--disable-ipv6)
}
// ParsePorts expands a --listen-port spec into a flat list of candidate ports.
@@ -94,19 +89,9 @@ func quietSlogger(w io.Writer) *slog.Logger {
// NewClient builds the shared anacrolix client from the run-wide config. When a
// listen-port spec is given it tries each candidate port in turn, advancing past
// any that is already in use, and falls back to an OS-chosen port if none bind,
// so a single busy port does not disable BitTorrent entirely.
//
// Two per-torrent features have no equivalent in anacrolix/torrent v1.61.0,
// which exposes only client-wide knobs; both must therefore be configured run-wide
// here rather than attached to an individual Torrent:
//
// 1. No per-Torrent rate limiter. Throttles are read from the client-wide
// DownloadRateLimiter/UploadRateLimiter (no TorrentSpec rate field exists), so
// per-torrent --max-download-limit / --max-upload-limit cannot be
// honored individually; the effective cap is the shared limiter set below.
// 2. No per-Torrent DHT/PEX/LSD toggle for BEP27 private torrents. Those are
// client-wide only (NoDHT, DisablePEX, etc.), so a private torrent's leak
// prevention must come from a run-wide setting (e.g. --enable-dht=false).
// so a single busy port does not disable BitTorrent entirely. The rate limits
// and the DHT toggle are client-wide (anacrolix has no per-torrent equivalent),
// which is why they live here on the shared client rather than per download.
func NewClient(cfg ClientConfig) (*torrent.Client, error) {
c := torrent.NewDefaultClientConfig()
// Keep the library's own logging out of the readout. Webseed/peer warnings go
@@ -120,24 +105,11 @@ func NewClient(cfg ClientConfig) (*torrent.Client, error) {
}
c.Seed = true
c.NoDHT = !cfg.DHT
if cfg.MaxPeers > 0 {
c.EstablishedConnsPerTorrent = cfg.MaxPeers
}
if cfg.UserAgent != "" {
c.HTTPUserAgent = cfg.UserAgent
}
c.DisableIPv6 = cfg.DisableIPv6
switch {
case cfg.OverallDown != nil:
if cfg.OverallDown != nil {
c.DownloadRateLimiter = cfg.OverallDown
case cfg.DownLimit > 0:
c.DownloadRateLimiter = rate.NewLimiter(rate.Limit(cfg.DownLimit), download.LimiterBurst(cfg.DownLimit))
}
switch {
case cfg.OverallUp != nil:
if cfg.OverallUp != nil {
c.UploadRateLimiter = cfg.OverallUp
case cfg.UpLimit > 0:
c.UploadRateLimiter = rate.NewLimiter(rate.Limit(cfg.UpLimit), download.LimiterBurst(cfg.UpLimit))
}
// Candidate ports from the --listen-port spec.
@@ -183,7 +155,6 @@ type Options struct {
SeedTimeSet bool // was --seed-time given?
SeedTime time.Duration // how long to seed (0 with SeedTimeSet means no seeding)
SeedRatio float64 // stop seeding at this ratio; checked alongside SeedTime
StopTimeout time.Duration // abort if no download progress for this long (0 = off)
SelectFiles map[int]bool // 1-based file indexes to fetch; empty = all
CheckIntegrity bool // re-verify data against piece hashes before downloading
DryRun bool // fetch metadata only, then stop without downloading (--dry-run)
@@ -409,14 +380,11 @@ func (d *Download) awaitInfo(ctx context.Context, t *torrent.Torrent) error {
}
}
// wait blocks until the wanted data is complete, ctx is cancelled, or the
// download stalls past --bt-stop-timeout.
// wait blocks until the wanted data is complete or ctx is cancelled, polling
// completion a couple of times a second.
func (d *Download) wait(ctx context.Context, t *torrent.Torrent) error {
tick := time.NewTicker(500 * time.Millisecond)
defer tick.Stop()
last := t.BytesCompleted()
lastChange := time.Now()
for {
if d.complete(t) {
return nil
@@ -425,11 +393,6 @@ func (d *Download) wait(ctx context.Context, t *torrent.Torrent) error {
case <-ctx.Done():
return ctx.Err()
case <-tick.C:
if n := t.BytesCompleted(); n != last {
last, lastChange = n, time.Now()
} else if d.opts.StopTimeout > 0 && time.Since(lastChange) > d.opts.StopTimeout {
return fmt.Errorf("no progress for %s", d.opts.StopTimeout)
}
}
}
}