Commit Graph

11 Commits

Author SHA1 Message Date
Jonathan 034ae40084 feat(worker): share the MusicBrainz cache from scans/discovery
The worker's MB browser (discography + artist search, used by monitor sweeps,
library scans, and Last.fm discovery) re-fetched every time. Route it through
the same ApiCache table + logical keys the web app uses, so a discography one
process fetched warms the other, and repeat lookups stop hitting MB.

- apicache.py cached_api(): read-through, serve-stale-on-outage, never caches
  null, best-effort (a cache-DB error degrades to a live fetch, never breaks
  scan/sweep/discovery). Its own DEDICATED autocommit connection so it can't
  commit the worker's in-flight transaction, and autocommit avoids freezing
  Postgres now() (which would break TTL math).
- CachingMbBrowser decorates MusicBrainzBrowser, serializing to the SAME
  camelCase JSON shape the web writes (round-trip tested). registry.build_browser
  wraps it when a DSN is present; the Last.fm source's browser rides it too.
- Hourly prune drops ApiCache rows > 90d so the table stays bounded.
- worker 213 tests / 7-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:17:51 +02:00
Jonathan 146bf32f23 feat(worker): Last.fm artist.getSimilar as a second discovery SimilaritySource
Adds LastfmSource, resolving Last.fm's often-empty artist mbid via the
injected MB browser (name->mbid cached) so every emitted SimilarArtist.mbid
is a real MusicBrainz artist MBID. Registered in build_similarity_sources,
gated on lastfm.api_key so the ListenBrainz-only path is unchanged when
unset.
2026-07-13 19:23:18 +02:00
Jonathan a15f4ae8bb feat(discovery): build_similarity_sources + worker sweep/trigger wiring
Adds registry.build_similarity_sources() (always constructs
ListenBrainzSource; reachability is a per-run health() concern, not a
startup gate) and wires discovery into the worker's main loop: a
scheduled sweep gated on discover.enabled + DISCOVER_TICK_SECONDS, and
a one-shot discover.requested trigger mirroring the existing
scan.requested pattern.

Also hardens run_discovery's health-check pass: a source whose
health() raises is now logged and skipped instead of aborting the
whole sweep (accepted finding from the Task-3 review).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:22:34 +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 133f6219ae feat: monitor sweep, real MB browser, and worker-loop wiring 2026-07-11 12:35:07 +02:00
Jonathan 1596bab23e feat: wire the mutagen tagger into the worker loop 2026-07-10 23:59:28 +02:00
Jonathan fef8cf0681 feat: wire MusicBrainz resolver into the worker loop 2026-07-10 23:27:28 +02:00
Jonathan 6b09047f64 feat: add Soulseek to the config-aware registry 2026-07-10 22:53:25 +02:00
Jonathan 71143dc15d feat: config-aware registry gating Qobuz on credentials; wire config into worker 2026-07-10 22:13:11 +02:00
Jonathan 4b2b2888ca feat: wire real YouTube adapter into the registry 2026-07-10 21:29:26 +02:00
Jonathan fbd6a056a4 feat: wire adapter registry into worker loop 2026-07-10 18:59:28 +02:00