From 974d917df4e46d6026ff3878b76526867b1917c4 Mon Sep 17 00:00:00 2001 From: Danny Trunk Date: Mon, 12 Jan 2026 17:00:23 +0100 Subject: [PATCH] fix(fetchart): prevent deletion of configured fallback cover art When `import.delete` or `import.move` is enabled, the `assign_art` method calls `task.prune(candidate.path)` unconditionally. This incorrectly deletes the configured `fetchart.fallback` file. Add explicit check to skip pruning when the candidate path matches the configured fallback. --- beetsplug/fetchart.py | 12 +++++++++++- docs/changelog.rst | 1 + test/plugins/test_art.py | 10 ++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index e4de9181b..789182c33 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -1446,6 +1446,16 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): "move" ].get(bool) + def _is_candidate_fallback(self, candidate: Candidate) -> bool: + try: + return ( + candidate.path is not None + and self.fallback is not None + and os.path.samefile(candidate.path, self.fallback) + ) + except OSError: + return False + # Asynchronous; after music is added to the library. def fetch_art(self, session: ImportSession, task: ImportTask) -> None: """Find art for the album being imported.""" @@ -1494,7 +1504,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): self._set_art(task.album, candidate, not removal_enabled) - if removal_enabled: + if removal_enabled and not self._is_candidate_fallback(candidate): task.prune(candidate.path) # Manual album art fetching. diff --git a/docs/changelog.rst b/docs/changelog.rst index fe478c710..711749a9f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -43,6 +43,7 @@ Bug fixes ampersand. - :doc:`plugins/zero`: When the ``omit_single_disc`` option is set, ``disctotal`` is zeroed alongside ``disc``. +- :doc:`plugins/fetchart`: Prevent deletion of configured fallback cover art For plugin developers ~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/plugins/test_art.py b/test/plugins/test_art.py index 02d23d59b..9f5e6c216 100644 --- a/test/plugins/test_art.py +++ b/test/plugins/test_art.py @@ -310,6 +310,16 @@ class FSArtTest(UseThePlugin): ] assert candidates == paths + @patch("os.path.samefile") + def test_is_candidate_fallback_os_error(self, mock_samefile): + mock_samefile.side_effect = OSError("os error") + fallback = os.path.join(self.temp_dir, b"a.jpg") + self.plugin.fallback = fallback + candidate = fetchart.Candidate(logger, self.source.ID, fallback) + result = self.plugin._is_candidate_fallback(candidate) + mock_samefile.assert_called_once() + assert not result + class CombinedTest(FetchImageTestCase, CAAHelper): ASIN = "xxxx"