|
10 | 10 | Ultra-fast disk reader module |
11 | 11 | """ |
12 | 12 |
|
| 13 | +import errno |
| 14 | + |
13 | 15 | from cpython.buffer cimport PyBuffer_FillInfo |
14 | | -from libc.stdlib cimport free |
| 16 | +from cpython.mem cimport PyMem_Free |
| 17 | +from cpython.mem cimport PyMem_Malloc |
| 18 | +from cpython.unicode cimport PyUnicode_FromString |
| 19 | +from libc.stddef cimport size_t |
15 | 20 |
|
16 | 21 | cdef extern from "disk_io.h": |
17 | 22 | int read_all_pread(const char* path, unsigned char* dst, size_t* out_len, |
18 | 23 | bint sequential, bint willneed, bint drop_after) |
19 | 24 | int read_all_mmap(const char* path, unsigned char** dst, size_t* out_len) |
20 | 25 | int unmap_memory_c(unsigned char* addr, size_t size) |
21 | 26 |
|
| 27 | +cdef extern from "directories.h": |
| 28 | + ctypedef struct file_info_t: |
| 29 | + char* name |
| 30 | + int is_directory |
| 31 | + int is_regular_file |
| 32 | + long long size |
| 33 | + long long mtime |
| 34 | + |
| 35 | + int list_directory_c "list_directory"(const char* path, file_info_t** files, size_t* count) |
| 36 | + void free_file_list(file_info_t* files, size_t count) |
| 37 | + int list_matching_files_c "list_matching_files"(const char* base_path, const char** extensions, |
| 38 | + size_t ext_count, char*** files, size_t* count) nogil |
| 39 | + void free_file_names(char** files, size_t count) |
| 40 | + |
22 | 41 | cdef class MappedMemory: |
23 | 42 | cdef unsigned char* data |
24 | 43 | cdef size_t size |
25 | 44 | cdef bint owned |
26 | 45 |
|
27 | | - def __dealloc__(self): |
28 | | - if self.owned and self.data != NULL: |
29 | | - # Free the allocated memory (for non-mmap case) |
30 | | - free(self.data) |
31 | | - |
32 | 46 | def __getbuffer__(self, Py_buffer* buffer, int flags): |
33 | 47 | PyBuffer_FillInfo(buffer, self, self.data, self.size, 1, flags) |
34 | 48 |
|
@@ -64,6 +78,104 @@ def read_file(str path, bint sequential=True, bint willneed=True, bint drop_afte |
64 | 78 | return memoryview(buf)[:out_len] |
65 | 79 |
|
66 | 80 |
|
| 81 | +def list_directory(str path): |
| 82 | + """Return directory entries using the native file system scanner.""" |
| 83 | + |
| 84 | + path_b = path.encode("utf-8") |
| 85 | + cdef const char* c_path = path_b |
| 86 | + cdef file_info_t* files = NULL |
| 87 | + cdef size_t count = 0 |
| 88 | + |
| 89 | + cdef int rc = list_directory_c(c_path, &files, &count) |
| 90 | + if rc != 0: |
| 91 | + err = -rc |
| 92 | + if err == errno.ENOENT: |
| 93 | + raise FileNotFoundError(path) |
| 94 | + raise OSError(err, f"Failed to list directory: {path}") |
| 95 | + |
| 96 | + entries = [] |
| 97 | + cdef file_info_t entry |
| 98 | + cdef char* name_ptr |
| 99 | + cdef size_t idx |
| 100 | + try: |
| 101 | + for idx in range(count): |
| 102 | + entry = files[idx] |
| 103 | + name_ptr = entry.name |
| 104 | + if name_ptr == NULL: |
| 105 | + continue |
| 106 | + |
| 107 | + py_name = PyUnicode_FromString(name_ptr) |
| 108 | + entries.append( |
| 109 | + ( |
| 110 | + py_name, |
| 111 | + bool(entry.is_directory), |
| 112 | + bool(entry.is_regular_file), |
| 113 | + entry.size, |
| 114 | + entry.mtime, |
| 115 | + ) |
| 116 | + ) |
| 117 | + finally: |
| 118 | + if files != NULL: |
| 119 | + free_file_list(files, count) |
| 120 | + |
| 121 | + return entries |
| 122 | + |
| 123 | + |
| 124 | +def list_files(str path, extensions): |
| 125 | + """Return a list of files under ``path`` matching provided extensions.""" |
| 126 | + |
| 127 | + if extensions is None: |
| 128 | + raise ValueError("extensions must be provided") |
| 129 | + |
| 130 | + ext_seq = tuple(extensions) |
| 131 | + ext_count = len(ext_seq) |
| 132 | + |
| 133 | + cdef size_t c_ext_count = ext_count |
| 134 | + cdef const char** ext_array = NULL |
| 135 | + cdef char** file_array = NULL |
| 136 | + cdef size_t file_count = 0 |
| 137 | + |
| 138 | + ext_bytes = [ext.encode("utf-8") if isinstance(ext, str) else ext for ext in ext_seq] |
| 139 | + |
| 140 | + if c_ext_count > 0: |
| 141 | + ext_array = <const char**>PyMem_Malloc(c_ext_count * sizeof(const char*)) |
| 142 | + if ext_array == NULL: |
| 143 | + raise MemoryError("Unable to allocate extension array") |
| 144 | + for idx in range(c_ext_count): |
| 145 | + ext_array[idx] = <const char*>ext_bytes[idx] |
| 146 | + |
| 147 | + path_b = path.encode("utf-8") |
| 148 | + cdef const char* c_path = path_b |
| 149 | + |
| 150 | + cdef int rc |
| 151 | + with nogil: |
| 152 | + rc = list_matching_files_c(c_path, ext_array, c_ext_count, &file_array, &file_count) |
| 153 | + |
| 154 | + if ext_array != NULL: |
| 155 | + PyMem_Free(ext_array) |
| 156 | + |
| 157 | + if rc != 0: |
| 158 | + if file_array != NULL: |
| 159 | + free_file_names(file_array, file_count) |
| 160 | + err = -rc |
| 161 | + if err == errno.ENOENT: |
| 162 | + raise FileNotFoundError(path) |
| 163 | + raise OSError(err, f"Failed to list files: {path}") |
| 164 | + |
| 165 | + results = [] |
| 166 | + try: |
| 167 | + for idx in range(file_count): |
| 168 | + py_path = PyUnicode_FromString(file_array[idx]) |
| 169 | + if py_path is None: |
| 170 | + raise MemoryError("Unable to decode file path") |
| 171 | + results.append(py_path) |
| 172 | + finally: |
| 173 | + if file_array != NULL: |
| 174 | + free_file_names(file_array, file_count) |
| 175 | + |
| 176 | + return results |
| 177 | + |
| 178 | + |
67 | 179 | def read_file_mmap(str path): |
68 | 180 | """ |
69 | 181 | Read file using memory mapping - returns an object that provides memoryview interface |
|
0 commit comments