diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 4f0aa283c..a83ad263b 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -13,6 +13,7 @@ # included in all copies or substantial portions of the Software. """Miscellaneous utility functions.""" + from __future__ import annotations import errno @@ -29,6 +30,7 @@ import traceback from collections import Counter from contextlib import suppress from enum import Enum +from importlib import import_module from logging import Logger from multiprocessing.pool import ThreadPool from pathlib import Path @@ -615,31 +617,33 @@ def reflink( Raise an `OSError` if `dest` already exists, unless `replace` is True. If `path` == `dest`, then do nothing. - If reflinking fails and `fallback` is enabled, try copying the file - instead. Otherwise, raise an error without trying a plain copy. - - May raise an `ImportError` if the `reflink` module is not available. + If `fallback` is enabled, ignore errors and copy the file instead. + Otherwise, errors are re-raised as FilesystemError with an explanation. """ - import reflink as pyreflink - if samefile(path, dest): return if os.path.exists(syspath(dest)) and not replace: raise FilesystemError("file exists", "rename", (path, dest)) + if fallback: + with suppress(Exception): + return import_module("reflink").reflink(path, dest) + return copy(path, dest, replace) + try: - pyreflink.reflink(path, dest) - except (NotImplementedError, pyreflink.ReflinkImpossibleError): - if fallback: - copy(path, dest, replace) - else: - raise FilesystemError( - "OS/filesystem does not support reflinks.", - "link", - (path, dest), - traceback.format_exc(), - ) + import_module("reflink").reflink(path, dest) + except (ImportError, OSError): + raise + except Exception as exc: + msg = { + "EXDEV": "Cannot reflink across devices", + "EOPNOTSUPP": "Device does not support reflinks", + }.get(str(exc), "OS does not support reflinks") + + raise FilesystemError( + msg, "reflink", (path, dest), traceback.format_exc() + ) from exc def unique_path(path: bytes) -> bytes: diff --git a/test/test_files.py b/test/test_files.py index 6490c5b55..204979755 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -86,12 +86,10 @@ class MoveTest(BeetsTestCase): self.i.move(operation=MoveOperation.COPY) self.assertExists(self.path) - @NEEDS_REFLINK def test_reflink_arrives(self): self.i.move(operation=MoveOperation.REFLINK_AUTO) self.assertExists(self.dest) - @NEEDS_REFLINK def test_reflink_does_not_depart(self): self.i.move(operation=MoveOperation.REFLINK_AUTO) self.assertExists(self.path) @@ -584,7 +582,6 @@ class SafeMoveCopyTest(BeetsTestCase): with pytest.raises(util.FilesystemError): util.copy(self.path, self.otherpath) - @NEEDS_REFLINK def test_unsuccessful_reflink(self): with pytest.raises(util.FilesystemError): util.reflink(self.path, self.otherpath)