soulsync/core/downloads
Broque Thomas a2db5382bb fix(downloads): route torrent/usenet through streaming-result validation path
Live-test bug: Spotify-flow downloads with Torrent Only as the
active source produced 'download_failed' for every track. Searches
hit Prowlarr fine but no candidate ever got picked. Root cause was
in core/downloads/validation.py's get_valid_candidates:

- The streaming-source allowlist for the structured-metadata path
  didn't include 'torrent' / 'usenet', so torrent results fell into
  the Soulseek matching branch.
- Soulseek matching parses ``candidate.filename`` as a slskd-style
  ``Artist/Album/Track.flac`` path. Torrent / usenet filenames are
  encoded as ``<download_url>||<display_name>`` so the orchestrator
  can recover the URL — splitting that string on slashes produced
  garbage path segments that never matched the expected artist,
  every candidate failed the artist-folder gate, returned [], track
  status flipped to 'not_found'.

Fixes:
- _streaming_sources now includes 'torrent' and 'usenet'. They take
  the structured-metadata scoring path that reads r.title / r.artist
  directly (the projection layer pre-fills both correctly).
- Artist gate skipped for torrent/usenet, same as YouTube. Album-
  level releases legitimately don't expose per-track artist — the
  projection falls back to the indexer name as the 'artist' field,
  which would otherwise fail the gate against every Spotify artist.
- New album-name fallback scoring: for torrent/usenet only, the
  candidate title is ALSO scored against the wanted track's
  spotify_track.album field, and the max of (track-title score,
  album-title score) wins. This makes a candidate titled
  "GNX (2024) [FLAC]" match every track on the GNX album rather
  than scoring near zero against a specific track title like
  "Luther (with SZA)". match_type 'album_release' for visibility.

All 9 existing validation tests still pass.
2026-05-20 18:27:14 -07:00
..
__init__.py PR4a: lift sync history recording to core/downloads/history.py 2026-04-27 20:37:16 -07:00
cancel.py diag: log every cancel_download caller with a trigger label 2026-05-19 22:31:29 -07:00
candidates.py diag: log every cancel_download caller with a trigger label 2026-05-19 22:31:29 -07:00
cleanup.py PR4c: lift _automatic_wishlist_cleanup_after_db_update to core/downloads/cleanup.py 2026-04-27 22:23:41 -07:00
history.py PR4a: lift sync history recording to core/downloads/history.py 2026-04-27 20:37:16 -07:00
lifecycle.py fix(downloads): wait for post-processing result 2026-05-20 09:39:56 -07:00
master.py Honor manual library matches across source labels 2026-05-18 16:32:38 -07:00
monitor.py fix(downloads): wait for post-processing result 2026-05-20 09:39:56 -07:00
post_processing.py Cin-6: Rename soulseek_client global → download_orchestrator 2026-05-04 23:23:32 -07:00
staging.py Lift _try_staging_match to core/downloads/staging.py 2026-04-28 23:26:15 -07:00
status.py Harden quarantine approval flows 2026-05-18 08:37:11 -07:00
task_worker.py fix(amazon): wire amazon into all streaming-source guards 2026-05-16 10:24:13 -07:00
validation.py fix(downloads): route torrent/usenet through streaming-result validation path 2026-05-20 18:27:14 -07:00
wishlist_failed.py Surface silent exceptions across remaining modules — ~70 sites 2026-05-07 10:28:58 -07:00