diff --git a/app/pipeline/file_manager.py b/app/pipeline/file_manager.py index c483575..d74c01a 100644 --- a/app/pipeline/file_manager.py +++ b/app/pipeline/file_manager.py @@ -146,6 +146,18 @@ class SMBClientConnection: full_path = self._resolve_path(path) smbclient.remove(full_path) + def rename(self, path_from: str, path_to: str, replace: bool = False) -> None: + smbclient = _import_smbclient() + full_source = self._resolve_path(path_from) + full_target = self._resolve_path(path_to) + self.mkdir(get_parent_dir(path_to), parent=True) + if self.path_exists(path_to) and not replace: + raise FileExistsError(full_target) + try: + smbclient.rename(full_source, full_target) + except TypeError: + smbclient.rename(full_source, full_target, replace=replace) + def open(self, path: str, mode: str = "rb"): smbclient = _import_smbclient() full_path = self._resolve_path(path) @@ -401,6 +413,15 @@ def _next_available_samba_target(dir_path: str, file_name: str, samba_conn) -> s def _samba_move_file(path_from: str, path_to: str, samba_conn) -> None: + rename_error: Exception | None = None + if hasattr(samba_conn, "rename"): + try: + with samba_conn: + samba_conn.rename(path_from=path_from, path_to=path_to, replace=False) + return + except Exception as exc: # noqa: BLE001 + rename_error = exc + try: with samba_conn: samba_conn.copy(path_from=path_from, path_to=path_to, replace=False) @@ -408,7 +429,7 @@ def _samba_move_file(path_from: str, path_to: str, samba_conn) -> None: raise RuntimeError( "SMB move failed at copy stage: " f"source='{path_from}', target='{path_to}', " - f"{_format_smb_exception(exc)}" + f"{_format_smb_exception(exc)}{_format_rename_attempt(rename_error)}" ) from exc try: @@ -420,6 +441,7 @@ def _samba_move_file(path_from: str, path_to: str, samba_conn) -> None: "SMB move failed at delete stage after successful copy: " f"source='{path_from}', target='{path_to}', " f"target_exists={target_exists}, {_format_smb_exception(exc)}" + f"{_format_rename_attempt(rename_error)}" ) from exc @@ -440,6 +462,12 @@ def _format_smb_exception(exc: Exception) -> str: return ", ".join(details) +def _format_rename_attempt(rename_error: Exception | None) -> str: + if rename_error is None: + return "" + return f"; native_rename_attempt=failed({_format_smb_exception(rename_error)})" + + def _write_probe_file(remote_path: str, samba_conn) -> None: if hasattr(samba_conn, "open"): with samba_conn.open(remote_path, mode="wb") as remote_file: diff --git a/tests/unit/test_file_manager.py b/tests/unit/test_file_manager.py index 91797d8..238fb1c 100644 --- a/tests/unit/test_file_manager.py +++ b/tests/unit/test_file_manager.py @@ -58,6 +58,14 @@ class FakeSamba: if target.exists(): target.unlink() + def rename(self, path_from: str, path_to: str, replace: bool = False) -> None: + source = self._to_local(path_from) + target = self._to_local(path_to) + target.parent.mkdir(parents=True, exist_ok=True) + if target.exists() and not replace: + raise FileExistsError(path_to) + source.rename(target) + def open(self, path: str, mode: str = "rb"): target = self._to_local(path) target.parent.mkdir(parents=True, exist_ok=True)