From 2f3ade8acbb51d3e7175c20c961f13e03b3929d7 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Mon, 8 Jun 2026 17:32:38 -0700 Subject: [PATCH] Cover Art Filler: scan falls back to the raw file path when mapping fails (Sokhi #fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of Sokhi's endless 0-findings: the SCAN resolved each track's path through the path-mapping layer with NO fallback, while the APPLY (_fix_missing_cover_art) uses `_resolve_file_path(...) or p` — i.e. it falls back to the raw DB path when mapping returns nothing. On his Docker setup the mapping returns None, so the scan set has_local=False and skipped every album (never looking at the folder), even though the apply WOULD have written the cover.jpg from the raw path. Fix: make the scan match the apply — if mapping returns nothing but the raw DB path is a real file (container path == stored path), use it as-is. Now the scan actually inspects the folder, sees the missing cover.jpg, and flags it; the apply then writes it from the embedded art. Tests: unresolved-path-but-real-file → flagged (sidecar_from_embedded); the fallback does NOT fire for a non-existent path (media-server-only stays skipped). Kept the [cover-diag] logging from the prior commit to confirm on Sokhi's run. --- core/repair_jobs/missing_cover_art.py | 8 ++++++ tests/test_missing_cover_art.py | 39 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index ddac13db..8e1e39e6 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -185,6 +185,14 @@ class MissingCoverArtJob(RepairJob): download_folder=download_folder, config_manager=context.config_manager, ) if rep_path else None + # Match the apply (_fix_missing_cover_art line ~1376: `... or p`): if + # the path-mapping layer returns nothing but the raw DB path is + # already a real file (the common Docker case where the container + # path == the stored path), use it as-is. Without this the scan never + # sees the folder and skips the album, while the apply WOULD have + # written the cover.jpg — the exact gap behind Sokhi's 0-findings. + if not resolved_rep and rep_path and os.path.isfile(rep_path): + resolved_rep = rep_path has_local = bool(resolved_rep) # Check embedded art and the cover.jpg sidecar SEPARATELY (not the # combined album_has_art_on_disk, which returns True if EITHER is diff --git a/tests/test_missing_cover_art.py b/tests/test_missing_cover_art.py index 57c841db..4cbcaf64 100644 --- a/tests/test_missing_cover_art.py +++ b/tests/test_missing_cover_art.py @@ -390,3 +390,42 @@ def test_media_server_only_album_empty_thumb_still_flagged(monkeypatch): result = mca.MissingCoverArtJob().scan(context) assert result.findings_created == 1 # media-server-only + empty thumb → flagged + + +def test_unresolved_path_falls_back_to_raw_when_file_exists(tmp_path, monkeypatch): + # Docker case (Sokhi): the path-mapping layer returns None, but the raw DB + # path is already a real file in the container. The scan must use it as-is — + # like the apply does (`_resolve_file_path(...) or p`) — so the album's + # folder is actually checked instead of the album being skipped. + track = tmp_path / 'Album' / '01.flac' + track.parent.mkdir() + track.write_bytes(b'') + conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None)) + _add_track(conn, str(track)) + context = _make_context(conn) + monkeypatch.setattr(mca, 'resolve_library_file_path', lambda raw, **k: None) # mapping fails + monkeypatch.setattr(mca, 'file_has_embedded_art', lambda p: True) # files have art + # real folder has no cover.jpg → should flag for the sidecar + monkeypatch.setattr(mca, 'get_primary_source', lambda: 'spotify') + monkeypatch.setattr(mca, 'get_client_for_source', lambda s: _FakeClient()) # API finds nothing + + result = mca.MissingCoverArtJob().scan(context) + + assert result.findings_created == 1 # raw path used → folder checked → flagged + assert context.findings[0]['details']['sidecar_from_embedded'] is True + + +def test_unresolved_path_with_no_real_file_still_skips(tmp_path, monkeypatch): + # Guard: the raw-path fallback must NOT fire for a path that isn't a real + # file (no false "local" on a genuinely media-server-only album). + conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None)) + _add_track(conn, '/does/not/exist/01.flac') + context = _make_context(conn) + monkeypatch.setattr(mca, 'resolve_library_file_path', lambda raw, **k: None) + called = [] + monkeypatch.setattr(mca, 'file_has_embedded_art', lambda p: called.append(p) or True) + + result = mca.MissingCoverArtJob().scan(context) + + assert result.findings_created == 0 # no real file, db has thumb → skipped + assert called == [] # never treated as local