soulsync/core/download_engine
Broque Thomas 4b4076b57f F: Engine owns hybrid fallback chain
`engine.search_with_fallback(query, source_chain, ...)` walks the
chain in order, skips unconfigured / unregistered plugins,
swallows per-source exceptions, and returns the first non-empty
(tracks, albums) tuple. Replaces orchestrator's hand-rolled
hybrid search loop.

`engine.download_with_fallback(username, filename, file_size,
source_chain)` falls through the chain when a source returns
None / raises. Username hint promotes a matching source-chain
entry to head of order. NOT yet wired into orchestrator.download
— today's username comes from a search result and represents
the user's explicit source pick, so silently falling through
would override their choice. Engine method is available for
future callers that want fallback semantics
(search_and_download_best, automation).

Orchestrator gains _resolve_source_chain helper that builds
the ordered list (hybrid_order config, falling back to legacy
primary/secondary pair). Orchestrator.search hands chain off
to engine.search_with_fallback for hybrid mode.

8 new tests pin the fallback semantics: chain ordering,
unconfigured-skip, exception-continue, empty-when-exhausted,
username-hint promotion. Suite still green (2050 passed).
2026-05-04 14:58:27 -07:00
..
__init__.py E1: Add RateLimitPolicy declaration mechanism 2026-05-04 14:38:20 -07:00
engine.py F: Engine owns hybrid fallback chain 2026-05-04 14:58:27 -07:00
rate_limit.py E1: Add RateLimitPolicy declaration mechanism 2026-05-04 14:38:20 -07:00
worker.py C1: Add BackgroundDownloadWorker to engine 2026-05-04 13:21:41 -07:00