Skip to content

Commit b03b5c3

Browse files
committed
fetchart: handle FilesystemError when setting album art
When set_art encounters a permissions error or cross-device move failure (e.g. file locked by foobar2000 on Windows), the unhandled FilesystemError crashes the import or the fetchart CLI command. Wrap both _set_art call sites in try/except FilesystemError, matching the pattern already used in the cleanup method (line ~520). On failure, log a warning and continue instead of aborting. Fixes #6193.
1 parent fd586ef commit b03b5c3

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

beetsplug/fetchart.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,11 @@ def assign_art(self, session: ImportSession, task: ImportTask):
15251525
candidate = self.art_candidates.pop(task)
15261526
removal_enabled = self._is_source_file_removal_enabled()
15271527

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

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

16301634
candidate = self.art_for_album(album, local_paths)
16311635
if candidate:
1632-
self._set_art(album, candidate)
1633-
message = colorize("text_success", "found album art")
1636+
try:
1637+
self._set_art(album, candidate)
1638+
except util.FilesystemError as exc:
1639+
self._log.warning("error setting art: {}", exc)
1640+
message = colorize("text_error", "error setting art")
1641+
else:
1642+
message = colorize("text_success", "found album art")
16341643
else:
16351644
message = colorize("text_error", "no art found")
16361645
ui.print_(f"{album}: {message}")

test/plugins/test_fetchart.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import ctypes
1717
import os
1818
import sys
19+
from unittest.mock import patch
1920

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

123+
def test_batch_fetch_filesystem_error(self):
124+
"""When _set_art raises FilesystemError (e.g. permissions), the
125+
fetchart command should log a warning instead of crashing."""
126+
self.touch(b"c\xc3\xb6ver.jpg", dir=self.album.path, content="IMAGE")
127+
128+
exc = util.FilesystemError(
129+
reason=PermissionError("mocked permission error"),
130+
verb="move",
131+
paths=[b"/src", b"/dst"],
132+
)
133+
with patch(
134+
"beetsplug.fetchart.FetchArtPlugin._set_art",
135+
side_effect=exc,
136+
):
137+
out = self.run_with_output("fetchart")
138+
139+
assert "error setting art" in out
140+
141+
def test_assign_art_filesystem_error(self):
142+
"""When _set_art raises FilesystemError during import, the import
143+
should continue instead of crashing."""
144+
exc = util.FilesystemError(
145+
reason=PermissionError("mocked permission error"),
146+
verb="move",
147+
paths=[b"/src", b"/dst"],
148+
)
149+
fa = FetchArtPlugin()
150+
# Simulate an import task with a queued candidate
151+
task = type("FakeTask", (), {"album": self.album})()
152+
fa.art_candidates[task] = type(
153+
"FakeCandidate", (), {"path": b"/tmp/art.jpg", "source_name": "test"}
154+
)()
155+
156+
with patch.object(fa, "_set_art", side_effect=exc):
157+
# Should not raise
158+
fa.assign_art(session=None, task=task)
159+
122160
def test_sources_is_a_string(self):
123161
self.config["fetchart"].set({"sources": "filesystem"})
124162
fa = FetchArtPlugin()

0 commit comments

Comments
 (0)