feat(daemon): add auto-scan, force start, and stall timeout default

- Auto-scan: daemon scans library daily (configurable via config.toml)
  [library] auto_scan = true, scan_interval = "24h"
- Force start: tasks with forceStart=true bypass concurrency semaphore
  (like Transmission's Force Start — opens temporary extra slot)
- Stall timeout default: 30m instead of unlimited, prevents dead torrents
  from permanently blocking download slots
- ForceStart field in agent.Task for CLI/server communication
This commit is contained in:
Deivid Soto 2026-03-29 20:22:15 +02:00
parent 386c97f84a
commit c476bd865c
5 changed files with 167 additions and 6 deletions

View file

@ -59,6 +59,17 @@ func (m *Manager) Submit(ctx context.Context, at agent.Task) {
m.reporter.Track(task)
// Force start: bypass semaphore (like Transmission's "Force Start")
if at.ForceStart {
log.Printf("[%s] force start: bypassing queue", task.ID[:8])
m.wg.Add(1)
go func() {
defer m.wg.Done()
m.processTask(ctx, task)
}()
return
}
// Acquire semaphore slot
select {
case m.sem <- struct{}{}:

View file

@ -82,8 +82,11 @@ type TorrentDownloader struct {
// NewTorrentDownloader creates a BitTorrent downloader with a long-lived client.
func NewTorrentDownloader(cfg TorrentConfig) (*TorrentDownloader, error) {
// 0 = unlimited for all timeouts (like qBittorrent)
// Users can set these in config.toml [downloads] section
// MetadataTimeout: 0 = unlimited (wait forever like qBittorrent)
// StallTimeout: default 30m (no bytes for 30 min = dead torrent, frees the slot)
if cfg.StallTimeout == 0 {
cfg.StallTimeout = 30 * time.Minute
}
if err := os.MkdirAll(cfg.DataDir, 0o755); err != nil {
return nil, fmt.Errorf("create data dir: %w", err)