Files

242 lines
7.9 KiB
Plaintext

from stdint import *
import win32.win32base
import win32.win32file
import memhub
import stdlib
# Maximum Windows path length
define MAX_PATH_LEN = 260
# INVALID_FILE_ATTRIBUTES
define INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
# Module-level global MBuddy pointer (initialized by the user in main)
_mbuddy: memhub.MemBuddy*
# =============================================================================
# File/Directory Attribute Query
# =============================================================================
def _get_attrs(path: str) -> ULONG:
"""Get file attributes. Return INVALID_FILE_ATTRIBUTES on failure."""
return win32.win32file.GetFileAttributesA(path)
def exists(path: str) -> bool:
"""Check whether the file/directory exists"""
attrs: ULONG = _get_attrs(path)
if attrs == INVALID_FILE_ATTRIBUTES:
return False
return True
def isdir(path: str) -> bool:
"""Check whether the path is a directory"""
attrs: ULONG = _get_attrs(path)
if attrs == INVALID_FILE_ATTRIBUTES:
return False
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
return True
return False
def isfile(path: str) -> bool:
"""Check whether the path is a regular file"""
attrs: ULONG = _get_attrs(path)
if attrs == INVALID_FILE_ATTRIBUTES:
return False
if (attrs & win32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0:
return False
return True
def getsize(path: str) -> int64:
"""Return the file size in bytes. Return -1 on failure."""
handle: win32.win32base.HANDLE = win32.win32file.CreateFileA(
path, win32.win32file.GENERIC_READ, win32.win32file.FILE_SHARE_READ,
None, win32.win32file.OPEN_EXISTING, win32.win32file.FILE_ATTRIBUTE_NORMAL, None
)
if handle == win32.win32base.INVALID_HANDLE_VALUE:
return -1
li: win32.win32base.LARGE_INTEGER = win32.win32base.LARGE_INTEGER()
result: BOOL = win32.win32file.GetFileSizeEx(handle, &li)
win32.win32base.CloseHandle(handle)
if result == 0:
return -1
return li.QuadPart
# =============================================================================
# Working Directory
# =============================================================================
def getcwd() -> str:
"""Return the current working directory (memory allocated via _mbuddy)"""
buf: bytes = _mbuddy.alloc(MAX_PATH_LEN + 1)
if buf == None: return None
length: ULONG = win32.win32file.GetCurrentDirectoryA(MAX_PATH_LEN + 1, buf)
if length == 0:
return None
if length > MAX_PATH_LEN:
# Buffer too small; reallocate
buf2: bytes = _mbuddy.alloc(length + 1)
if buf2 == None: return None
win32.win32file.GetCurrentDirectoryA(length + 1, buf2)
buf2[length] = 0
return str(buf2)
buf[length] = 0
return str(buf)
def chdir(path: str) -> int:
"""Switch the current working directory. Return 0 on success, -1 on failure."""
result: BOOL = win32.win32file.SetCurrentDirectoryA(path)
if result == 0:
return -1
return 0
# =============================================================================
# File/Directory Operations
# =============================================================================
def mkdir(path: str) -> int:
"""Create a directory. Return 0 on success, -1 on failure.
If path is valid UTF-8, convert to UTF-16 and use CreateDirectoryW; otherwise fall back to the A-series."""
if win32.win32base.utf8_is_valid(path) == 0:
result: BOOL = win32.win32file.CreateDirectoryA(path, None)
if result == 0:
return -1
return 0
wp: win32.win32base.WCHARPTR = win32.win32base.utf8_to_utf16_dup(path)
if wp is None:
result_a: BOOL = win32.win32file.CreateDirectoryA(path, None)
if result_a == 0:
return -1
return 0
result_w: BOOL = win32.win32file.CreateDirectoryW(t.CPtr(wp), None)
stdlib.free(wp)
if result_w == 0:
return -1
return 0
def remove(path: str) -> int:
"""Delete a file. Return 0 on success, -1 on failure."""
result: BOOL = win32.win32file.DeleteFileA(path)
if result == 0:
return -1
return 0
def rmdir(path: str) -> int:
"""Delete an empty directory. Return 0 on success, -1 on failure."""
result: BOOL = win32.win32file.RemoveDirectoryA(path)
if result == 0:
return -1
return 0
def rename(old_path: str, new_path: str) -> int:
"""Rename/move a file or directory. Return 0 on success, -1 on failure."""
result: BOOL = win32.win32file.MoveFileExA(
old_path, new_path,
win32.win32file.MOVEFILE_REPLACE_EXISTING | win32.win32file.MOVEFILE_COPY_ALLOWED
)
if result == 0:
return -1
return 0
def chmod(path: str, mode: int) -> int:
"""Modify file attributes (simplified Windows: read-only bit only).
S_IWUSR (0o200) in mode: 0 sets read-only, 1 sets writable. Return 0 on success, -1 on failure."""
if (mode & 0o200) != 0:
# Writable: clear the read-only attribute
result: BOOL = win32.win32file.SetFileAttributesA(
path, win32.win32file.FILE_ATTRIBUTE_NORMAL
)
else:
# Read-only: set the read-only attribute
result: BOOL = win32.win32file.SetFileAttributesA(
path, win32.win32file.FILE_ATTRIBUTE_READONLY
)
if result == 0:
return -1
return 0
# =============================================================================
# Directory Listing (FindFirstFile / FindNextFile)
# =============================================================================
def listdir(path: str, out_names: str*, max_count: ULONG) -> ULONG:
"""List file/subdirectory names under a directory.
out_names is a caller-provided char** array (each entry points to a filename allocated via _mbuddy); max_count is its capacity. Return the count, or 0 on failure."""
# Build search pattern: path\*
path_len: sizet = len(path)
pattern: bytes = _mbuddy.alloc(path_len + 3)
if pattern == None: return 0
i: sizet = 0
for ch in path:
pattern[i] = ch
i += 1
# Append \* suffix
if path[path_len - 1] != '\\' and path[path_len - 1] != '/':
pattern[i] = '\\'
i += 1
pattern[i] = '*'
i += 1
pattern[i] = 0
find_data: win32.win32file.WIN32_FIND_DATAA = win32.win32file.WIN32_FIND_DATAA()
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, &find_data)
if handle == win32.win32base.INVALID_HANDLE_VALUE:
return 0
count: ULONG = 0
while count < max_count:
# Skip . and ..
name: str = find_data.cFileName
if name[0] == '.':
if name[1] == 0:
# "."
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
break
continue
if name[1] == '.' and name[2] == 0:
# ".."
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
break
continue
# Copy the filename into newly allocated memory
name_len: sizet = len(name)
name_buf: bytes = _mbuddy.alloc(name_len + 1)
if name_buf == None: break
j: sizet = 0
for ch in name:
name_buf[j] = ch
j += 1
name_buf[name_len] = 0
out_names[count] = str(name_buf)
count += 1
if win32.win32file.FindNextFileA(handle, &find_data) == 0:
break
win32.win32file.FindClose(handle)
return count
# =============================================================================
# Command Execution
# =============================================================================
def system(cmd: str) -> int:
"""Execute a command line command (via the C runtime system()).
Output goes to the child terminal (inherits parent stdout/stderr). Return the exit code (0 = success)."""
return stdlib.system(cmd)