Move pack files instead of copying; one file-op core for all modes

Install: staging and sd:/ are on the same card, so pack folders are now
moved with f_rename (a missing target folder moves in one step, existing
ones are merged file by file). Copying is only a per-entry fallback.
The staging cleanup afterwards only removes empty folders.

Deletion: one f_stat per list entry, no full recursive count before
deleting; large folders are detected with a probe that stops after 51
entries. Progress lines count processed entries instead of needing an
extra pass for a total.

Six copy/delete walkers (fs.c + install.c) are replaced by fs_delete_tree,
fs_move_tree and fs_move_file sharing one path buffer and one copy buffer
per run; the 'alternative copy method' retry is gone. extras/hekate-ams-pro
is moved too. Backups keep copy semantics.
This commit is contained in:
niklascfw committed 2026-11-12 22:57:26 +01:00
1 parent c033b988ca
commit 664216fa11
6 files changed
+453 -784

No files matched your search

+101 -481
View File
@@ -80,390 +80,133 @@ bool install_path_exists(const char *path) {
return (f_stat(path, &fno) == FR_OK);
}
// Count total items (files + directories) in a directory tree recursively
int install_count_directory_items(const char *path) {
DIR dir;
FILINFO fno;
int res;
int count = 0;
res = f_opendir(&dir, path);
if (res != FR_OK) {
return 0;
}
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
// Skip . and ..
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
continue;
}
count++; // Count this item
// If it's a directory, recursively count its contents
if (fno.fattrib & AM_DIR) {
char sub_path[256];
s_printf(sub_path, "%s/%s", path, fno.fname);
count += install_count_directory_items(sub_path);
}
}
f_closedir(&dir);
return count;
/* ---- progress line ---------------------------------------------------
* One status line per operation. Counts entries as they are processed, so no
* extra pass over the tree is needed just to compute a total. */
typedef struct {
const char *verb;
const char *name;
u32 done;
u32 x, y;
} install_progress_t;
static void progress_draw(install_progress_t *p) {
gfx_con_setpos(p->x, p->y);
install_set_color(COLOR_CYAN);
gfx_printf(" %s: %s (%d)", p->verb, p->name, p->done);
install_set_color(COLOR_WHITE);
}
// Helper to combine paths (handles trailing slashes properly)
void install_combine_path(char *result, size_t size, const char *base, const char *add) {
size_t base_len = strlen(base);
if (base_len > 0 && base[base_len - 1] == '/') {
// Base has trailing slash, just append
s_printf(result, "%s%s", base, add);
static void progress_begin(install_progress_t *p, const char *verb, const char *name) {
p->verb = verb;
p->name = name;
p->done = 0;
gfx_con_getpos(&p->x, &p->y);
progress_draw(p);
}
static void progress_tick(void *ctx) {
install_progress_t *p = ctx;
p->done++;
if (p->done % 25 == 0)
progress_draw(p);
}
static void progress_end(install_progress_t *p, int res) {
gfx_con_setpos(p->x, p->y);
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" %s: %s (%d) - Fertig!\n", p->verb, p->name, p->done);
} else {
// No trailing slash, add one
s_printf(result, "%s/%s", base, add);
install_set_color(COLOR_ORANGE);
gfx_printf(" %s: %s - Fehler: %s\n", p->verb, p->name, fs_error_str(res));
}
install_set_color(COLOR_WHITE);
}
/* Copy every regular file at staging root to dst_root; subdirs and volume labels skipped. */
int install_copy_staging_root_files(const char *staging, const char *dst_root) {
DIR dir;
FILINFO fno;
char src_full[256];
char dst_full[256];
int res = f_opendir(&dir, staging);
if (res != FR_OK)
return res;
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0)
break;
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0')))
continue;
if (fno.fattrib & (AM_DIR | AM_VOL))
continue;
install_combine_path(src_full, sizeof(src_full), staging, fno.fname);
install_combine_path(dst_full, sizeof(dst_full), dst_root, fno.fname);
res = file_copy(src_full, dst_full);
if (res != FR_OK) {
f_closedir(&dir);
return res;
}
}
f_closedir(&dir);
return FR_OK;
static const char *install_basename(const char *path) {
const char *slash = strrchr(path, '/');
return slash ? slash + 1 : path;
}
// Recursive folder copy with progress tracking
static int folder_copy_progress_recursive(const char *src, const char *dst, int *copied, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
DIR dir;
FILINFO fno;
int res;
char src_full[256];
char dst_full[256];
char dst_dir[256];
res = f_opendir(&dir, src);
if (res != FR_OK) {
return res;
}
// Get folder name from src path
const char *folder_name = strrchr(src, '/');
if (folder_name) {
folder_name++;
} else {
folder_name = src;
}
// Create destination folder path (handle trailing slash in dst)
install_combine_path(dst_dir, sizeof(dst_dir), dst, folder_name);
// Try to create the directory (ignore if it already exists)
res = f_mkdir(dst_dir);
if (res == FR_EXIST) {
res = FR_OK; // Directory already exists, that's fine
} else if (res != FR_OK) {
// If mkdir fails, check if it's actually a file (shouldn't happen, but be safe)
FILINFO fno;
if (f_stat(dst_dir, &fno) == FR_OK && !(fno.fattrib & AM_DIR)) {
// Destination exists but is a file, not a directory - this is an error
f_closedir(&dir);
return FR_DENIED;
}
// If it's a directory, continue (might have been created between check and mkdir)
if (f_stat(dst_dir, &fno) == FR_OK && (fno.fattrib & AM_DIR)) {
res = FR_OK;
} else {
f_closedir(&dir);
return res;
}
}
// Copy contents
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
// Skip . and ..
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
continue;
}
// Build source and destination paths
install_combine_path(src_full, sizeof(src_full), src, fno.fname);
install_combine_path(dst_full, sizeof(dst_full), dst_dir, fno.fname);
if (fno.fattrib & AM_DIR) {
res = folder_copy_progress_recursive(src_full, dst_dir, copied, total, start_x, start_y, display_name, last_percent);
// Increment counter for directory (it was counted in total)
(*copied)++;
} else {
res = file_copy(src_full, dst_full);
(*copied)++;
}
// Update progress every 10 items or when percentage changes
if ((*copied % 10 == 0 || total == 0) && res == FR_OK) {
int percent = total > 0 ? (*copied * 100) / total : 0;
if (percent != *last_percent || *copied % 50 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, *copied, total);
install_set_color(COLOR_WHITE);
*last_percent = percent;
}
}
if (res != FR_OK) break;
}
f_closedir(&dir);
// Delete one folder tree with its own progress line (staging cleanup, large folders).
int install_delete_tree(const char *path) {
install_progress_t p;
progress_begin(&p, "Loesche", install_basename(path));
int res = fs_delete_tree(path, progress_tick, &p);
progress_end(&p, res);
return res;
}
// Progress-aware folder copy (improved version)
int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name) {
int copied = 0;
int total = 0;
int last_percent = -1;
u32 start_x, start_y;
int res;
// Check if source exists
// Move staging/<name> into dst_parent/<name>. FR_NO_FILE if the pack has no such folder.
int install_move_tree(const char *src, const char *dst_parent, const char *display_name) {
if (!install_path_exists(src)) {
install_set_color(COLOR_ORANGE);
gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name);
install_set_color(COLOR_WHITE);
return FR_NO_FILE;
}
// Count total items first
total = install_count_directory_items(src);
if (total == 0) {
// Empty directory, just create destination
const char *folder_name = strrchr(src, '/');
if (folder_name) folder_name++;
else folder_name = src;
char dst_path[256];
install_combine_path(dst_path, sizeof(dst_path), dst, folder_name);
res = f_mkdir(dst_path);
if (res == FR_OK || res == FR_EXIST) {
return FR_OK;
}
return res;
}
// Save cursor position
gfx_con_getpos(&start_x, &start_y);
// Show initial status
install_set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total);
install_set_color(COLOR_WHITE);
// Perform the copy with progress updates
log_write("COPY TREE %s -> %s (%d Eintraege)\n", src, dst, (u32)total);
res = folder_copy_progress_recursive(src, dst, &copied, total, start_x, start_y, display_name, &last_percent);
log_write("COPY TREE %s: %s\n", src, fs_error_str(res));
// Final update - overwrite the same line
gfx_con_setpos(start_x, start_y);
if (res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" Kopiere: %s [100%%] (%d/%d) - Fertig!\n", display_name, copied, total);
install_set_color(COLOR_WHITE);
} else {
install_progress_t p;
progress_begin(&p, "Installiere", display_name);
int res = fs_move_tree(src, dst_parent, progress_tick, &p);
progress_end(&p, res);
if (res != FR_OK) {
install_set_color(COLOR_RED);
gfx_printf(" Kopiere: %s - Fehlgeschlagen!\n", display_name);
gfx_printf(" Fehler: %s (Code=%d)\n", fs_error_str(res), res);
gfx_printf(" Quelle: %s\n", src);
gfx_printf(" Ziel: %s\n", dst);
gfx_printf(" Quelle: %s\n Ziel: %s\n", src, dst_parent);
install_set_color(COLOR_WHITE);
// Fallback: try using the original folder_copy function
install_set_color(COLOR_ORANGE);
gfx_printf(" Versuche alternative Kopiermethode...\n");
install_set_color(COLOR_WHITE);
int fallback_res = folder_copy(src, dst);
if (fallback_res == FR_OK) {
install_set_color(COLOR_GREEN);
gfx_printf(" Alternative Kopie erfolgreich!\n");
install_set_color(COLOR_WHITE);
return FR_OK;
}
}
return res;
}
// Recursive folder delete with progress tracking (shared implementation)
int folder_delete_progress_recursive(const char *path, int *deleted, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
/* Move every regular file at staging root to dst_root; subdirs and volume labels skipped. */
int install_move_staging_root_files(const char *staging, const char *dst_root) {
DIR dir;
FILINFO fno;
int res;
res = f_opendir(&dir, path);
if (res != FR_OK) {
// Maybe it's a file, try to delete it
res = f_unlink(path);
if (res == FR_OK || res == FR_NO_FILE) {
(*deleted)++;
}
char src_full[FS_PATH_MAX];
char dst_full[FS_PATH_MAX];
int res = f_opendir(&dir, staging);
if (res != FR_OK)
return res;
}
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
// Skip . and ..
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
while ((res = f_readdir(&dir, &fno)) == FR_OK && fno.fname[0]) {
if (fno.fattrib & (AM_DIR | AM_VOL))
continue;
}
char full_path[256];
install_combine_path(full_path, sizeof(full_path), path, fno.fname);
if (fno.fattrib & AM_DIR) {
res = folder_delete_progress_recursive(full_path, deleted, total, start_x, start_y, display_name, last_percent);
(*deleted)++;
} else {
// Clear read-only attribute if set
if (fno.fattrib & AM_RDO) {
f_chmod(full_path, fno.fattrib & ~AM_RDO, AM_RDO);
}
res = f_unlink(full_path);
if (res == FR_OK || res == FR_NO_FILE) {
(*deleted)++;
}
}
// Update progress every 10 items or when percentage changes
if ((*deleted % 10 == 0 || total == 0) && (res == FR_OK || res == FR_NO_FILE)) {
int percent = total > 0 ? (*deleted * 100) / total : 0;
if (percent != *last_percent || *deleted % 50 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", display_name, percent, *deleted, total);
install_set_color(COLOR_WHITE);
*last_percent = percent;
}
}
if (res != FR_OK && res != FR_NO_FILE) break;
s_printf(src_full, "%s/%s", staging, fno.fname);
s_printf(dst_full, "%s%s", dst_root, fno.fname);
res = fs_move_file(src_full, dst_full);
if (res != FR_OK)
break;
}
f_closedir(&dir);
if (res == FR_OK || res == FR_NO_FILE) {
// Check and clear read-only attribute on directory if set
FILINFO dir_info;
if (f_stat(path, &dir_info) == FR_OK && (dir_info.fattrib & AM_RDO)) {
f_chmod(path, dir_info.fattrib & ~AM_RDO, AM_RDO);
}
res = f_unlink(path);
}
return res;
}
// Progress-aware delete for a single directory (used for large directories)
int folder_delete_single_with_progress(const char *path, const char *display_name) {
int deleted = 0;
int total = 0;
int last_percent = -1;
u32 start_x, start_y;
int res;
// Count total items first
total = install_count_directory_items(path);
if (total == 0) {
// Empty directory, just delete it
FILINFO fno;
if (f_stat(path, &fno) == FR_OK) {
if (fno.fattrib & AM_RDO) {
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
}
res = f_unlink(path);
} else {
res = FR_NO_FILE;
}
return res;
}
// Save cursor position
gfx_con_getpos(&start_x, &start_y);
// Show initial status
install_set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", display_name, total);
install_set_color(COLOR_WHITE);
// Use the shared recursive progress function
res = folder_delete_progress_recursive(path, &deleted, total, start_x, start_y, display_name, &last_percent);
// Final update
gfx_con_setpos(start_x, start_y);
if (res == FR_OK || res == FR_NO_FILE) {
install_set_color(COLOR_GREEN);
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", display_name, deleted, total);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_RED);
gfx_printf(" Loesche: %s - Fehlgeschlagen!\n", display_name);
install_set_color(COLOR_WHITE);
}
return res;
}
#define LARGE_DIR_ITEMS 50
// Delete a list of paths with progress tracking
// Delete multiple path lists under one label; one progress line "Bereinige: folder/ [p%] (d/t)".
// Varargs: path lists (const char**), terminated by NULL.
int delete_path_lists_grouped(const char *folder_display_name, ...) {
va_list ap;
const char *entries[GROUPED_DELETE_MAX_ENTRIES];
bool is_dir[GROUPED_DELETE_MAX_ENTRIES];
int n_entries = 0;
int deleted = 0;
int failed = 0;
u32 start_x, start_y;
int last_percent = -1;
int res;
FILINFO fno;
// One f_stat per entry: existence + type in a single call.
va_start(ap, folder_display_name);
const char **list;
while (n_entries < GROUPED_DELETE_MAX_ENTRIES && (list = va_arg(ap, const char **)) != NULL) {
for (int i = 0; list[i] != NULL && n_entries < GROUPED_DELETE_MAX_ENTRIES; i++) {
if (install_path_exists(list[i]) && !install_path_blocked_by_user_preserve(list[i]))
entries[n_entries++] = list[i];
if (f_stat(list[i], &fno) != FR_OK || install_path_blocked_by_user_preserve(list[i]))
continue;
entries[n_entries] = list[i];
is_dir[n_entries] = (fno.fattrib & AM_DIR) != 0;
n_entries++;
}
}
va_end(ap);
@@ -471,167 +214,42 @@ int delete_path_lists_grouped(const char *folder_display_name, ...) {
if (n_entries == 0)
return FR_OK;
int total_paths = n_entries;
gfx_con_getpos(&start_x, &start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Bereinige: %s [ 0%%] (0/%d)", folder_display_name, total_paths);
gfx_printf(" Bereinige: %s [ 0%%] (0/%d)", folder_display_name, n_entries);
install_set_color(COLOR_WHITE);
for (int i = 0; i < n_entries; i++) {
const char *path = entries[i];
FILINFO fno;
if (f_stat(path, &fno) != FR_OK)
continue;
const char *item_name = strrchr(path, '/');
if (item_name) item_name++;
else item_name = path;
if (fno.fattrib & AM_DIR) {
int item_count = install_count_directory_items(path);
if (item_count > 50) {
gfx_printf("\n");
res = folder_delete_single_with_progress(path, item_name);
} else {
res = folder_delete(path);
}
int res;
// Big folders get their own progress line; the probe stops after 51 entries.
if (is_dir[i] && fs_count_items(entries[i], LARGE_DIR_ITEMS + 1) > LARGE_DIR_ITEMS) {
gfx_printf("\n");
res = install_delete_tree(entries[i]);
} else {
if (fno.fattrib & AM_RDO)
f_chmod(path, fno.fattrib & ~AM_RDO, AM_RDO);
res = f_unlink(path);
res = fs_delete_tree(entries[i], NULL, NULL);
}
log_write("DELETE %s: %s\n", path, fs_error_str(res));
if (res == FR_OK || res == FR_NO_FILE)
if (res == FR_OK)
deleted++;
else
failed++;
int percent = (deleted * 100) / total_paths;
if (percent != last_percent || deleted % 5 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d)", folder_display_name, percent, deleted, total_paths);
install_set_color(COLOR_WHITE);
last_percent = percent;
}
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d)", folder_display_name,
((deleted + failed) * 100) / n_entries, deleted, n_entries);
install_set_color(COLOR_WHITE);
}
gfx_con_setpos(start_x, start_y);
if (failed == 0) {
install_set_color(COLOR_GREEN);
gfx_printf(" Bereinige: %s [100%%] (%d/%d) - Fertig!\n", folder_display_name, deleted, total_paths);
install_set_color(COLOR_WHITE);
gfx_printf(" Bereinige: %s [100%%] (%d/%d) - Fertig!\n", folder_display_name, deleted, n_entries);
} else {
install_set_color(COLOR_ORANGE);
gfx_printf(" Bereinige: %s [%3d%%] (%d/%d) - %d Fehler\n", folder_display_name, last_percent, deleted, total_paths, failed);
install_set_color(COLOR_WHITE);
gfx_printf(" Bereinige: %s (%d/%d) - %d Fehler\n", folder_display_name, deleted, n_entries, failed);
}
return (failed == 0) ? FR_OK : FR_DISK_ERR;
}
int delete_path_list(const char* paths[], const char* description) {
int res;
int deleted = 0;
int failed = 0;
int total_paths = 0;
u32 start_x, start_y;
int last_percent = -1;
// Count total paths first (only those we will delete)
for (int i = 0; paths[i] != NULL; i++) {
if (install_path_exists(paths[i]) && !install_path_blocked_by_user_preserve(paths[i])) {
total_paths++;
}
}
if (total_paths == 0) {
return FR_OK; // Nothing to delete
}
// Save cursor position
gfx_con_getpos(&start_x, &start_y);
// Show initial status
install_set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", description, total_paths);
install_set_color(COLOR_WHITE);
for (int i = 0; paths[i] != NULL; i++) {
if (install_path_exists(paths[i]) && !install_path_blocked_by_user_preserve(paths[i])) {
FILINFO fno;
f_stat(paths[i], &fno);
// Extract just the folder/file name for display
const char *item_name = strrchr(paths[i], '/');
if (item_name) {
item_name++; // Skip the '/'
} else {
item_name = paths[i];
}
if (fno.fattrib & AM_DIR) {
// Check if directory has many items - if so, show detailed progress
int item_count = install_count_directory_items(paths[i]);
if (item_count > 50) {
// Large directory - show individual progress
// Move to new line for the detailed progress
gfx_printf("\n");
res = folder_delete_single_with_progress(paths[i], item_name);
} else {
// Small directory - use regular delete
res = folder_delete(paths[i]);
}
} else {
// Clear read-only attribute if set
if (fno.fattrib & AM_RDO) {
f_chmod(paths[i], fno.fattrib & ~AM_RDO, AM_RDO);
}
res = f_unlink(paths[i]);
}
if (res == FR_OK || res == FR_NO_FILE) {
deleted++;
} else {
failed++;
}
// Update list-level progress (only if not showing detailed progress)
if (fno.fattrib & AM_DIR && install_count_directory_items(paths[i]) <= 50) {
int percent = (deleted * 100) / total_paths;
if (percent != last_percent || deleted % 5 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", description, percent, deleted, total_paths);
install_set_color(COLOR_WHITE);
last_percent = percent;
}
} else if (!(fno.fattrib & AM_DIR)) {
// Update for files
int percent = (deleted * 100) / total_paths;
if (percent != last_percent || deleted % 5 == 0) {
gfx_con_setpos(start_x, start_y);
install_set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s [%3d%%] (%d/%d)", description, percent, deleted, total_paths);
install_set_color(COLOR_WHITE);
last_percent = percent;
}
}
}
}
// Final update
gfx_con_setpos(start_x, start_y);
if (failed == 0) {
install_set_color(COLOR_GREEN);
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", description, deleted, total_paths);
install_set_color(COLOR_WHITE);
} else {
install_set_color(COLOR_ORANGE);
gfx_printf(" Loesche: %s [%3d%%] (%d/%d) - %d Fehler\n", description, last_percent, deleted, total_paths, failed);
install_set_color(COLOR_WHITE);
}
return (failed == 0) ? FR_OK : FR_DISK_ERR;
}
@@ -1228,15 +846,16 @@ static int install_apply_hekate_ams_pro(void)
if (fno.fname[0] == '.')
continue;
char src[256];
// extras/ is deleted afterwards, so moving is enough.
char src[FS_PATH_MAX];
s_printf(src, "%s/%s", HEKATE_AMS_PRO_SRC, fno.fname);
if (fno.fattrib & AM_DIR)
res = folder_copy(src, "sd:/");
res = fs_move_tree(src, "sd:/", NULL, NULL);
else {
char dst[256];
char dst[FS_PATH_MAX];
s_printf(dst, "sd:/%s", fno.fname);
res = file_copy(src, dst);
res = fs_move_file(src, dst);
}
if (res != FR_OK)
break;
@@ -1463,6 +1082,7 @@ int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
}
int res = perform_installation_steps(pack_variant, mode, &dbg);
fs_release_buffers();
if (log_is_enabled()) {
log_write("\n=== Ende: res=%d (%s) ===\n", (u32)res, fs_error_str(res));