Files
TransPyC/includes/shutil.py
2026-07-18 19:25:40 +08:00

284 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import platmacro
import memhub
import string
# Windows 后端依赖
import w32.win32base
import w32.win32file
# POSIX 后端依赖
import posix
# 文件拷贝缓冲区大小
COPY_BUF_SIZE: t.CDefine = 65536
# 模块级全局 MBuddy 指针(由用户在 main 中赋值初始化)
_mbuddy: memhub.MemBuddy | t.CPtr
# =============================================================================
# copyfile拷贝文件内容不保留权限/元数据)
# =============================================================================
def copyfile(src: str, dst: str) -> int:
"""拷贝文件内容。成功返回 0失败返回 -1。"""
if platmacro.IS_WINDOWS:
return _copyfile_win32(src, dst)
return _copyfile_posix(src, dst)
def _copyfile_win32(src: str, dst: str) -> int:
"""Windows使用 CopyFileA 内核实现(高效)"""
result: BOOL = w32.win32file.CopyFileA(src, dst, 0)
if result == 0:
return -1
return 0
def _copyfile_posix(src: str, dst: str) -> int:
"""POSIX手动 read/write 循环拷贝"""
fd_in: INT = posix.open(src, posix.O_RDONLY, 0)
if fd_in < 0:
return -1
fd_out: INT = posix.open(dst, posix.O_WRONLY | posix.O_CREAT | posix.O_TRUNC, 0o644)
if fd_out < 0:
posix.close(fd_in)
return -1
buf: bytes = _mbuddy.alloc(COPY_BUF_SIZE)
if buf == None:
posix.close(fd_in)
posix.close(fd_out)
return -1
n: ssize_t = 0
result: int = 0
n = posix.read(fd_in, buf, COPY_BUF_SIZE)
while n > 0:
written: ssize_t = posix.write(fd_out, buf, t.CSizeT(n))
if written < 0:
result = -1
break
n = posix.read(fd_in, buf, COPY_BUF_SIZE)
if n < 0:
result = -1
_mbuddy.free(buf)
posix.close(fd_in)
posix.close(fd_out)
return result
# =============================================================================
# copy拷贝文件并保留权限
# =============================================================================
def copy(src: str, dst: str) -> int:
"""拷贝文件并保留权限POSIX 上调用 stat+chmod。成功返回 0失败返回 -1。"""
if platmacro.IS_WINDOWS:
return _copy_win32(src, dst)
return _copy_posix(src, dst)
def _copy_win32(src: str, dst: str) -> int:
"""Windows拷贝文件CopyFileAWindows 权限模型简单,直接委托 copyfile"""
return _copyfile_win32(src, dst)
def _copy_posix(src: str, dst: str) -> int:
"""POSIX拷贝文件并保留权限stat+chmod"""
result: int = _copyfile_posix(src, dst)
if result != 0:
return -1
st: posix.Stat = posix.Stat()
if posix.stat(src, t.CPtr(c.Addr(st))) == 0:
posix.chmod(dst, st.st_mode)
return 0
# =============================================================================
# rmtree递归删除目录树
# =============================================================================
def rmtree(path: str) -> int:
"""递归删除目录树。成功返回 0失败返回 -1。"""
if platmacro.IS_WINDOWS:
return _rmtree_win32(path)
return _rmtree_posix(path)
def _rmtree_win32(path: str) -> int:
"""WindowsFindFirstFileA/FindNextFileA 递归删除"""
path_len: t.CSizeT = len(path)
# 构造搜索模式 path\*
pattern: bytes = _mbuddy.alloc(path_len + 3)
if pattern == None:
return -1
i: t.CSizeT = 0
for ch in path:
pattern[i] = ch
i += 1
if path_len > 0:
if path[path_len - 1] != '\\' and path[path_len - 1] != '/':
pattern[i] = '\\'
i += 1
pattern[i] = '*'
i += 1
pattern[i] = 0
find_data: w32.win32file.WIN32_FIND_DATAA = w32.win32file.WIN32_FIND_DATAA()
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, t.CPtr(c.Addr(find_data)))
_mbuddy.free(pattern)
if handle == w32.win32base.INVALID_HANDLE_VALUE:
return -1
result: int = 0
has_more: int = 1
while has_more == 1:
name: str = find_data.cFileName
# 跳过 . 和 ..
skip: bool = False
if name[0] == '.':
if name[1] == 0:
skip = True
elif name[1] == '.' and name[2] == 0:
skip = True
if not skip:
# 构造完整路径 path\name
name_len: t.CSizeT = len(name)
full_path: bytes = _mbuddy.alloc(path_len + name_len + 2)
if full_path == None:
result = -1
else:
j: t.CSizeT = 0
for ch in path:
full_path[j] = ch
j += 1
if path_len > 0:
if path[path_len - 1] != '\\' and path[path_len - 1] != '/':
full_path[j] = '\\'
j += 1
for ch in name:
full_path[j] = ch
j += 1
full_path[j] = 0
# 判断是文件还是目录
if (find_data.dwFileAttributes & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
# 递归删除子目录
sub_result: int = _rmtree_win32(str(full_path))
if sub_result != 0:
result = -1
else:
# 删除文件
if w32.win32file.DeleteFileA(str(full_path)) == 0:
result = -1
_mbuddy.free(full_path)
# 继续搜索下一个条目
if w32.win32file.FindNextFileA(handle, t.CPtr(c.Addr(find_data))) == 0:
has_more = 0
w32.win32file.FindClose(handle)
# 最后删除空目录本身
if result == 0:
if w32.win32file.RemoveDirectoryA(path) == 0:
result = -1
return result
def _rmtree_posix(path: str) -> int:
"""POSIXopendir/readdir 递归删除"""
dirp: posix.DIR = posix.opendir(path)
if dirp == None:
return -1
path_len: t.CSizeT = len(path)
result: int = 0
entry: posix.Dirent = posix.readdir(dirp)
while entry != None:
name: str = entry.d_name
# 跳过 . 和 ..
skip: bool = False
if name[0] == '.':
if name[1] == 0:
skip = True
elif name[1] == '.' and name[2] == 0:
skip = True
if not skip:
# 构造完整路径 path/name
name_len: t.CSizeT = len(name)
full_path: bytes = _mbuddy.alloc(path_len + name_len + 2)
if full_path == None:
result = -1
else:
j: t.CSizeT = 0
for ch in path:
full_path[j] = ch
j += 1
if path_len > 0:
if path[path_len - 1] != '/':
full_path[j] = '/'
j += 1
for ch in name:
full_path[j] = ch
j += 1
full_path[j] = 0
# 判断条目类型
if entry.d_type == posix.DT_DIR:
# 递归删除子目录
sub_result: int = _rmtree_posix(str(full_path))
if sub_result != 0:
result = -1
else:
# 删除文件/符号链接等
if posix.unlink(str(full_path)) != 0:
result = -1
_mbuddy.free(full_path)
entry = posix.readdir(dirp)
posix.closedir(dirp)
# 最后删除空目录本身
if result == 0:
if posix.rmdir(path) != 0:
result = -1
return result
# =============================================================================
# move移动文件/目录
# =============================================================================
def move(src: str, dst: str) -> int:
"""移动/重命名文件或目录。同分区时直接 rename跨分区时 copy+delete。
成功返回 0失败返回 -1。"""
if platmacro.IS_WINDOWS:
return _move_win32(src, dst)
return _move_posix(src, dst)
def _move_win32(src: str, dst: str) -> int:
"""WindowsMoveFileExA同分区 rename跨分区 copy+delete"""
rename_result: BOOL = w32.win32file.MoveFileExA(
src, dst,
w32.win32file.MOVEFILE_REPLACE_EXISTING | w32.win32file.MOVEFILE_COPY_ALLOWED
)
if rename_result != 0:
return 0
# rename 失败,尝试 copy + remove
if copyfile(src, dst) != 0:
return -1
if w32.win32file.DeleteFileA(src) == 0:
return -1
return 0
def _move_posix(src: str, dst: str) -> int:
"""POSIXrename同分区跨分区 copy+unlink"""
if posix.rename(src, dst) == 0:
return 0
# rename 失败(可能跨分区),尝试 copy + unlink
if copyfile(src, dst) != 0:
return -1
if posix.unlink(src) != 0:
return -1
return 0