diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 33d6a997ef..d68d1a1f55 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -21,8 +21,9 @@ WindowsFileId = Tuple[int, int, int] class UnixFileCopier: - def __init__(self, delete_all=False): + def __init__(self, delete_all=False, allow_move=False): self.delete_all = delete_all + self.allow_move = allow_move self.copy_map: Dict[str, str] = {} def register(self, path: str, dest: str) -> None: @@ -62,8 +63,9 @@ class WindowsFileCopier: Locks all files before starting the copy, ensuring other processes cannot interfere ''' - def __init__(self, delete_all=False): + def __init__(self, delete_all=False, allow_move=False): self.delete_all = delete_all + self.allow_move = allow_move self.path_to_fileid_map : Dict[str, WindowsFileId] = {} self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} @@ -87,9 +89,11 @@ class WindowsFileCopier: def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> 'winutil.Handle': flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN + if self.delete_all: + flags |= winutil.FILE_FLAG_DELETE_ON_CLOSE try: return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, - winutil.FILE_SHARE_DELETE, winutil.OPEN_EXISTING, flags) + winutil.FILE_SHARE_DELETE if self.allow_move else 0, winutil.OPEN_EXISTING, flags) except OSError as e: if e.winerror == winutil.ERROR_SHARING_VIOLATION: # The file could be a hardlink to an already opened file, @@ -116,14 +120,10 @@ class WindowsFileCopier: self.folder_to_handle_map[path] = self._open_file(path, is_folder=True) def __exit__(self, exc_type, exc_val, exc_tb) -> None: - try: - if self.delete_all and exc_val is None: - self.delete_all_source_files() - finally: - for h in self.path_to_handle_map.values(): - h.close() - for h in self.folder_to_handle_map.values(): - h.close() + for h in self.path_to_handle_map.values(): + h.close() + for h in reversed(self.folder_to_handle_map.values()): + h.close() def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): @@ -146,20 +146,14 @@ class WindowsFileCopier: for src_path, dest_path in self.copy_map.items(): winutil.move_file(make_long_path_useable(src_path), make_long_path_useable(dest_path)) - def delete_all_source_files(self) -> None: - for src_path in self.copy_map: - winutil.delete_file(make_long_path_useable(src_path)) - for path in reversed(self.folders): - os.rmdir(make_long_path_useable(path)) - -def get_copier(delete_all=False) -> Union[UnixFileCopier, WindowsFileCopier]: - return WindowsFileCopier(delete_all) if iswindows else UnixFileCopier(delete_all) +def get_copier(delete_all=False, allow_move=False) -> Union[UnixFileCopier, WindowsFileCopier]: + return (WindowsFileCopier if iswindows else UnixFileCopier)(delete_all, allow_move) def rename_files(src_to_dest_map: Dict[str, str]) -> None: ' Rename a bunch of files. On Windows all files are locked before renaming so no other process can interfere. ' - copier = get_copier() + copier = get_copier(allow_move=True) for s, d in src_to_dest_map.items(): copier.register(s, d) with copier: