Commit Graph

17 Commits

Author SHA1 Message Date
Jonathan 53c0dcc256 fix(worker): run streamrip on one persistent event loop (fixes Qobuz download failures)
Each search/download called asyncio.run, creating and closing a fresh event loop.
streamrip's module-global download-concurrency Semaphore binds to the loop on first
use, so the 2nd+ download reused a Semaphore bound to a closed loop -> "Semaphore is
bound to a different event loop", tracks failed, album arrived short -> "incomplete
download" / needs_attention. Run all streamrip coroutines on one persistent loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 23:04:25 +02:00
Jonathan 889e33355f feat(discovery): SimilaritySource protocol + FakeSimilaritySource
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:03:45 +02:00
Jonathan 4907d0d5a4 feat: library scan — probe seam + scan_library (have + monitored + followed)
Adds the AudioProbe seam (probe.py, _probe.py MutagenProbe with a lazy
mutagen import, FakeAudioProbe) and scan_library, which walks /music,
resolves each album against MusicBrainz (falling back to embedded tags
when the folder name doesn't resolve), and idempotently records matched
albums as have (LibraryItem), monitored (MonitoredRelease, fulfilled),
and followed (WatchedArtist, autoMonitorFuture=false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:00:33 +02:00
Jonathan 2ca0f7a385 feat: MbBrowser seam and FakeMbBrowser test double
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 12:12:17 +02:00
Jonathan c75b9d3562 fix: force IPv4 in worker, count real Qobuz files, flatten nested output
- worker had no IPv6 route -> Qobuz CDN (dual-stack) failed ~half the tracks
  with 'Network is unreachable'; disable IPv6 in the container (IPv4 only).
- StreamripClient counted metadata tracks (always full) -> a partial download
  falsely imported; now count the actual files written (short count -> needs_attention).
- streamrip writes into a nested 'Artist - Album [..]/' folder the tagger never saw;
  flatten audio files up into the album dir so tagging/organization runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 01:01:23 +02:00
Jonathan 00b3dddca6 feat: Qobuz auth-token (user_id + token) login as a reliable alternative to email/password
Qobuz's email/password API login returns 401 even with valid credentials
(a known Qobuz limitation, unaffected by streamrip version). Add settings
fields for a Qobuz user ID + auth token (token stored encrypted); when both
are present StreamripClient uses use_auth_token=True, else falls back to
email/password.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 00:46:17 +02:00
Jonathan caa0d542ea fix: MD5-hash Qobuz password and never log credential-bearing login errors
streamrip's email/password login expects the MD5 digest, not plaintext
(caused 'Invalid credentials'). Also wrap login() so streamrip's exception —
which embeds the email/password/app_id — can never reach the worker logs;
re-raise a clean credential-free RuntimeError instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 00:30:34 +02:00
Jonathan ec1fc568ba fix: scope slskd download polling to enqueued files and raise on timeout
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 22:52:03 +02:00
Jonathan 7b4b7ef1aa feat: add real slskd Soulseek client and opt-in live test 2026-07-10 22:45:36 +02:00
Jonathan b5931a7b66 feat: add SoulseekAdapter with injectable client seam 2026-07-10 22:40:53 +02:00
Jonathan 085193aa82 fix: run Qobuz login inside try/finally and reuse a single streamrip config
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 22:11:25 +02:00
Jonathan 707038da64 feat: add real streamrip Qobuz client and opt-in live test 2026-07-10 22:05:36 +02:00
Jonathan 0091c4f5e7 feat: add QobuzAdapter with injectable client seam 2026-07-10 21:59:57 +02:00
Jonathan a93c248aaf feat: add real yt-dlp client and opt-in live test; ffmpeg in worker image 2026-07-10 21:24:05 +02:00
Jonathan 5a3e64b057 feat: add YouTubeAdapter with injectable client seam 2026-07-10 21:19:27 +02:00
Jonathan d3bec2ee10 refactor: make adapter .tier the single source of truth for ranking
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 19:07:58 +02:00
Jonathan 991365e746 feat: add SourceAdapter contract and fake adapters 2026-07-10 18:35:52 +02:00