"""Upgrade guard: when an album is already in the library and no currently-available source can beat it (e.g. Qobuz is gated by pacing and only a same-quality Soulseek copy is left), the pipeline must NOT waste a download — keep-if-better would discard it anyway. It should skip and let the release be re-attempted later (when Qobuz frees up). A genuinely better source still upgrades.""" import os from lyra_worker.claim import claim_next from lyra_worker.pipeline import run_pipeline from lyra_worker.types import Candidate, DownloadResult, Quality _CD = Quality(fmt="FLAC", lossless=True, bit_depth=16, sample_rate=44100) # class 2 _HIRES = Quality(fmt="FLAC", lossless=True, bit_depth=24, sample_rate=96000) # class 3 def _cfg(conn, key, value): with conn.cursor() as cur: cur.execute( 'INSERT INTO "Config"(key,value,secret,"updatedAt") VALUES (%s,%s,false,now()) ' 'ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value, "updatedAt"=now()', (key, value), ) conn.commit() class _Counting: def __init__(self, name, tier, quality): self.name, self.tier, self._q = name, tier, quality self.calls = 0 self.searches = 0 def health(self): return True def search(self, target): self.searches += 1 return [Candidate(source=self.name, source_ref=self.name, matched_artist=target.artist, matched_album=target.album, quality=self._q, track_count=3, source_tier=self.tier)] def download(self, candidate, dest, on_progress): self.calls += 1 os.makedirs(dest, exist_ok=True) for i in range(3): with open(os.path.join(dest, f"0{i}.flac"), "wb") as fh: fh.write(b"a") return DownloadResult(ok=True, path=dest, track_count=3) def _seed_library(conn, artist, album, quality_class, path): with conn.cursor() as cur: cur.execute( 'INSERT INTO "LibraryItem" (id, artist, album, path, source, format, "qualityClass", "importedAt") ' "VALUES (gen_random_uuid()::text, %s, %s, %s, 'soulseek', 'FLAC', %s, now())", (artist, album, path, quality_class), ) conn.commit() def _insert_upgrade_job(conn, artist, album): with conn.cursor() as cur: cur.execute( 'INSERT INTO "MonitoredRelease" (id, "artistMbid", "artistName", "rgMbid", album, ' 'monitored, state, "secondaryTypes", "createdAt") ' "VALUES (gen_random_uuid()::text, 'm', %s, 'rg', %s, true, 'grabbed', '{}', now()) RETURNING id", (artist, album), ) rel_id = cur.fetchone()[0] cur.execute( 'INSERT INTO "Request" (id, artist, album, status, "monitoredReleaseId", "createdAt") ' "VALUES (gen_random_uuid()::text, %s, %s, 'pending', %s, now()) RETURNING id", (artist, album, rel_id), ) req = cur.fetchone()[0] cur.execute( 'INSERT INTO "Job" (id, "requestId", state, "currentStage", attempts, "createdAt", "updatedAt") ' "VALUES (gen_random_uuid()::text, %s, 'requested', 'intake', 0, now(), now()) RETURNING id", (req,), ) job = cur.fetchone()[0] conn.commit() return job def _lib(conn, artist, album): with conn.cursor() as cur: cur.execute('SELECT path, "qualityClass" FROM "LibraryItem" WHERE artist=%s AND album=%s', (artist, album)) return cur.fetchone() def _state(conn, job_id): with conn.cursor() as cur: cur.execute('SELECT state FROM "Job" WHERE id=%s', (job_id,)) return cur.fetchone()[0] def test_upgrade_skipped_pre_search_when_qobuz_gated(conn): # Qobuz gated (empty active window) + existing >= CD copy: only hi-res Qobuz could improve it, # so defer WITHOUT even searching — avoids re-searching every owned album each monitor cycle. _cfg(conn, "qobuz.pacing.activeStartHour", "5") _cfg(conn, "qobuz.pacing.activeEndHour", "5") # empty window → gate closed _seed_library(conn, "A", "B", 2, "/m/existing") job_id = _insert_upgrade_job(conn, "A", "B") claim_next(conn) soulseek = _Counting("soulseek", 1, _CD) run_pipeline(conn, job_id, [soulseek], dest_root="/tmp/lib-ug", upgrade_cutoff=3) assert soulseek.searches == 0 # no wasted search assert soulseek.calls == 0 # no wasted download assert _lib(conn, "A", "B") == ("/m/existing", 2) assert _state(conn, job_id) == "imported" def test_upgrade_skipped_post_search_when_only_same_quality_found(conn): # Gate OPEN, so it searches, but the only source found is same-quality → skip the download. _cfg(conn, "qobuz.pacing.activeStartHour", "0") _cfg(conn, "qobuz.pacing.activeEndHour", "24") # gate open _seed_library(conn, "A", "B", 2, "/m/existing") job_id = _insert_upgrade_job(conn, "A", "B") claim_next(conn) soulseek = _Counting("soulseek", 1, _CD) run_pipeline(conn, job_id, [soulseek], dest_root="/tmp/lib-ug", upgrade_cutoff=3) assert soulseek.searches == 1 # it did search (gate open) assert soulseek.calls == 0 # but skipped the useless download assert _lib(conn, "A", "B") == ("/m/existing", 2) def test_upgrade_proceeds_when_a_better_source_is_available(conn): # Existing class-2 copy; a class-3 (hi-res) source IS available → download + upgrade. _seed_library(conn, "A", "B", 2, "/m/existing") job_id = _insert_upgrade_job(conn, "A", "B") claim_next(conn) qobuz = _Counting("qobuz", 0, _HIRES) run_pipeline(conn, job_id, [qobuz], dest_root="/tmp/lib-ug2", upgrade_cutoff=3) assert qobuz.calls == 1 # downloaded the better copy assert _lib(conn, "A", "B")[1] == 3 # upgraded to hi-res