From 827738505191d12e9fe33949cbb70400fa396de2 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 8 Jun 2026 09:52:17 -0700 Subject: [PATCH] Cover Art Filler: resolve the rep path before the disk-art check (flags-every-album) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scan checked album_has_art_on_disk() on the RAW DB track path, while the apply (_fix_missing_cover_art) resolves the path first. On any path-mapped setup (docker mounts, a Plex/SoulSync path mismatch) the raw path isn't found, disk art reads as "missing", and EVERY album gets flagged — then the apply resolves the path, finds the art already there, and reports "already present". Scan and apply disagreed purely because only the apply resolved paths. Now the scan resolves the representative path the same way (resolve_library_ file_path, same transfer/download/config inputs the retag job uses) before checking disk art. Unresolvable → treated as no-local-file (not claimed disk-missing) so we never false-flag a file we simply can't reach. Tests: disk check runs on the resolved path (thumb+art → not flagged); unresolvable path → not flagged + art never checked on None. --- core/repair_jobs/missing_cover_art.py | 18 ++++++++++- tests/test_missing_cover_art.py | 45 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index eb3a8bf1..bed11419 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -4,6 +4,7 @@ import os import re from core.metadata.art_apply import album_has_art_on_disk +from core.library.path_resolver import resolve_library_file_path from core.metadata_service import get_client_for_source, get_primary_source, get_source_priority from core.repair_jobs import register_job from core.repair_jobs.base import JobContext, JobResult, RepairJob @@ -137,6 +138,8 @@ class MissingCoverArtJob(RepairJob): context.update_progress(0, total) logger.info("Found %d albums missing cover art", total) + download_folder = (context.config_manager.get('soulseek.download_path', '') + if context.config_manager else None) if context.report_progress: context.report_progress(phase=f'Searching artwork for {total} albums...', total=total) @@ -160,7 +163,20 @@ class MissingCoverArtJob(RepairJob): # Art can be missing in the DB (no thumb_url) and/or on disk (no # embedded art and no cover.jpg). Skip albums that already have both. db_missing = not (str(album_thumb).strip() if album_thumb else '') - disk_missing = bool(rep_path) and not album_has_art_on_disk(rep_path) + # Resolve the representative path the SAME way the apply does + # (_fix_missing_cover_art) before checking disk art. Checking the raw + # DB path would fail on any path-mapped setup (docker mounts, a + # Plex/SoulSync path mismatch) — the file isn't found, album art + # reads as "missing", and EVERY album gets flagged while the apply + # (which resolves) then finds the art already present. Unresolvable → + # treat as no-local-file (don't claim disk-missing). + resolved_rep = resolve_library_file_path( + rep_path, + transfer_folder=getattr(context, 'transfer_folder', None), + download_folder=download_folder, + config_manager=context.config_manager, + ) if rep_path else None + disk_missing = bool(resolved_rep) and not album_has_art_on_disk(resolved_rep) if not db_missing and not disk_missing: result.skipped += 1 continue diff --git a/tests/test_missing_cover_art.py b/tests/test_missing_cover_art.py index c32ba009..da952e45 100644 --- a/tests/test_missing_cover_art.py +++ b/tests/test_missing_cover_art.py @@ -281,3 +281,48 @@ def test_result_matches_unit(): # no artist on result → require exact title assert m({'title': 'Album'}, 'Album', 'Artist') assert not m({'title': 'Album Deluxe'}, 'Album', 'Artist') + + +# ── disk-art check must run on the RESOLVED path (flags-every-album bug) ── + +def _add_track(conn, path): + conn.execute( + "INSERT INTO tracks (id, album_id, file_path, disc_number, track_number) " + "VALUES (1, 1, ?, 1, 1)", (path,)) + conn.commit() + + +def test_scan_checks_disk_art_on_resolved_path(monkeypatch): + # Album already has a DB thumb (db not missing) and a track whose DB path + # only resolves via mapping. The disk-art check must run on the RESOLVED + # path — checking the raw path would fail on path-mapped setups and flag + # the whole library while the apply (which resolves) finds art present. + conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None)) + _add_track(conn, '/plex/raw/song.flac') + context = _make_context(conn) + checked = {} + monkeypatch.setattr(mca, 'resolve_library_file_path', + lambda raw, **k: '/resolved/song.flac' if raw == '/plex/raw/song.flac' else None) + monkeypatch.setattr(mca, 'album_has_art_on_disk', + lambda p: checked.update(path=p) or True) + + result = mca.MissingCoverArtJob().scan(context) + + assert checked.get('path') == '/resolved/song.flac' # resolved, not raw + assert result.findings_created == 0 # thumb + disk art → not flagged + + +def test_scan_unresolvable_path_not_flagged_disk_missing(monkeypatch): + # An unreachable file (resolve → None) must NOT be claimed as "missing disk + # art" — we can't know, so don't false-flag. (Album has a thumb already.) + conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None)) + _add_track(conn, '/gone/song.flac') + context = _make_context(conn) + monkeypatch.setattr(mca, 'resolve_library_file_path', lambda raw, **k: None) + called = [] + monkeypatch.setattr(mca, 'album_has_art_on_disk', lambda p: called.append(p) or False) + + result = mca.MissingCoverArtJob().scan(context) + + assert result.findings_created == 0 # thumb present, disk unknown → not flagged + assert called == [] # never checked art on a None path