Handle missing reflink dependency in the business logic

This commit is contained in:
Šarūnas Nejus 2024-09-04 20:34:00 +01:00
parent 255ac4bc2f
commit fee959c500
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
2 changed files with 21 additions and 20 deletions

View file

@ -13,6 +13,7 @@
# included in all copies or substantial portions of the Software. # included in all copies or substantial portions of the Software.
"""Miscellaneous utility functions.""" """Miscellaneous utility functions."""
from __future__ import annotations from __future__ import annotations
import errno import errno
@ -29,6 +30,7 @@ import traceback
from collections import Counter from collections import Counter
from contextlib import suppress from contextlib import suppress
from enum import Enum from enum import Enum
from importlib import import_module
from logging import Logger from logging import Logger
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
from pathlib import Path from pathlib import Path
@ -615,31 +617,33 @@ def reflink(
Raise an `OSError` if `dest` already exists, unless `replace` is Raise an `OSError` if `dest` already exists, unless `replace` is
True. If `path` == `dest`, then do nothing. True. If `path` == `dest`, then do nothing.
If reflinking fails and `fallback` is enabled, try copying the file If `fallback` is enabled, ignore errors and copy the file instead.
instead. Otherwise, raise an error without trying a plain copy. Otherwise, errors are re-raised as FilesystemError with an explanation.
May raise an `ImportError` if the `reflink` module is not available.
""" """
import reflink as pyreflink
if samefile(path, dest): if samefile(path, dest):
return return
if os.path.exists(syspath(dest)) and not replace: if os.path.exists(syspath(dest)) and not replace:
raise FilesystemError("file exists", "rename", (path, dest)) 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: try:
pyreflink.reflink(path, dest) import_module("reflink").reflink(path, dest)
except (NotImplementedError, pyreflink.ReflinkImpossibleError): except (ImportError, OSError):
if fallback: raise
copy(path, dest, replace) except Exception as exc:
else: msg = {
raise FilesystemError( "EXDEV": "Cannot reflink across devices",
"OS/filesystem does not support reflinks.", "EOPNOTSUPP": "Device does not support reflinks",
"link", }.get(str(exc), "OS does not support reflinks")
(path, dest),
traceback.format_exc(), raise FilesystemError(
) msg, "reflink", (path, dest), traceback.format_exc()
) from exc
def unique_path(path: bytes) -> bytes: def unique_path(path: bytes) -> bytes:

View file

@ -86,12 +86,10 @@ class MoveTest(BeetsTestCase):
self.i.move(operation=MoveOperation.COPY) self.i.move(operation=MoveOperation.COPY)
self.assertExists(self.path) self.assertExists(self.path)
@NEEDS_REFLINK
def test_reflink_arrives(self): def test_reflink_arrives(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO) self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.dest) self.assertExists(self.dest)
@NEEDS_REFLINK
def test_reflink_does_not_depart(self): def test_reflink_does_not_depart(self):
self.i.move(operation=MoveOperation.REFLINK_AUTO) self.i.move(operation=MoveOperation.REFLINK_AUTO)
self.assertExists(self.path) self.assertExists(self.path)
@ -584,7 +582,6 @@ class SafeMoveCopyTest(BeetsTestCase):
with pytest.raises(util.FilesystemError): with pytest.raises(util.FilesystemError):
util.copy(self.path, self.otherpath) util.copy(self.path, self.otherpath)
@NEEDS_REFLINK
def test_unsuccessful_reflink(self): def test_unsuccessful_reflink(self):
with pytest.raises(util.FilesystemError): with pytest.raises(util.FilesystemError):
util.reflink(self.path, self.otherpath) util.reflink(self.path, self.otherpath)