feat(seeding): wire seed ratio/time lifecycle into the torrent daemon
SeedRatio/SeedTime were declared on TorrentConfig but never consumed, and SeedEnabled was hardcoded false in both constructors — the daemon never seeded, and if forced it seeded forever. - config: [downloads] seed_enabled/seed_ratio/seed_time (opt-in, off by default) - daemon: parse seed_time + wire all three; startup log per target shape - engine: seedTargetReached() (pure) + seedAndDrop() background monitor on a downloader-scoped seedCtx (not the task ctx, which dies when Download returns); drops the torrent on ratio (uploaded/size) OR time, whichever first; no target = seed until shutdown. Configurable check interval (tests lower it). - fix: cleanup() now always drops — previously leaked the handle on error paths when seeding was enabled. - refactor: dropTracked() helper shared by cleanup + post-seeding drop. Tests: TestSeedTargetReached (9 cases) + ctx/no-target branches + loopback swarm smoke (-tags smoke). Roadmap hueco closed.
This commit is contained in:
parent
665ec0a34f
commit
132c88b3f0
8 changed files with 459 additions and 34 deletions
|
|
@ -43,14 +43,20 @@ type DownloadConfig struct {
|
|||
PreferredMethod string `toml:"preferred_method"`
|
||||
PreferredQuality string `toml:"preferred_quality"` // "2160p", "1080p", "720p" — hint for auto-selection
|
||||
MaxConcurrent int `toml:"max_concurrent"`
|
||||
MinFreeDiskMB int `toml:"min_free_disk_mb"` // refuse a download if it would leave less than this free (reserve to keep the FS healthy); default 2048, 0 = disable
|
||||
MinFreeDiskMB int `toml:"min_free_disk_mb"` // refuse a download if it would leave less than this free (reserve to keep the FS healthy); default 2048, 0 = disable
|
||||
MaxDownloadSpeed string `toml:"max_download_speed"` // e.g. "10MB", "500KB", "0" = unlimited
|
||||
MaxUploadSpeed string `toml:"max_upload_speed"` // e.g. "1MB", "0" = unlimited
|
||||
MetadataTimeout string `toml:"metadata_timeout"` // e.g. "1h", "30m", "0" = unlimited (default: "0")
|
||||
StallTimeout string `toml:"stall_timeout"` // e.g. "30m", "1h", "0" = unlimited (default: "30m")
|
||||
ListenPort int `toml:"listen_port"` // fixed port for incoming peer connections (default: 42069, 0 = random)
|
||||
StreamPort int `toml:"stream_port"` // fixed port for streaming HTTP server (default: 11818)
|
||||
EnableUPnP bool `toml:"enable_upnp"` // map StreamPort to the WAN via UPnP/NAT-PMP (default: false; opt-in)
|
||||
// Seeding lifecycle (BitTorrent only). Off by default — the daemon leeches
|
||||
// then drops the torrent. Enable to keep uploading after a download finishes;
|
||||
// seeding stops at whichever target is hit first, or never if both are unset.
|
||||
SeedEnabled bool `toml:"seed_enabled"` // keep uploading after completion (default: false)
|
||||
SeedRatio float64 `toml:"seed_ratio"` // stop once uploaded/size reaches this ratio (0 = no ratio target)
|
||||
SeedTime string `toml:"seed_time"` // stop after this long since completion, e.g. "24h" (0/"" = no time target)
|
||||
MetadataTimeout string `toml:"metadata_timeout"` // e.g. "1h", "30m", "0" = unlimited (default: "0")
|
||||
StallTimeout string `toml:"stall_timeout"` // e.g. "30m", "1h", "0" = unlimited (default: "30m")
|
||||
ListenPort int `toml:"listen_port"` // fixed port for incoming peer connections (default: 42069, 0 = random)
|
||||
StreamPort int `toml:"stream_port"` // fixed port for streaming HTTP server (default: 11818)
|
||||
EnableUPnP bool `toml:"enable_upnp"` // map StreamPort to the WAN via UPnP/NAT-PMP (default: false; opt-in)
|
||||
// RequireStreamToken gates remote (non-loopback) /stream + /hls requests on a
|
||||
// signed, short-lived token embedded in the URLs the agent reports. Default
|
||||
// true (secure by default); loopback callers (local mpv/vlc) are always exempt.
|
||||
|
|
|
|||
|
|
@ -246,6 +246,55 @@ enabled = false
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadSeedingDefaultsOff(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "config.toml")
|
||||
|
||||
// No [downloads] seeding keys — seeding must stay off by default.
|
||||
os.WriteFile(path, []byte(`[auth]
|
||||
api_key = "tc_x"
|
||||
`), 0o644)
|
||||
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
if cfg.Download.SeedEnabled {
|
||||
t.Error("SeedEnabled should default to false")
|
||||
}
|
||||
if cfg.Download.SeedRatio != 0 {
|
||||
t.Errorf("SeedRatio = %v, want 0", cfg.Download.SeedRatio)
|
||||
}
|
||||
if cfg.Download.SeedTime != "" {
|
||||
t.Errorf("SeedTime = %q, want empty", cfg.Download.SeedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSeedingExplicit(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "config.toml")
|
||||
|
||||
os.WriteFile(path, []byte(`[downloads]
|
||||
seed_enabled = true
|
||||
seed_ratio = 2.0
|
||||
seed_time = "24h"
|
||||
`), 0o644)
|
||||
|
||||
cfg, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load failed: %v", err)
|
||||
}
|
||||
if !cfg.Download.SeedEnabled {
|
||||
t.Error("SeedEnabled = false, want true")
|
||||
}
|
||||
if cfg.Download.SeedRatio != 2.0 {
|
||||
t.Errorf("SeedRatio = %v, want 2.0", cfg.Download.SeedRatio)
|
||||
}
|
||||
if cfg.Download.SeedTime != "24h" {
|
||||
t.Errorf("SeedTime = %q, want 24h", cfg.Download.SeedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadInvalidTOML(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "config.toml")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue