Skip to content

Commit 3d85da8

Browse files
committed
Add cross-platform file move utility for extraction
Introduced _move_file_cross_platform to handle file moves across drives, especially on Windows where rename may fail. Updated archive extraction logic to use this utility, ensuring robust file operations during extraction and conflict resolution.
1 parent 4e87976 commit 3d85da8

File tree

1 file changed

+24
-6
lines changed

1 file changed

+24
-6
lines changed

src/tzst/core.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ def _get_unique_filename(file_path: Path) -> Path:
8787
counter += 1
8888

8989

90+
def _move_file_cross_platform(src: Path, dst: Path) -> None:
91+
"""Move a file from src to dst, handling cross-drive moves on Windows."""
92+
try:
93+
# Try the fast rename operation first
94+
src.rename(dst)
95+
except OSError as e:
96+
# On Windows, rename fails across drives with error 17
97+
# Fall back to copy + delete for cross-drive moves
98+
import shutil
99+
100+
try:
101+
shutil.copy2(src, dst)
102+
src.unlink()
103+
except Exception:
104+
# If copy also fails, re-raise the original rename error
105+
raise e from None
106+
107+
90108
def _handle_file_conflict(
91109
target_path: Path,
92110
resolution: ConflictResolution | str,
@@ -845,23 +863,23 @@ def extract_archive(
845863
):
846864
continue
847865
elif actual_resolution == ConflictResolution.EXIT:
848-
break
849-
850-
# For AUTO_RENAME, we need to adjust the member path
866+
break # For AUTO_RENAME, we need to adjust the member path
851867
if actual_resolution in (
852868
ConflictResolution.AUTO_RENAME,
853869
ConflictResolution.AUTO_RENAME_ALL,
854870
):
855871
# Create parent directories for renamed file
856-
final_path.parent.mkdir(parents=True, exist_ok=True)
872+
if final_path:
873+
final_path.parent.mkdir(parents=True, exist_ok=True)
857874
# Extract to temporary location, then move
858875
temp_extract_path = Path(tempfile.mkdtemp())
859876
try:
860877
archive.extract(
861878
member, temp_extract_path, filter=filter
862879
)
863880
temp_file = temp_extract_path / member
864-
temp_file.rename(final_path)
881+
if final_path:
882+
_move_file_cross_platform(temp_file, final_path)
865883
finally:
866884
# Clean up temp directory
867885
import shutil
@@ -920,7 +938,7 @@ def extract_archive(
920938
target_path.unlink() # Remove existing file
921939

922940
if target_path:
923-
temp_file.rename(target_path)
941+
_move_file_cross_platform(temp_file, target_path)
924942
finally:
925943
# Clean up temp directory
926944
import shutil

0 commit comments

Comments
 (0)