feat: filesystem and Files app support for >64 file entries, 64-bit sizes, async ops, GUI toolkit improvements to devexplorer app
This commit is contained in:
@@ -8,6 +8,92 @@
|
||||
|
||||
namespace filemanager {
|
||||
|
||||
bool filemanager_ensure_entry_capacity(FileManagerState* fm, int needed) {
|
||||
if (!fm) return false;
|
||||
if (needed <= fm->entry_cap) return true;
|
||||
if (needed > FM_ENTRY_HARD_CAP) needed = FM_ENTRY_HARD_CAP;
|
||||
if (needed <= fm->entry_cap) return true; // already at the hard cap
|
||||
|
||||
int new_cap = fm->entry_cap ? fm->entry_cap : 64;
|
||||
while (new_cap < needed) new_cap *= 2;
|
||||
if (new_cap > FM_ENTRY_HARD_CAP) new_cap = FM_ENTRY_HARD_CAP;
|
||||
|
||||
// Grow each parallel array. On any failure we leave already-grown arrays in
|
||||
// place (they only get bigger) and keep entry_cap at the old value, so all
|
||||
// arrays still hold at least entry_count entries.
|
||||
auto* names = (char(*)[128])montauk::realloc(fm->entry_names, (uint64_t)new_cap * 128);
|
||||
if (!names) return false;
|
||||
fm->entry_names = names;
|
||||
int* types = (int*)montauk::realloc(fm->entry_types, (uint64_t)new_cap * sizeof(int));
|
||||
if (!types) return false;
|
||||
fm->entry_types = types;
|
||||
uint64_t* sizes = (uint64_t*)montauk::realloc(fm->entry_sizes, (uint64_t)new_cap * sizeof(uint64_t));
|
||||
if (!sizes) return false;
|
||||
fm->entry_sizes = sizes;
|
||||
bool* isdir = (bool*)montauk::realloc(fm->is_dir, (uint64_t)new_cap * sizeof(bool));
|
||||
if (!isdir) return false;
|
||||
fm->is_dir = isdir;
|
||||
int* di = (int*)montauk::realloc(fm->drive_indices, (uint64_t)new_cap * sizeof(int));
|
||||
if (!di) return false;
|
||||
fm->drive_indices = di;
|
||||
int* dk = (int*)montauk::realloc(fm->drive_kinds, (uint64_t)new_cap * sizeof(int));
|
||||
if (!dk) return false;
|
||||
fm->drive_kinds = dk;
|
||||
bool* msel = (bool*)montauk::realloc(fm->multi_selected, (uint64_t)new_cap * sizeof(bool));
|
||||
if (!msel) return false;
|
||||
fm->multi_selected = msel;
|
||||
auto* ve = (FileManagerVirtualEntry*)montauk::realloc(
|
||||
fm->virtual_entries, (uint64_t)new_cap * sizeof(FileManagerVirtualEntry));
|
||||
if (!ve) return false;
|
||||
fm->virtual_entries = ve;
|
||||
|
||||
// Zero the newly added tail: virtual_entries icon pointers must start null
|
||||
// (free_app_icons walks them) and multi_selected flags must start false.
|
||||
int old_cap = fm->entry_cap;
|
||||
int added = new_cap - old_cap;
|
||||
montauk::memset(&fm->multi_selected[old_cap], 0, (uint64_t)added * sizeof(bool));
|
||||
montauk::memset(&fm->virtual_entries[old_cap], 0,
|
||||
(uint64_t)added * sizeof(FileManagerVirtualEntry));
|
||||
fm->entry_cap = new_cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool filemanager_ensure_clipboard_capacity(FileManagerState* fm, int needed) {
|
||||
if (!fm) return false;
|
||||
if (needed <= fm->clipboard_cap) return true;
|
||||
if (needed > FM_ENTRY_HARD_CAP) needed = FM_ENTRY_HARD_CAP;
|
||||
if (needed <= fm->clipboard_cap) return true;
|
||||
|
||||
int new_cap = fm->clipboard_cap ? fm->clipboard_cap : 64;
|
||||
while (new_cap < needed) new_cap *= 2;
|
||||
if (new_cap > FM_ENTRY_HARD_CAP) new_cap = FM_ENTRY_HARD_CAP;
|
||||
|
||||
auto* paths = (char(*)[256])montauk::realloc(fm->clipboard_paths, (uint64_t)new_cap * 256);
|
||||
if (!paths) return false;
|
||||
fm->clipboard_paths = paths;
|
||||
fm->clipboard_cap = new_cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
void filemanager_free_arrays(FileManagerState* fm) {
|
||||
if (!fm) return;
|
||||
filemanager_free_app_icons(fm);
|
||||
montauk::mfree(fm->entry_names); fm->entry_names = nullptr;
|
||||
montauk::mfree(fm->entry_types); fm->entry_types = nullptr;
|
||||
montauk::mfree(fm->entry_sizes); fm->entry_sizes = nullptr;
|
||||
montauk::mfree(fm->is_dir); fm->is_dir = nullptr;
|
||||
montauk::mfree(fm->drive_indices); fm->drive_indices = nullptr;
|
||||
montauk::mfree(fm->drive_kinds); fm->drive_kinds = nullptr;
|
||||
montauk::mfree(fm->multi_selected); fm->multi_selected = nullptr;
|
||||
montauk::mfree(fm->virtual_entries); fm->virtual_entries = nullptr;
|
||||
montauk::mfree(fm->clipboard_paths); fm->clipboard_paths = nullptr;
|
||||
fm->entry_cap = 0;
|
||||
fm->clipboard_cap = 0;
|
||||
fm->entry_count = 0;
|
||||
fm->virtual_entry_count = 0;
|
||||
fm->clipboard_count = 0;
|
||||
}
|
||||
|
||||
static void filemanager_reset_list_state(FileManagerState* fm) {
|
||||
fm->selected = -1;
|
||||
fm->scroll_offset = 0;
|
||||
@@ -33,7 +119,7 @@ static void filemanager_add_root_entry(FileManagerState* fm,
|
||||
const char* name,
|
||||
FileManagerEntryType type,
|
||||
int drive_index) {
|
||||
if (!fm || fm->entry_count >= 64) return;
|
||||
if (!fm || !filemanager_ensure_entry_capacity(fm, fm->entry_count + 1)) return;
|
||||
|
||||
int idx = fm->entry_count++;
|
||||
montauk::strncpy(fm->entry_names[idx], name, 63);
|
||||
@@ -51,7 +137,7 @@ static int filemanager_add_virtual_entry(FileManagerState* fm,
|
||||
const char* name,
|
||||
const char* icon_path,
|
||||
FileManagerVirtualEntryKind kind) {
|
||||
if (!fm || fm->entry_count >= 64) return -1;
|
||||
if (!fm || !filemanager_ensure_entry_capacity(fm, fm->entry_count + 1)) return -1;
|
||||
|
||||
int idx = fm->entry_count;
|
||||
montauk::strncpy(fm->entry_names[idx], name, 63);
|
||||
@@ -117,7 +203,7 @@ void filemanager_read_drives(FileManagerState* fm) {
|
||||
|
||||
// Special user folders (Documents, Desktop, Music, etc.) - only if they exist
|
||||
if (ds && ds->home_dir[0] != '\0') {
|
||||
for (int sf = 0; sf < SF_COUNT && fm->entry_count < 64; sf++) {
|
||||
for (int sf = 0; sf < SF_COUNT; sf++) {
|
||||
char probe_path[256];
|
||||
montauk::strcpy(probe_path, ds->home_dir);
|
||||
int plen = montauk::slen(probe_path);
|
||||
@@ -167,7 +253,6 @@ void filemanager_read_drives(FileManagerState* fm) {
|
||||
str_append(label, ")", 64);
|
||||
}
|
||||
filemanager_add_root_entry(fm, label, FM_ENTRY_DRIVE, d);
|
||||
if (fm->entry_count >= 64) break;
|
||||
}
|
||||
|
||||
filemanager_reset_list_state(fm);
|
||||
@@ -181,10 +266,7 @@ void filemanager_read_dir(FileManagerState* fm) {
|
||||
|
||||
filemanager_free_app_icons(fm);
|
||||
fm->virtual_view = FM_VIRTUAL_VIEW_NONE;
|
||||
|
||||
const char* names[64];
|
||||
fm->entry_count = montauk::readdir(fm->current_path, names, 64);
|
||||
if (fm->entry_count < 0) fm->entry_count = 0;
|
||||
fm->entry_count = 0;
|
||||
|
||||
// readdir returns full paths from the VFS (e.g. "man/fetch.1" instead
|
||||
// of just "fetch.1"). Compute the prefix to strip so we get basenames.
|
||||
@@ -206,52 +288,67 @@ void filemanager_read_dir(FileManagerState* fm) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < fm->entry_count; i++) {
|
||||
const char* raw = names[i];
|
||||
// Strip directory prefix if it matches
|
||||
if (prefix_len > 0) {
|
||||
bool match = true;
|
||||
for (int k = 0; k < prefix_len; k++) {
|
||||
if (raw[k] != prefix[k]) { match = false; break; }
|
||||
// Page through the directory. The kernel returns a bounded batch per call,
|
||||
// so keep asking from the running offset until a call returns nothing. This
|
||||
// removes the old fixed cap -- directories of any size are fully listed.
|
||||
static constexpr int BATCH = 128;
|
||||
const char* names[BATCH];
|
||||
for (;;) {
|
||||
int got = montauk::readdir_at(fm->current_path, names, BATCH, fm->entry_count);
|
||||
if (got <= 0) break;
|
||||
if (!filemanager_ensure_entry_capacity(fm, fm->entry_count + got)) break;
|
||||
|
||||
for (int b = 0; b < got; b++) {
|
||||
int i = fm->entry_count + b;
|
||||
const char* raw = names[b];
|
||||
// Strip directory prefix if it matches
|
||||
if (prefix_len > 0) {
|
||||
bool match = true;
|
||||
for (int k = 0; k < prefix_len; k++) {
|
||||
if (raw[k] != prefix[k]) { match = false; break; }
|
||||
}
|
||||
if (match) raw += prefix_len;
|
||||
}
|
||||
if (match) raw += prefix_len;
|
||||
}
|
||||
montauk::strncpy(fm->entry_names[i], raw, 128);
|
||||
int len = montauk::slen(fm->entry_names[i]);
|
||||
montauk::strncpy(fm->entry_names[i], raw, 128);
|
||||
int len = montauk::slen(fm->entry_names[i]);
|
||||
|
||||
// Detect directory
|
||||
if (len > 0 && fm->entry_names[i][len - 1] == '/') {
|
||||
fm->is_dir[i] = true;
|
||||
fm->entry_names[i][len - 1] = '\0';
|
||||
} else {
|
||||
fm->is_dir[i] = false;
|
||||
}
|
||||
|
||||
fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]);
|
||||
|
||||
// Get file size
|
||||
fm->entry_sizes[i] = 0;
|
||||
if (!fm->is_dir[i]) {
|
||||
char fullpath[512];
|
||||
montauk::strcpy(fullpath, fm->current_path);
|
||||
int plen = montauk::slen(fullpath);
|
||||
if (plen > 0 && fullpath[plen - 1] != '/') {
|
||||
str_append(fullpath, "/", 512);
|
||||
// Detect directory
|
||||
if (len > 0 && fm->entry_names[i][len - 1] == '/') {
|
||||
fm->is_dir[i] = true;
|
||||
fm->entry_names[i][len - 1] = '\0';
|
||||
} else {
|
||||
fm->is_dir[i] = false;
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[i], 512);
|
||||
int fd = montauk::open(fullpath);
|
||||
if (fd >= 0) {
|
||||
fm->entry_sizes[i] = (int)montauk::getsize(fd);
|
||||
montauk::close(fd);
|
||||
|
||||
fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]);
|
||||
|
||||
// Get file size (64-bit: no truncation for files >= 2 GiB)
|
||||
fm->entry_sizes[i] = 0;
|
||||
if (!fm->is_dir[i]) {
|
||||
char fullpath[512];
|
||||
montauk::strcpy(fullpath, fm->current_path);
|
||||
int plen = montauk::slen(fullpath);
|
||||
if (plen > 0 && fullpath[plen - 1] != '/') {
|
||||
str_append(fullpath, "/", 512);
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[i], 512);
|
||||
int fd = montauk::open(fullpath);
|
||||
if (fd >= 0) {
|
||||
fm->entry_sizes[i] = montauk::getsize(fd);
|
||||
montauk::close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fm->entry_count += got;
|
||||
if (fm->entry_count >= FM_ENTRY_HARD_CAP) break;
|
||||
}
|
||||
|
||||
// Sort: directories first, then alphabetical (case-insensitive)
|
||||
for (int i = 1; i < fm->entry_count; i++) {
|
||||
char tmp_name[128];
|
||||
int tmp_type = fm->entry_types[i];
|
||||
int tmp_size = fm->entry_sizes[i];
|
||||
uint64_t tmp_size = fm->entry_sizes[i];
|
||||
bool tmp_isdir = fm->is_dir[i];
|
||||
montauk::strcpy(tmp_name, fm->entry_names[i]);
|
||||
|
||||
@@ -315,7 +412,7 @@ void filemanager_read_apps(FileManagerState* fm) {
|
||||
DesktopState* ds = fm->desktop;
|
||||
if (!ds) return;
|
||||
|
||||
for (int i = 0; i < ds->external_app_count && fm->entry_count < 64; i++) {
|
||||
for (int i = 0; i < ds->external_app_count; i++) {
|
||||
filemanager_add_external_app_entry(fm, ds->external_apps[i], i);
|
||||
}
|
||||
|
||||
@@ -327,24 +424,23 @@ void filemanager_read_system_configuration(FileManagerState* fm) {
|
||||
|
||||
DesktopAppletEntry applets[32];
|
||||
int applet_count = desktop_list_system_configuration_applets(fm->desktop, applets, 32);
|
||||
for (int i = 0; i < applet_count && fm->entry_count < 64; i++) {
|
||||
for (int i = 0; i < applet_count; i++) {
|
||||
filemanager_add_system_configuration_applet_entry(fm, applets[i]);
|
||||
}
|
||||
|
||||
filemanager_reset_list_state(fm);
|
||||
}
|
||||
|
||||
bool filemanager_delete_recursive(const char* path) {
|
||||
// Try deleting as a file (or empty directory) first
|
||||
if (montauk::fdelete(path) == 0) return true;
|
||||
// Read ALL direct children of `dir` into a freshly-allocated array of basenames
|
||||
// (each up to 255 chars, directory entries keep their trailing '/'). Pages the
|
||||
// directory fully so there is no fixed entry cap. Returns the child count and
|
||||
// stores the malloc'd buffer in *out (caller frees with montauk::mfree); the
|
||||
// buffer is null when the count is 0. Returns -1 if the path is not readable.
|
||||
static int filemanager_read_all_children(const char* dir, char (**out)[256]) {
|
||||
*out = nullptr;
|
||||
|
||||
// If that failed, it may be a non-empty directory -- enumerate and delete children
|
||||
const char* names[64];
|
||||
int count = montauk::readdir(path, names, 64);
|
||||
if (count < 0) return false;
|
||||
|
||||
// Compute the prefix to strip (readdir returns paths relative to drive root)
|
||||
const char* after_drive = path;
|
||||
// Compute the prefix to strip (readdir returns drive-root-relative paths).
|
||||
const char* after_drive = dir;
|
||||
for (int k = 0; after_drive[k]; k++) {
|
||||
if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
|
||||
after_drive += k + 2;
|
||||
@@ -362,22 +458,68 @@ bool filemanager_delete_recursive(const char* path) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char* raw = names[i];
|
||||
if (prefix_len > 0) {
|
||||
bool match = true;
|
||||
for (int k = 0; k < prefix_len; k++) {
|
||||
if (raw[k] != prefix[k]) { match = false; break; }
|
||||
}
|
||||
if (match) raw += prefix_len;
|
||||
static constexpr int BATCH = 128;
|
||||
const char* names[BATCH];
|
||||
char (*list)[256] = nullptr;
|
||||
int cap = 0;
|
||||
int count = 0;
|
||||
bool any = false;
|
||||
|
||||
for (;;) {
|
||||
int got = montauk::readdir_at(dir, names, BATCH, count);
|
||||
if (got < 0) { if (!any) return -1; break; }
|
||||
any = true;
|
||||
if (got == 0) break;
|
||||
|
||||
if (count + got > cap) {
|
||||
int new_cap = cap ? cap * 2 : 128;
|
||||
while (new_cap < count + got) new_cap *= 2;
|
||||
if (new_cap > FM_ENTRY_HARD_CAP) new_cap = FM_ENTRY_HARD_CAP;
|
||||
auto* grown = (char(*)[256])montauk::realloc(list, (uint64_t)new_cap * 256);
|
||||
if (!grown) break;
|
||||
list = grown;
|
||||
cap = new_cap;
|
||||
}
|
||||
|
||||
for (int b = 0; b < got && count < cap; b++) {
|
||||
const char* raw = names[b];
|
||||
if (prefix_len > 0) {
|
||||
bool match = true;
|
||||
for (int k = 0; k < prefix_len; k++) {
|
||||
if (raw[k] != prefix[k]) { match = false; break; }
|
||||
}
|
||||
if (match) raw += prefix_len;
|
||||
}
|
||||
montauk::strncpy(list[count], raw, 255);
|
||||
list[count][255] = '\0';
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count >= FM_ENTRY_HARD_CAP) break;
|
||||
}
|
||||
|
||||
*out = list;
|
||||
return count;
|
||||
}
|
||||
|
||||
bool filemanager_delete_recursive(const char* path) {
|
||||
// Try deleting as a file (or empty directory) first
|
||||
if (montauk::fdelete(path) == 0) return true;
|
||||
|
||||
// If that failed, it may be a non-empty directory -- snapshot all children
|
||||
// first (the directory shrinks as we delete, so we cannot page while
|
||||
// deleting), then delete each.
|
||||
char (*children)[256] = nullptr;
|
||||
int count = filemanager_read_all_children(path, &children);
|
||||
if (count < 0) return false;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
char child[512];
|
||||
montauk::strcpy(child, path);
|
||||
int plen = montauk::slen(child);
|
||||
if (plen > 0 && child[plen - 1] != '/')
|
||||
str_append(child, "/", 512);
|
||||
str_append(child, raw, 512);
|
||||
str_append(child, children[i], 512);
|
||||
|
||||
// Strip trailing slash if present (directory marker)
|
||||
int clen = montauk::slen(child);
|
||||
@@ -386,6 +528,8 @@ bool filemanager_delete_recursive(const char* path) {
|
||||
filemanager_delete_recursive(child);
|
||||
}
|
||||
|
||||
if (children) montauk::mfree(children);
|
||||
|
||||
// Now the directory should be empty -- delete it
|
||||
return montauk::fdelete(path) == 0;
|
||||
}
|
||||
@@ -446,42 +590,15 @@ bool filemanager_copy_dir_recursive(const char* src, const char* dst) {
|
||||
|
||||
montauk::fmkdir(dst);
|
||||
|
||||
const char* names[64];
|
||||
int count = montauk::readdir(src, names, 64);
|
||||
char (*children)[256] = nullptr;
|
||||
int count = filemanager_read_all_children(src, &children);
|
||||
if (count < 0) return false;
|
||||
|
||||
// Compute prefix to strip
|
||||
const char* after_drive = src;
|
||||
for (int k = 0; after_drive[k]; k++) {
|
||||
if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
|
||||
after_drive += k + 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
char prefix[256] = {0};
|
||||
int prefix_len = 0;
|
||||
if (after_drive[0] != '\0') {
|
||||
montauk::strcpy(prefix, after_drive);
|
||||
prefix_len = montauk::slen(prefix);
|
||||
if (prefix_len > 0 && prefix[prefix_len - 1] != '/') {
|
||||
prefix[prefix_len++] = '/';
|
||||
prefix[prefix_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char* raw = names[i];
|
||||
if (prefix_len > 0) {
|
||||
bool match = true;
|
||||
for (int k = 0; k < prefix_len; k++) {
|
||||
if (raw[k] != prefix[k]) { match = false; break; }
|
||||
}
|
||||
if (match) raw += prefix_len;
|
||||
}
|
||||
|
||||
bool child_is_dir = false;
|
||||
char basename[64];
|
||||
montauk::strncpy(basename, raw, 63);
|
||||
char basename[256];
|
||||
montauk::strncpy(basename, children[i], 255);
|
||||
basename[255] = '\0';
|
||||
int blen = montauk::slen(basename);
|
||||
if (blen > 0 && basename[blen - 1] == '/') {
|
||||
child_is_dir = true;
|
||||
@@ -498,6 +615,8 @@ bool filemanager_copy_dir_recursive(const char* src, const char* dst) {
|
||||
filemanager_copy_file(src_child, dst_child);
|
||||
}
|
||||
|
||||
if (children) montauk::mfree(children);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user