Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions beetsplug/fetchart.py
Original file line number Diff line number Diff line change
Expand Up @@ -1525,7 +1525,11 @@ def assign_art(self, session: ImportSession, task: ImportTask):
candidate = self.art_candidates.pop(task)
removal_enabled = self._is_source_file_removal_enabled()

self._set_art(task.album, candidate, not removal_enabled)
try:
self._set_art(task.album, candidate, not removal_enabled)
except util.FilesystemError as exc:
self._log.warning("error setting art: {}", exc)
return # No art was set, so skip the prune step below.

if removal_enabled and not self._is_candidate_fallback(candidate):
task.prune(candidate.path)
Expand Down Expand Up @@ -1629,8 +1633,13 @@ def batch_fetch_art(

candidate = self.art_for_album(album, local_paths)
if candidate:
self._set_art(album, candidate)
message = colorize("text_success", "found album art")
try:
self._set_art(album, candidate)
except util.FilesystemError as exc:
self._log.warning("error setting art: {}", exc)
message = colorize("text_error", "error setting art")
else:
message = colorize("text_success", "found album art")
else:
message = colorize("text_error", "no art found")
ui.print_(f"{album}: {message}")
38 changes: 38 additions & 0 deletions test/plugins/test_fetchart.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import ctypes
import os
import sys
from unittest.mock import patch

from beets import util
from beets.test.helper import IOMixin, PluginTestCase
Expand Down Expand Up @@ -119,6 +120,43 @@ def test_colorization(self):
out = self.run_with_output("fetchart")
assert " - the älbum: \x1b[1;31mno art found\x1b[39;49;00m\n" == out

def test_batch_fetch_filesystem_error(self):
"""When _set_art raises FilesystemError (e.g. permissions), the
fetchart command should log a warning instead of crashing."""
self.touch(b"c\xc3\xb6ver.jpg", dir=self.album.path, content="IMAGE")

exc = util.FilesystemError(
reason=PermissionError("mocked permission error"),
verb="move",
paths=[b"/src", b"/dst"],
)
with patch(
"beetsplug.fetchart.FetchArtPlugin._set_art",
side_effect=exc,
):
out = self.run_with_output("fetchart")

assert "error setting art" in out

def test_assign_art_filesystem_error(self):
"""When _set_art raises FilesystemError during import, the import
should continue instead of crashing."""
exc = util.FilesystemError(
reason=PermissionError("mocked permission error"),
verb="move",
paths=[b"/src", b"/dst"],
)
fa = FetchArtPlugin()
# Simulate an import task with a queued candidate
task = type("FakeTask", (), {"album": self.album})()
fa.art_candidates[task] = type(
"FakeCandidate", (), {"path": b"/tmp/art.jpg", "source_name": "test"}
)()

with patch.object(fa, "_set_art", side_effect=exc):
# Should not raise
fa.assign_art(session=None, task=task)

def test_sources_is_a_string(self):
self.config["fetchart"].set({"sources": "filesystem"})
fa = FetchArtPlugin()
Expand Down
Loading