284 lines
8.9 KiB
Python
284 lines
8.9 KiB
Python
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:拷贝文件(CopyFileA,Windows 权限模型简单,直接委托 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:
|
||
"""Windows:FindFirstFileA/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:
|
||
"""POSIX:opendir/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:
|
||
"""Windows:MoveFileExA(同分区 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:
|
||
"""POSIX:rename(同分区),跨分区 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
|