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