feat(downloads): ordered preferred_methods list honored for web tasks

The agent ignored its config.toml method preference for web-driven downloads
(only the local `unarr download` command read it), and resolveMethod tried
torrent first in auto mode — so a 'debrid only' user still got torrent tasks.

- config: preferred_methods (ordered list, e.g. ["debrid","usenet"]) with
  MethodOrder() resolution; back-compat with the singular preferred_method.
  Methods absent from the list are disabled (debrid-only never torrents).
- resolveMethod/tryFallback honor the config order (gating, no fallback to a
  method outside the list) over the per-task preference.
- report preferred_methods on register so the web honors it (resolves debrid,
  gates the P2P stream fallback).
- enable the usenet downloader when usenet is listed (it was never enabled).
- config_menu: ordered presets (debrid-only, debrid→torrent, debrid→usenet…).

Tests: resolveMethod gating + fallback within/outside the list.
This commit is contained in:
Deivid Soto 2026-06-14 12:51:32 +02:00
parent 523ecc724a
commit c7ee0c0a28
8 changed files with 256 additions and 45 deletions

View file

@ -15,6 +15,11 @@ type ManagerConfig struct {
OutputDir string
Organize OrganizeConfig
Notifications bool // send desktop notifications on complete/fail
// PreferredMethods is the agent's ordered download-method preference from
// config.toml (e.g. ["debrid","usenet"]). Non-empty → it gates which methods
// resolveMethod will try, ignoring the per-task preference. Empty/nil → defer
// to the task's web-sent preference (legacy auto/torrent-first).
PreferredMethods []string
}
// Manager orchestrates concurrent downloads with method resolution and fallback.
@ -380,7 +385,7 @@ func (m *Manager) processTask(ctx context.Context, task *Task) {
return
}
method, err := resolveMethod(ctx, task, m.downloaders)
method, err := resolveMethod(ctx, task, m.downloaders, m.cfg.PreferredMethods)
if err != nil {
m.fail(ctx, task, "no method available: "+err.Error())
return
@ -416,7 +421,7 @@ func (m *Manager) processTask(ctx context.Context, task *Task) {
return
}
// Try fallback
if tryFallback(task, m.downloaders) {
if tryFallback(task, m.downloaders, m.cfg.PreferredMethods) {
log.Printf("[%s] %s failed, trying fallback: %v", agent.ShortID(task.ID), method, err)
if err := task.Transition(StatusResolving); err == nil {
m.processTaskRetry(ctx, task)
@ -432,7 +437,7 @@ func (m *Manager) processTask(ctx context.Context, task *Task) {
// processTaskRetry handles fallback after a method failure.
func (m *Manager) processTaskRetry(ctx context.Context, task *Task) {
method, err := resolveMethod(ctx, task, m.downloaders)
method, err := resolveMethod(ctx, task, m.downloaders, m.cfg.PreferredMethods)
if err != nil {
m.fail(ctx, task, "fallback failed: "+err.Error())
return