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:
1 parent
c033b988ca
commit
664216fa11
6 files changed
+453
-784
No files matched your search
+293
-190
@@ -1,6 +1,10 @@
|
||||
/*
|
||||
* OmniNX Installer - Filesystem operations with file logging
|
||||
* Based on HATS Installer
|
||||
*
|
||||
* All tree walkers share one path buffer per call (no malloc per entry) and
|
||||
* visit every entry exactly once. Moving within the SD card uses f_rename and
|
||||
* only falls back to copying when a rename fails.
|
||||
*/
|
||||
|
||||
#include "fs.h"
|
||||
@@ -10,8 +14,8 @@
|
||||
#include <utils/sprintf.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define FS_BUFFER_SIZE 0x100000 // 1MB copy buffer
|
||||
#define LOG_BUFFER_SIZE 1024 // fits two 255-char paths plus text
|
||||
#define FS_BUFFER_SIZE 0x100000 // 1MB copy buffer, allocated once
|
||||
#define LOG_BUFFER_SIZE 1024 // fits two 255-char paths plus text
|
||||
#define LOG_SYNC_EVERY 16
|
||||
|
||||
// Log file handle
|
||||
@@ -20,6 +24,9 @@ static bool log_enabled = false;
|
||||
static u32 log_lines;
|
||||
static char log_buf[LOG_BUFFER_SIZE];
|
||||
|
||||
// Shared copy buffer (lazy, released with fs_release_buffers)
|
||||
static u8 *copy_buf;
|
||||
|
||||
// Initialize log file
|
||||
void log_init(const char *path) {
|
||||
int res = f_open(&log_file, path, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
@@ -61,6 +68,13 @@ bool log_is_enabled(void) {
|
||||
return log_enabled;
|
||||
}
|
||||
|
||||
void fs_release_buffers(void) {
|
||||
if (copy_buf) {
|
||||
free(copy_buf);
|
||||
copy_buf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert FatFS error code to string
|
||||
const char *fs_error_str(int err) {
|
||||
switch (err) {
|
||||
@@ -88,270 +102,359 @@ const char *fs_error_str(int err) {
|
||||
}
|
||||
}
|
||||
|
||||
// Combine two paths
|
||||
static char *combine_paths(const char *base, const char *add) {
|
||||
size_t base_len = strlen(base);
|
||||
size_t add_len = strlen(add);
|
||||
char *result = malloc(base_len + add_len + 2);
|
||||
/* ---- path helpers ---------------------------------------------------- */
|
||||
|
||||
if (!result) return NULL;
|
||||
|
||||
if (base_len > 0 && base[base_len - 1] == '/') {
|
||||
s_printf(result, "%s%s", base, add);
|
||||
} else {
|
||||
s_printf(result, "%s/%s", base, add);
|
||||
}
|
||||
|
||||
return result;
|
||||
// Append "/name" to path (len = current length). Returns new length or 0 if too long.
|
||||
static size_t path_push(char *path, size_t len, const char *name) {
|
||||
size_t nlen = strlen(name);
|
||||
size_t slash = (len > 0 && path[len - 1] != '/') ? 1 : 0;
|
||||
if (len + slash + nlen + 1 > FS_PATH_MAX)
|
||||
return 0;
|
||||
if (slash)
|
||||
path[len++] = '/';
|
||||
memcpy(path + len, name, nlen + 1);
|
||||
return len + nlen;
|
||||
}
|
||||
|
||||
static size_t path_set(char *path, const char *src) {
|
||||
size_t len = strlen(src);
|
||||
if (len + 1 > FS_PATH_MAX)
|
||||
return 0;
|
||||
memcpy(path, src, len + 1);
|
||||
// Normalise "sd:/foo/" -> "sd:/foo" (keep "sd:/")
|
||||
while (len > 4 && path[len - 1] == '/')
|
||||
path[--len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
static const char *path_basename(const char *path) {
|
||||
const char *slash = strrchr(path, '/');
|
||||
return slash ? slash + 1 : path;
|
||||
}
|
||||
|
||||
static bool is_dot_entry(const char *name) {
|
||||
return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
|
||||
}
|
||||
|
||||
static void clear_readonly(const char *path, BYTE attr) {
|
||||
if (attr & AM_RDO)
|
||||
f_chmod(path, attr & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
/* ---- single file ----------------------------------------------------- */
|
||||
|
||||
// Copy a single file with logging
|
||||
int file_copy(const char *src, const char *dst) {
|
||||
FIL fin, fout;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("COPY: %s -> %s\n", src, dst);
|
||||
|
||||
res = f_open(&fin, src, FA_READ | FA_OPEN_EXISTING);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR open src: %s\n", fs_error_str(res));
|
||||
log_write("COPY %s: open src %s\n", src, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
f_stat(src, &fno);
|
||||
u64 file_size = f_size(&fin);
|
||||
log_write(" Size: %d bytes\n", (u32)file_size);
|
||||
if (f_stat(src, &fno) != FR_OK)
|
||||
fno.fattrib = 0;
|
||||
u64 remaining = f_size(&fin);
|
||||
|
||||
res = f_open(&fout, dst, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
if (res != FR_OK) {
|
||||
f_close(&fin);
|
||||
log_write(" ERROR open dst: %s\n", fs_error_str(res));
|
||||
log_write("COPY %s: open dst %s\n", dst, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
u8 *buf = malloc(FS_BUFFER_SIZE);
|
||||
if (!buf) {
|
||||
if (!copy_buf)
|
||||
copy_buf = malloc(FS_BUFFER_SIZE);
|
||||
if (!copy_buf) {
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
log_write(" ERROR: Out of memory for buffer\n");
|
||||
log_write("COPY %s: out of memory\n", src);
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
u64 remaining = file_size;
|
||||
UINT br, bw;
|
||||
|
||||
while (remaining > 0) {
|
||||
UINT to_copy = (remaining > FS_BUFFER_SIZE) ? FS_BUFFER_SIZE : (UINT)remaining;
|
||||
|
||||
res = f_read(&fin, buf, to_copy, &br);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR read: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (br != to_copy) {
|
||||
log_write(" ERROR: Read %d bytes, expected %d\n", br, to_copy);
|
||||
res = f_read(&fin, copy_buf, to_copy, &br);
|
||||
if (res == FR_OK && br != to_copy)
|
||||
res = FR_DISK_ERR;
|
||||
if (res != FR_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
res = f_write(&fout, buf, to_copy, &bw);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR write: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (bw != to_copy) {
|
||||
log_write(" ERROR: Wrote %d bytes, expected %d\n", bw, to_copy);
|
||||
res = f_write(&fout, copy_buf, to_copy, &bw);
|
||||
if (res == FR_OK && bw != to_copy)
|
||||
res = FR_DISK_ERR;
|
||||
if (res != FR_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
remaining -= to_copy;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
f_close(&fin);
|
||||
f_close(&fout);
|
||||
|
||||
if (res == FR_OK) {
|
||||
if (res == FR_OK)
|
||||
f_chmod(dst, fno.fattrib, 0x3A);
|
||||
log_write(" OK\n");
|
||||
}
|
||||
|
||||
log_write("COPY %s -> %s: %s\n", src, dst, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively delete a folder with logging
|
||||
int folder_delete(const char *path) {
|
||||
DIR dir;
|
||||
// Move a single file; replaces dst. Falls back to copy + delete if rename fails.
|
||||
int fs_move_file(const char *src, const char *dst) {
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("DELETE: %s\n", path);
|
||||
|
||||
res = f_opendir(&dir, path);
|
||||
if (res != FR_OK) {
|
||||
// Maybe it's a file, try to delete it
|
||||
log_write(" Not a dir, trying as file...\n");
|
||||
res = f_unlink(path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR unlink: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK (file deleted)\n");
|
||||
if (f_stat(dst, &fno) == FR_OK) {
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
log_write("MOVE %s: destination is a folder\n", dst);
|
||||
return FR_DENIED;
|
||||
}
|
||||
return res;
|
||||
clear_readonly(dst, fno.fattrib);
|
||||
f_unlink(dst);
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
int res = f_rename(src, dst);
|
||||
if (res == FR_OK) {
|
||||
log_write("MOVE %s -> %s\n", src, dst);
|
||||
return FR_OK;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
res = file_copy(src, dst);
|
||||
if (res == FR_OK)
|
||||
f_unlink(src);
|
||||
return res;
|
||||
}
|
||||
|
||||
char *full_path = combine_paths(path, fno.fname);
|
||||
if (!full_path) {
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
/* ---- trees ----------------------------------------------------------- */
|
||||
|
||||
static int delete_tree_rec(char *path, size_t len, fs_progress_cb cb, void *ctx) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
|
||||
int res = f_opendir(&dir, path);
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
|
||||
while ((res = f_readdir(&dir, &fno)) == FR_OK && fno.fname[0]) {
|
||||
if (is_dot_entry(fno.fname))
|
||||
continue;
|
||||
size_t sub = path_push(path, len, fno.fname);
|
||||
if (!sub) {
|
||||
res = FR_INVALID_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_delete(full_path);
|
||||
res = delete_tree_rec(path, sub, cb, ctx);
|
||||
if (res == FR_OK) {
|
||||
clear_readonly(path, fno.fattrib);
|
||||
res = f_unlink(path);
|
||||
}
|
||||
} else {
|
||||
file_count++;
|
||||
log_write(" DEL: %s\n", fno.fname);
|
||||
|
||||
// Clear read-only attribute if set (common with some CFW packs)
|
||||
if (fno.fattrib & AM_RDO) {
|
||||
f_chmod(full_path, fno.fattrib & ~AM_RDO, AM_RDO);
|
||||
}
|
||||
|
||||
res = f_unlink(full_path);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR: %s\n", fs_error_str(res));
|
||||
}
|
||||
clear_readonly(path, fno.fattrib);
|
||||
res = f_unlink(path);
|
||||
}
|
||||
log_write(" DEL %s: %s\n", path, fs_error_str(res));
|
||||
path[len] = '\0';
|
||||
|
||||
free(full_path);
|
||||
if (res != FR_OK) break;
|
||||
if (res != FR_OK)
|
||||
break;
|
||||
if (cb)
|
||||
cb(ctx);
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
if (res == FR_OK || res == FR_NO_FILE) {
|
||||
log_write(" Removing dir: %s (%d files, %d subdirs)\n", path, file_count, dir_count);
|
||||
|
||||
// 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);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR rmdir: %s\n", fs_error_str(res));
|
||||
} else {
|
||||
log_write(" OK\n");
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively copy a folder with logging
|
||||
int folder_copy(const char *src, const char *dst) {
|
||||
// Delete a file or a whole folder. A missing path counts as success.
|
||||
int fs_delete_tree(const char *target, fs_progress_cb cb, void *ctx) {
|
||||
char path[FS_PATH_MAX];
|
||||
FILINFO fno;
|
||||
size_t len = path_set(path, target);
|
||||
if (!len)
|
||||
return FR_INVALID_NAME;
|
||||
|
||||
int res = f_stat(path, &fno);
|
||||
if (res == FR_NO_FILE || res == FR_NO_PATH)
|
||||
return FR_OK;
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
res = delete_tree_rec(path, len, cb, ctx);
|
||||
if (res != FR_OK) {
|
||||
log_write("DELETE %s: %s\n", target, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
}
|
||||
clear_readonly(path, fno.fattrib);
|
||||
res = f_unlink(path);
|
||||
log_write("DELETE %s: %s\n", target, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
int folder_delete(const char *path) {
|
||||
return fs_delete_tree(path, NULL, NULL);
|
||||
}
|
||||
|
||||
static int copy_tree_rec(char *src, size_t slen, char *dst, size_t dlen) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
int res;
|
||||
|
||||
log_write("FOLDER COPY: %s -> %s\n", src, dst);
|
||||
int res = f_mkdir(dst);
|
||||
if (res != FR_OK && res != FR_EXIST)
|
||||
return res;
|
||||
|
||||
res = f_opendir(&dir, src);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR opendir src: %s\n", fs_error_str(res));
|
||||
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
|
||||
char *dst_path = combine_paths(dst, folder_name);
|
||||
if (!dst_path) {
|
||||
f_closedir(&dir);
|
||||
return FR_NOT_ENOUGH_CORE;
|
||||
}
|
||||
|
||||
log_write(" Creating: %s\n", dst_path);
|
||||
|
||||
res = f_mkdir(dst_path);
|
||||
if (res == FR_EXIST) {
|
||||
log_write(" (already exists)\n");
|
||||
res = FR_OK;
|
||||
} else if (res != FR_OK) {
|
||||
log_write(" ERROR mkdir: %s\n", fs_error_str(res));
|
||||
f_closedir(&dir);
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
int file_count = 0;
|
||||
int dir_count = 0;
|
||||
|
||||
// Copy contents
|
||||
while (1) {
|
||||
res = f_readdir(&dir, &fno);
|
||||
if (res != FR_OK) {
|
||||
log_write(" ERROR readdir: %s\n", fs_error_str(res));
|
||||
break;
|
||||
}
|
||||
if (fno.fname[0] == 0) break; // End of directory
|
||||
|
||||
char *src_full = combine_paths(src, fno.fname);
|
||||
char *dst_full = combine_paths(dst_path, fno.fname);
|
||||
|
||||
if (!src_full || !dst_full) {
|
||||
if (src_full) free(src_full);
|
||||
if (dst_full) free(dst_full);
|
||||
res = FR_NOT_ENOUGH_CORE;
|
||||
while ((res = f_readdir(&dir, &fno)) == FR_OK && fno.fname[0]) {
|
||||
if (is_dot_entry(fno.fname))
|
||||
continue;
|
||||
size_t s = path_push(src, slen, fno.fname);
|
||||
size_t d = path_push(dst, dlen, fno.fname);
|
||||
if (!s || !d) {
|
||||
res = FR_INVALID_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
dir_count++;
|
||||
res = folder_copy(src_full, dst_path);
|
||||
} else {
|
||||
file_count++;
|
||||
res = file_copy(src_full, dst_full);
|
||||
}
|
||||
|
||||
free(src_full);
|
||||
free(dst_full);
|
||||
|
||||
if (res != FR_OK) break;
|
||||
if (fno.fattrib & AM_DIR)
|
||||
res = copy_tree_rec(src, s, dst, d);
|
||||
else
|
||||
res = file_copy(src, dst);
|
||||
src[slen] = '\0';
|
||||
dst[dlen] = '\0';
|
||||
if (res != FR_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
|
||||
// Copy folder attributes
|
||||
if (res == FR_OK) {
|
||||
FILINFO src_info;
|
||||
if (f_stat(src, &src_info) == FR_OK) {
|
||||
f_chmod(dst_path, src_info.fattrib, 0x3A);
|
||||
}
|
||||
log_write(" Done: %d files, %d subdirs\n", file_count, dir_count);
|
||||
}
|
||||
|
||||
free(dst_path);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Recursively copy folder src into dst_parent (creates dst_parent/<name of src>)
|
||||
int folder_copy(const char *src_dir, const char *dst_parent) {
|
||||
char src[FS_PATH_MAX];
|
||||
char dst[FS_PATH_MAX];
|
||||
size_t slen = path_set(src, src_dir);
|
||||
size_t dlen = path_set(dst, dst_parent);
|
||||
if (!slen || !dlen || !(dlen = path_push(dst, dlen, path_basename(src))))
|
||||
return FR_INVALID_NAME;
|
||||
|
||||
int res = copy_tree_rec(src, slen, dst, dlen);
|
||||
log_write("COPY TREE %s -> %s: %s\n", src_dir, dst, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
static int move_tree_rec(char *src, size_t slen, char *dst, size_t dlen, fs_progress_cb cb, void *ctx) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
FILINFO dst_info;
|
||||
|
||||
int res = f_opendir(&dir, src);
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
|
||||
while ((res = f_readdir(&dir, &fno)) == FR_OK && fno.fname[0]) {
|
||||
if (is_dot_entry(fno.fname))
|
||||
continue;
|
||||
size_t s = path_push(src, slen, fno.fname);
|
||||
size_t d = path_push(dst, dlen, fno.fname);
|
||||
if (!s || !d) {
|
||||
res = FR_INVALID_NAME;
|
||||
break;
|
||||
}
|
||||
|
||||
bool dst_exists = f_stat(dst, &dst_info) == FR_OK;
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
if (!dst_exists) {
|
||||
// Whole subtree moves in one step
|
||||
res = f_rename(src, dst);
|
||||
if (res != FR_OK)
|
||||
res = copy_tree_rec(src, s, dst, d);
|
||||
log_write("MOVE DIR %s -> %s: %s\n", src, dst, fs_error_str(res));
|
||||
} else if (dst_info.fattrib & AM_DIR) {
|
||||
res = move_tree_rec(src, s, dst, d, cb, ctx);
|
||||
} else {
|
||||
log_write("MOVE %s: destination is a file\n", dst);
|
||||
res = FR_DENIED;
|
||||
}
|
||||
} else {
|
||||
res = fs_move_file(src, dst);
|
||||
}
|
||||
src[slen] = '\0';
|
||||
dst[dlen] = '\0';
|
||||
|
||||
if (res != FR_OK)
|
||||
break;
|
||||
if (cb)
|
||||
cb(ctx);
|
||||
}
|
||||
f_closedir(&dir);
|
||||
return res;
|
||||
}
|
||||
|
||||
// Move folder src_dir into dst_parent (dst_parent/<name>), merging with an existing folder.
|
||||
int fs_move_tree(const char *src_dir, const char *dst_parent, fs_progress_cb cb, void *ctx) {
|
||||
char src[FS_PATH_MAX];
|
||||
char dst[FS_PATH_MAX];
|
||||
FILINFO fno;
|
||||
size_t slen = path_set(src, src_dir);
|
||||
size_t dlen = path_set(dst, dst_parent);
|
||||
if (!slen || !dlen || !(dlen = path_push(dst, dlen, path_basename(src))))
|
||||
return FR_INVALID_NAME;
|
||||
|
||||
int res = f_stat(dst, &fno);
|
||||
if (res == FR_NO_FILE) {
|
||||
// Target missing: one rename moves everything
|
||||
res = f_rename(src, dst);
|
||||
log_write("MOVE DIR %s -> %s: %s\n", src, dst, fs_error_str(res));
|
||||
if (res == FR_OK)
|
||||
return FR_OK;
|
||||
res = copy_tree_rec(src, slen, dst, dlen);
|
||||
log_write("COPY TREE %s -> %s: %s\n", src, dst, fs_error_str(res));
|
||||
return res;
|
||||
}
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
if (!(fno.fattrib & AM_DIR))
|
||||
return FR_DENIED;
|
||||
|
||||
return move_tree_rec(src, slen, dst, dlen, cb, ctx);
|
||||
}
|
||||
|
||||
// Count entries below path, stopping once limit is reached (limit 0 = no limit)
|
||||
static u32 count_rec(char *path, size_t len, u32 count, u32 limit) {
|
||||
DIR dir;
|
||||
FILINFO fno;
|
||||
if (f_opendir(&dir, path) != FR_OK)
|
||||
return count;
|
||||
|
||||
while (f_readdir(&dir, &fno) == FR_OK && fno.fname[0]) {
|
||||
if (is_dot_entry(fno.fname))
|
||||
continue;
|
||||
count++;
|
||||
if (limit && count >= limit)
|
||||
break;
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
size_t sub = path_push(path, len, fno.fname);
|
||||
if (sub) {
|
||||
count = count_rec(path, sub, count, limit);
|
||||
path[len] = '\0';
|
||||
}
|
||||
if (limit && count >= limit)
|
||||
break;
|
||||
}
|
||||
}
|
||||
f_closedir(&dir);
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 fs_count_items(const char *target, u32 limit) {
|
||||
char path[FS_PATH_MAX];
|
||||
size_t len = path_set(path, target);
|
||||
return len ? count_rec(path, len, 0, limit) : 0;
|
||||
}
|
||||
+19
-3
@@ -6,15 +6,31 @@
|
||||
#pragma once
|
||||
#include <utils/types.h>
|
||||
|
||||
#define FS_PATH_MAX 512
|
||||
|
||||
// Called once per processed entry (file or folder) by the tree walkers.
|
||||
typedef void (*fs_progress_cb)(void *ctx);
|
||||
|
||||
// Error code to string
|
||||
const char *fs_error_str(int err);
|
||||
|
||||
// File/folder operations - returns 0 on success
|
||||
// File/folder operations - return FR_OK (0) on success
|
||||
int file_copy(const char *src, const char *dst);
|
||||
int folder_copy(const char *src, const char *dst);
|
||||
int folder_copy(const char *src_dir, const char *dst_parent); // copies into dst_parent/<name>
|
||||
int folder_delete(const char *path);
|
||||
|
||||
// File logging
|
||||
// Delete a file or folder tree (missing path = FR_OK).
|
||||
int fs_delete_tree(const char *path, fs_progress_cb cb, void *ctx);
|
||||
// Move a file, replacing dst (rename, copy as fallback).
|
||||
int fs_move_file(const char *src, const char *dst);
|
||||
// Move folder src_dir into dst_parent/<name>, merging into an existing folder.
|
||||
int fs_move_tree(const char *src_dir, const char *dst_parent, fs_progress_cb cb, void *ctx);
|
||||
// Count entries below path; stops at limit (0 = count everything).
|
||||
u32 fs_count_items(const char *path, u32 limit);
|
||||
// Free the shared copy buffer.
|
||||
void fs_release_buffers(void);
|
||||
|
||||
// File logging (enabled via debug.ini log=1)
|
||||
void log_init(const char *path);
|
||||
void log_close(void);
|
||||
void log_write(const char *fmt, ...);
|
||||
|
||||
+101
-481
@@ -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));
|
||||
|
||||
+3
-7
@@ -39,11 +39,7 @@ void install_preserve_update_cleanup_end(void);
|
||||
void install_set_color(u32 color);
|
||||
void install_check_and_clear_screen_if_needed(void);
|
||||
bool install_path_exists(const char *path);
|
||||
int install_count_directory_items(const char *path);
|
||||
void install_combine_path(char *result, size_t size, const char *base, const char *add);
|
||||
int install_copy_staging_root_files(const char *staging, const char *dst_root);
|
||||
int delete_path_list(const char* paths[], const char* description);
|
||||
int install_move_staging_root_files(const char *staging, const char *dst_root);
|
||||
int install_move_tree(const char *src, const char *dst_parent, const char *display_name);
|
||||
int install_delete_tree(const char *path);
|
||||
int delete_path_lists_grouped(const char *folder_display_name, ...);
|
||||
int folder_delete_single_with_progress(const char *path, const char *display_name);
|
||||
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);
|
||||
int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name);
|
||||
+3
-25
@@ -29,7 +29,6 @@
|
||||
#define set_color install_set_color
|
||||
#define check_and_clear_screen_if_needed install_check_and_clear_screen_if_needed
|
||||
#define path_exists install_path_exists
|
||||
#define count_directory_items install_count_directory_items
|
||||
|
||||
// Clean mode: Backup user data
|
||||
int clean_mode_backup(void) {
|
||||
@@ -102,7 +101,7 @@ int clean_mode_restore(void) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" [OK] Wiederherstellung abgeschlossen\n");
|
||||
set_color(COLOR_WHITE);
|
||||
delete_path_list(clean_post_restore_dirs_to_delete, "switch/ (nach Restore)");
|
||||
delete_path_lists_grouped("switch/ (nach Restore)", clean_post_restore_dirs_to_delete, NULL);
|
||||
}
|
||||
cleanup_backup();
|
||||
return res;
|
||||
@@ -132,30 +131,9 @@ int cleanup_other_staging_directories(omninx_variant_t installed_variant) {
|
||||
gfx_printf("\nEntferne weiteren Installationsordner...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
int total = count_directory_items(staging);
|
||||
u32 start_x, start_y;
|
||||
gfx_con_getpos(&start_x, &start_y);
|
||||
|
||||
const char *folder_name = strrchr(staging, '/');
|
||||
if (folder_name) folder_name++;
|
||||
else folder_name = staging;
|
||||
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", folder_name, total);
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
int deleted = 0;
|
||||
int last_percent = -1;
|
||||
int res = folder_delete_progress_recursive(staging, &deleted, total, start_x, start_y, folder_name, &last_percent);
|
||||
|
||||
gfx_con_setpos(start_x, start_y);
|
||||
if (res == FR_OK) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", folder_name, deleted, total);
|
||||
set_color(COLOR_WHITE);
|
||||
} else {
|
||||
int res = install_delete_tree(staging);
|
||||
if (res != FR_OK) {
|
||||
set_color(COLOR_ORANGE);
|
||||
gfx_printf(" Loesche: %s - Fehlgeschlagen!\n", folder_name);
|
||||
gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res);
|
||||
set_color(COLOR_WHITE);
|
||||
}
|
||||
|
||||
+34
-78
@@ -28,7 +28,6 @@
|
||||
#define set_color install_set_color
|
||||
#define check_and_clear_screen_if_needed install_check_and_clear_screen_if_needed
|
||||
#define path_exists install_path_exists
|
||||
#define count_directory_items install_count_directory_items
|
||||
|
||||
// Update mode: Cleanup specific directories/files
|
||||
int update_mode_cleanup(omninx_variant_t variant) {
|
||||
@@ -73,11 +72,18 @@ int update_mode_cleanup(omninx_variant_t variant) {
|
||||
return FR_OK;
|
||||
}
|
||||
|
||||
// Update mode: Copy files from staging
|
||||
// Pack folders installed from staging (SaltySD only ships with OC).
|
||||
static const char *const pack_folders[] = {
|
||||
"atmosphere", "bootloader", "config", "switch", "themes",
|
||||
"warmboot_mariko", "hwtest", "SaltySD", "extras",
|
||||
};
|
||||
|
||||
// Update mode: Move files from staging (rename on the same SD card, copy only as fallback)
|
||||
int update_mode_install(omninx_variant_t variant) {
|
||||
int res;
|
||||
const char* staging = get_staging_path(variant);
|
||||
char src_path[256];
|
||||
char src_path[FS_PATH_MAX];
|
||||
char label[32];
|
||||
|
||||
if (!staging) {
|
||||
return FR_INVALID_PARAMETER;
|
||||
@@ -86,63 +92,35 @@ int update_mode_install(omninx_variant_t variant) {
|
||||
check_and_clear_screen_if_needed();
|
||||
|
||||
set_color(COLOR_YELLOW);
|
||||
gfx_printf("Dateien werden kopiert...\n");
|
||||
gfx_printf("Dateien werden installiert...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
s_printf(src_path, "%s/atmosphere", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "atmosphere/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/bootloader", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "bootloader/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/config", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "config/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/switch", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/themes", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "themes/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/warmboot_mariko", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
s_printf(src_path, "%s/hwtest", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "hwtest/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
if (variant == VARIANT_OC) {
|
||||
s_printf(src_path, "%s/SaltySD", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "SaltySD/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
for (u32 i = 0; i < sizeof(pack_folders) / sizeof(pack_folders[0]); i++) {
|
||||
if (!strcmp(pack_folders[i], "SaltySD") && variant != VARIANT_OC)
|
||||
continue;
|
||||
s_printf(src_path, "%s/%s", staging, pack_folders[i]);
|
||||
s_printf(label, "%s/", pack_folders[i]);
|
||||
res = install_move_tree(src_path, "sd:/", label);
|
||||
if (res != FR_OK && res != FR_NO_FILE)
|
||||
return res;
|
||||
}
|
||||
|
||||
s_printf(src_path, "%s/extras", staging);
|
||||
res = folder_copy_with_progress_v2(src_path, "sd:/", "extras/");
|
||||
if (res != FR_OK && res != FR_NO_FILE) return res;
|
||||
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf(" Kopiere Root-Dateien...\n");
|
||||
gfx_printf(" Installiere Root-Dateien...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
res = install_copy_staging_root_files(staging, "sd:/");
|
||||
res = install_move_staging_root_files(staging, "sd:/");
|
||||
if (res != FR_OK)
|
||||
return res;
|
||||
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" Kopie abgeschlossen!\n");
|
||||
gfx_printf(" Installation der Dateien abgeschlossen!\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
return FR_OK;
|
||||
}
|
||||
|
||||
// Remove staging directory after installation
|
||||
// Remove staging directory after installation (mostly empty folders after the move)
|
||||
int cleanup_staging_directory(omninx_variant_t pack_variant) {
|
||||
const char* staging = get_staging_path(pack_variant);
|
||||
if (!staging) {
|
||||
@@ -151,40 +129,18 @@ int cleanup_staging_directory(omninx_variant_t pack_variant) {
|
||||
|
||||
check_and_clear_screen_if_needed();
|
||||
|
||||
if (path_exists(staging)) {
|
||||
set_color(COLOR_YELLOW);
|
||||
gfx_printf("\nEntferne Installationsordner...\n");
|
||||
if (!path_exists(staging))
|
||||
return FR_OK;
|
||||
|
||||
set_color(COLOR_YELLOW);
|
||||
gfx_printf("\nEntferne Installationsordner...\n");
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
int res = install_delete_tree(staging);
|
||||
if (res != FR_OK) {
|
||||
set_color(COLOR_ORANGE);
|
||||
gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res);
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
int total = count_directory_items(staging);
|
||||
u32 start_x, start_y;
|
||||
gfx_con_getpos(&start_x, &start_y);
|
||||
|
||||
const char *folder_name = strrchr(staging, '/');
|
||||
if (folder_name) folder_name++;
|
||||
else folder_name = staging;
|
||||
|
||||
set_color(COLOR_CYAN);
|
||||
gfx_printf(" Loesche: %s [ 0%%] (0/%d)", folder_name, total);
|
||||
set_color(COLOR_WHITE);
|
||||
|
||||
int deleted = 0;
|
||||
int last_percent = -1;
|
||||
int res = folder_delete_progress_recursive(staging, &deleted, total, start_x, start_y, folder_name, &last_percent);
|
||||
|
||||
gfx_con_setpos(start_x, start_y);
|
||||
if (res == FR_OK) {
|
||||
set_color(COLOR_GREEN);
|
||||
gfx_printf(" Loesche: %s [100%%] (%d/%d) - Fertig!\n", folder_name, deleted, total);
|
||||
set_color(COLOR_WHITE);
|
||||
} else {
|
||||
set_color(COLOR_ORANGE);
|
||||
gfx_printf(" Loesche: %s - Fehlgeschlagen!\n", folder_name);
|
||||
gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res);
|
||||
set_color(COLOR_WHITE);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return FR_OK;
|
||||
return res;
|
||||
}
|
||||
Reference in new issue
Block a user