fix(fetchart): prevent deletion of configured fallback cover art (#6283)

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.
This commit is contained in:
Šarūnas Nejus 2026-03-02 23:09:58 +00:00 committed by GitHub
commit 31bbd1fad8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 22 additions and 1 deletions

View file

@ -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.

View file

@ -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
~~~~~~~~~~~~~~~~~~~~~

View file

@ -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"