fix(torrent): expand tracker list, add DHT persistence and configurable timeouts

- Expand default trackers from 5 to 31 (synced with web tracker-list.ts)
- Add DHT node persistence between sessions (~/.local/share/unarr/dht-nodes.txt)
  Saves known nodes on shutdown, restores on startup for warm DHT bootstrap
- Make metadata_timeout and stall_timeout configurable in config.toml
  Default: 0 (unlimited, like qBittorrent) — users can set custom values
- Fix CleanTitle to handle web domains and format patterns (e.g. pctfenix.com)
This commit is contained in:
Deivid Soto 2026-03-29 19:09:51 +02:00
parent 20d4d34dfc
commit 386c97f84a
7 changed files with 265 additions and 39 deletions

View file

@ -178,13 +178,19 @@ func runDaemonStart() error {
maxDl, _ := config.ParseSpeed(cfg.Download.MaxDownloadSpeed)
maxUl, _ := config.ParseSpeed(cfg.Download.MaxUploadSpeed)
// Parse torrent timeouts from config (default: 0 = unlimited, like qBittorrent)
metaTimeout, _ := time.ParseDuration(cfg.Download.MetadataTimeout)
stallTimeout, _ := time.ParseDuration(cfg.Download.StallTimeout)
// Create torrent downloader
torrentDl, err := engine.NewTorrentDownloader(engine.TorrentConfig{
DataDir: cfg.Download.Dir,
StallTimeout: 90 * time.Second,
MaxTimeout: 30 * time.Minute,
MetadataTimeout: metaTimeout, // 0 = unlimited (default)
StallTimeout: stallTimeout, // 0 = unlimited (default)
MaxTimeout: 0, // unlimited
MaxDownloadRate: maxDl,
MaxUploadRate: maxUl,
ListenPort: cfg.Download.ListenPort, // 0 = default 42069
SeedEnabled: false,
})
if err != nil {
@ -418,6 +424,20 @@ func runDaemonStart() error {
// Start progress reporter in background
go reporter.Run(ctx)
// Periodic DHT node persistence (every 5 min) — protects against crash data loss
go func() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
torrentDl.SaveDhtNodes()
case <-ctx.Done():
return
}
}
}()
// Start daemon (blocks)
errCh := make(chan error, 1)
go func() {

View file

@ -85,10 +85,11 @@ func runDownload(input, method string) error {
// Create torrent downloader
torrentDl, err := engine.NewTorrentDownloader(engine.TorrentConfig{
DataDir: outputDir,
StallTimeout: 90 * time.Second,
MaxTimeout: 60 * time.Minute,
SeedEnabled: false,
DataDir: outputDir,
MetadataTimeout: 15 * time.Minute,
StallTimeout: 10 * time.Minute,
MaxTimeout: 0, // unlimited
SeedEnabled: false,
})
if err != nil {
return fmt.Errorf("create downloader: %w", err)