Use delete on close semantics on windows for cleanup post copy

This commit is contained in:
Kovid Goyal 2023-06-06 13:24:21 +05:30
parent c2e9a78ddf
commit c3da25d112
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -21,8 +21,9 @@ WindowsFileId = Tuple[int, int, int]
class UnixFileCopier: class UnixFileCopier:
def __init__(self, delete_all=False): def __init__(self, delete_all=False, allow_move=False):
self.delete_all = delete_all self.delete_all = delete_all
self.allow_move = allow_move
self.copy_map: Dict[str, str] = {} self.copy_map: Dict[str, str] = {}
def register(self, path: str, dest: str) -> None: 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 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.delete_all = delete_all
self.allow_move = allow_move
self.path_to_fileid_map : Dict[str, WindowsFileId] = {} self.path_to_fileid_map : Dict[str, WindowsFileId] = {}
self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set)
self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} 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': 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 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: try:
return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, 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: except OSError as e:
if e.winerror == winutil.ERROR_SHARING_VIOLATION: if e.winerror == winutil.ERROR_SHARING_VIOLATION:
# The file could be a hardlink to an already opened file, # 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) self.folder_to_handle_map[path] = self._open_file(path, is_folder=True)
def __exit__(self, exc_type, exc_val, exc_tb) -> None: def __exit__(self, exc_type, exc_val, exc_tb) -> None:
try: for h in self.path_to_handle_map.values():
if self.delete_all and exc_val is None: h.close()
self.delete_all_source_files() for h in reversed(self.folder_to_handle_map.values()):
finally: h.close()
for h in self.path_to_handle_map.values():
h.close()
for h in self.folder_to_handle_map.values():
h.close()
def copy_all(self) -> None: def copy_all(self) -> None:
for src_path, dest_path in self.copy_map.items(): 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(): 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)) 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, allow_move=False) -> Union[UnixFileCopier, WindowsFileCopier]:
def get_copier(delete_all=False) -> Union[UnixFileCopier, WindowsFileCopier]: return (WindowsFileCopier if iswindows else UnixFileCopier)(delete_all, allow_move)
return WindowsFileCopier(delete_all) if iswindows else UnixFileCopier(delete_all)
def rename_files(src_to_dest_map: Dict[str, str]) -> None: 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. ' ' 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(): for s, d in src_to_dest_map.items():
copier.register(s, d) copier.register(s, d)
with copier: with copier: