Commit Graph

16 Commits

Author SHA1 Message Date
Jonathan 082365005c fix(worker): reconcile every job + backfill owned releases
Wanted releases pressed while the monitor is off never got reconciled, so
they lingered on the wanted list. Extract finish_job() and call it for every
finished job (not just when monitor.enabled) so the MonitoredRelease
transitions to fulfilled. Add fulfill_owned_releases(), run once at startup,
to backfill releases already owned at/above the quality cutoff (self-heals
items stuck from before this fix). Both idempotent + tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:08:24 +02:00
Jonathan 8bbc1db32f feat(worker): chunk the library scan + reconnect on DB loss
Two worker-loop robustness fixes:

- Scan chunking: maybe_run_scan advances the library scan by at most one
  scan_chunk (default scan.chunkSize=25 albums) per loop iteration, so a
  large library no longer blocks job-claim/monitor for minutes-to-hours.
  Progress persists in scan.inProgress/scan.cursor/scan.progress and is
  resumable; scan.result (unchanged format) is written on completion.

- DB reconnect: wrap the loop body in `except psycopg.OperationalError`
  (AdminShutdown subclasses it) to close the dead handle and reconnect via
  wait_for_db() in-process, instead of crashing and relying on the
  container restart policy. Verified via a backend-termination test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:37:06 +02:00
Jonathan 9ba587d136 fix(worker): run discovery at most once per loop iteration
The scheduled-tick branch and the discover.requested one-shot branch
both read the same stale per-iteration config snapshot, so an enabled+due
sweep coinciding with a pending request ran discovery twice back-to-back
(idempotent but wasteful). Extract maybe_run_discovery, which unifies both
triggers into a single run and is now unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:42:48 +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 aafd8b15d5 fix: scan populates each followed artist's full discography (unmonitored)
A followed artist previously showed only the one owned album ("of 1")
because scan_library recorded just the resolved release. Now scan
browses each artist's full MusicBrainz release-group list once per
run and stores it as unmonitored MonitoredRelease rows, matching
web-follow behavior. The owned album keeps its monitored/fulfilled
state via ON CONFLICT DO NOTHING.
2026-07-11 17:49:26 +02:00
Jonathan c31c8b5249 feat: worker runs the library scan when Config scan.requested is set 2026-07-11 17:08:51 +02:00
Jonathan 88ef4149d9 fix: atomic import swap, disk/DB replace consistency, guarded pipeline loop
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:27:48 +02:00
Jonathan fe973343ad feat: sweep orphaned staging dirs on worker startup 2026-07-11 15:18:30 +02:00
Jonathan 4481f1d998 fix: make intake dedupe cutoff-aware so monitor upgrade path works end-to-end
The monitor's quality-upgrade path was inert: run_pipeline's intake
short-circuit treated any existing LibraryItem as "done", so a
monitor-driven upgrade job for a below-cutoff copy jumped straight to
imported without downloading. Intake now checks whether the job is
monitor-driven (Request.monitoredReleaseId IS NOT NULL) and, if so,
only short-circuits when the existing copy already meets the quality
cutoff; plain manual requests keep exact slice-1 behavior. Also drops
an unused `field` import in browser.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 12:52:03 +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 71143dc15d feat: config-aware registry gating Qobuz on credentials; wire config into worker 2026-07-10 22:13:11 +02:00
Jonathan fbd6a056a4 feat: wire adapter registry into worker loop 2026-07-10 18:59:28 +02:00
Jonathan 7532e20d1e fix: worker waits for schema before claiming; npm ci for reproducible builds
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:32:20 +02:00
Jonathan 3e44de093e feat: add worker loop and production Dockerfiles 2026-07-10 17:16:24 +02:00