Files

944 lines
33 KiB
C++
Raw Permalink Normal View History

2025-12-15 00:18:06 -08:00
#include "actions.h"
2025-08-30 02:48:10 +10:00
2025-12-14 21:02:33 -08:00
#include "main.h"
#include "xips.h"
2025-12-15 00:18:06 -08:00
#include "xpatchlibUtils.h"
#include "unzipLIBUtils.h"
2025-11-24 02:19:47 -08:00
2025-09-02 14:26:19 +10:00
// The "toast window" behavior:
// 1) First B press shows "Press B again to cancel" and arms a window.
// 2) If the second B happens while the toast is still visible, we cancel.
// 3) If toast expires before second B, the next B just re-arms the window.
2025-08-31 15:46:31 +10:00
struct CopyProgCtx {
2025-12-06 01:32:28 -08:00
ULONGLONG base; // bytes completed from previous items (offset)
bool canceled; // set when user confirms cancel
2025-12-06 01:32:28 -08:00
bool confirmArmed; // true after the first B press
DWORD confirmUntil; // snapshot of when the toast will expire (informational)
bool prevB; // for rising-edge detection of B
2025-08-31 15:46:31 +10:00
};
2025-12-06 01:32:28 -08:00
// Progress callback used by file copy/move loops
static bool CopyProgThunk(LONGLONG done, LONGLONG total, const char* label, void* user) {
CopyProgCtx* ctx = (CopyProgCtx*)user;
2025-12-15 00:18:06 -08:00
FileBrowserApp& app = FileBrowserApp::Get();
2025-12-15 00:18:06 -08:00
if (done == LLONG_MIN) done = app.m_prog.done;
else if (done < 0) {
done = _abs64(done);
2025-12-15 00:18:06 -08:00
done += app.m_prog.done;
done += ctx->base;
}
else done += ctx->base;
2025-12-06 01:32:28 -08:00
total = (total == LLONG_MIN) ? 0 : total;
2025-09-02 14:26:19 +10:00
// Poll controller to read B presses while copying
XBInput_GetInput();
const XBGAMEPAD& pad = g_Gamepads[0];
const bool bNow = (pad.bAnalogButtons[XINPUT_GAMEPAD_B] > 30);
const DWORD now = GetTickCount();
2025-12-15 00:18:06 -08:00
app.UpdateProgress(done, total, label);
2025-09-02 14:26:19 +10:00
// Rising-edge B?
if (bNow && !ctx->prevB){
2025-12-15 00:18:06 -08:00
const DWORD statusUntil = app.StatusUntilMs();
const bool toastAlive = (now < statusUntil);
if (ctx->confirmArmed && toastAlive){
2025-09-02 14:26:19 +10:00
// Second B while the toast is still up => cancel
ctx->canceled = true;
SetLastError(ERROR_OPERATION_ABORTED);
ctx->prevB = bNow;
return false; // abort copy/move
} else {
2025-09-02 14:26:19 +10:00
// First B: arm and show "press B again" toast
ctx->confirmArmed = true;
2025-12-15 00:18:06 -08:00
app.SetStatus("Press \x81 again to cancel");
ctx->confirmUntil = app.StatusUntilMs(); // snapshot (optional)
}
}
2025-09-02 14:26:19 +10:00
// Disarm if toast has expired
2025-12-15 00:18:06 -08:00
if (ctx->confirmArmed && now >= app.StatusUntilMs()) ctx->confirmArmed = false;
ctx->prevB = bNow;
2025-12-06 01:32:28 -08:00
return true; // continue
2025-08-31 15:46:31 +10:00
}
2025-12-06 01:32:28 -08:00
// Return 1 if both paths are on the same drive letter (case-insensitive)
2025-08-31 16:23:57 +10:00
static int SameDriveLetter(const char* a, const char* b){
if (!a || !b || !a[0] || !b[0]) return 0;
return toupper((unsigned char)a[0]) == toupper((unsigned char)b[0]);
}
2025-12-06 01:32:28 -08:00
// Ensure trailing backslash on non-empty strings
2025-08-31 16:23:57 +10:00
static void NormalizeSlashEndLocal(char* s, size_t cap){
size_t n = s ? strlen(s) : 0;
if (n && s[n-1] != '\\' && n+1 < cap){ s[n] = '\\'; s[n+1] = 0; }
}
2025-09-02 14:26:19 +10:00
// Case-insensitive: is `child` a subpath of `parent`?
2025-08-31 16:23:57 +10:00
static int IsSubPathCaseI(const char* parent, const char* child){
char p[512], c[512];
_snprintf(p, sizeof(p), "%s", parent ? parent : ""); p[sizeof(p)-1] = 0;
_snprintf(c, sizeof(c), "%s", child ? child : ""); c[sizeof(c)-1] = 0;
NormalizeSlashEndLocal(p, sizeof(p));
NormalizeSlashEndLocal(c, sizeof(c));
return _strnicmp(p, c, (int)strlen(p)) == 0; // child starts with parent?
}
2025-12-06 01:32:28 -08:00
// Basename (pointer into input string), e.g., "E:\A\B\C" -> "C"
2025-08-31 16:23:57 +10:00
static const char* BaseNameOf(const char* path){
const char* s = path ? strrchr(path, '\\') : 0;
return s ? (s+1) : path;
}
2025-09-02 14:26:19 +10:00
// Collects selected sources for copy/move/delete:
// - If any items are marked, returns all marked (excluding "..").
// - Else returns the single current selection (if not "..").
// - Writes full paths (dir + name) into 'out'.
2025-08-31 15:46:31 +10:00
static void GatherMarkedOrSelectedFullPaths(const Pane& src, std::vector<std::string>& out) {
if (src.mode != 1 || src.items.empty()) return;
bool any = false;
for (size_t i=0; i<src.items.size(); ++i) {
const Item& it = src.items[i];
if (it.marked && !it.isUpEntry) {
char full[512]; JoinPath(full, sizeof(full), src.curPath, it.name);
out.push_back(full);
any = true;
}
}
if (!any) { // fall back to single selection
const Item& sel = src.items[src.sel];
if (!sel.isUpEntry) {
char full[512]; JoinPath(full, sizeof(full), src.curPath, sel.name);
out.push_back(full);
}
}
}
2025-09-02 14:26:19 +10:00
// Main dispatcher: runs the action the user picked from the context menu.
2025-12-15 00:18:06 -08:00
void Actions::Execute(Action act) {
FileBrowserApp& app = FileBrowserApp::Get();
2025-08-30 02:48:10 +10:00
Pane& src = app.m_pane[app.m_active];
2025-11-24 02:19:47 -08:00
Pane& dst = app.m_pane[1 - app.m_active];
2025-08-30 02:48:10 +10:00
const Item* sel = NULL;
2025-11-24 02:19:47 -08:00
const Item* sel2 = NULL;
2025-09-02 17:45:58 +10:00
if (!src.items.empty()) sel = &src.items[src.sel];
2025-11-24 02:19:47 -08:00
if (!dst.items.empty()) sel2 = &dst.items[dst.sel];
2025-09-02 17:45:58 +10:00
// Full path of selection (if any). Also supports drive-list selection.
char srcFull[512] = "";
2025-11-24 02:19:47 -08:00
char dstFull[512] = "";
const char* ext = NULL;
const char* ext2 = NULL;
2025-09-02 17:45:58 +10:00
if (sel) {
if (src.mode == 1 && !sel->isUpEntry) {
2025-11-24 02:19:47 -08:00
if (!sel->isDir) ext = GetExtension(sel->name);
2025-09-02 17:45:58 +10:00
// Normal directory listing: dir + name
JoinPath(srcFull, sizeof(srcFull), src.curPath, sel->name);
srcFull[sizeof(srcFull)-1] = 0;
} else if (src.mode == 0 && sel->isDir && !sel->isUpEntry) {
// Drive list: item name is already something like "E:\"
_snprintf(srcFull, sizeof(srcFull), "%s", sel->name);
srcFull[sizeof(srcFull)-1] = 0;
NormalizeDirA(srcFull); // ensure trailing slash
}
}
2025-11-24 02:19:47 -08:00
if (sel2) {
if (dst.mode == 1 && !sel2->isUpEntry) {
if (!sel2->isDir) ext2 = GetExtension(sel2->name);
// Normal directory listing: dir + name
JoinPath(dstFull, sizeof(dstFull), dst.curPath, sel2->name);
dstFull[sizeof(dstFull)-1] = 0;
} else if (dst.mode == 0 && sel2->isDir && !sel2->isUpEntry) {
// Drive list: item name is already something like "E:\"
_snprintf(dstFull, sizeof(dstFull), "%s", sel2->name);
dstFull[sizeof(dstFull)-1] = 0;
NormalizeDirA(dstFull); // ensure trailing slash
}
}
2025-08-30 02:48:10 +10:00
2025-12-06 01:32:28 -08:00
switch (act) {
2025-11-27 03:32:49 -08:00
// ---- Open / Enter / Launch --------------------------------------------
2025-08-30 02:48:10 +10:00
case ACT_OPEN:
2025-11-27 03:32:49 -08:00
{
if (sel) {
if (sel->isUpEntry) { app.UpOne(src); }
else if (sel->isDir) { app.EnterSelection(src); }
2025-11-27 03:32:49 -08:00
else if (HasXbeExt(sel->name)) {
char full[512]; JoinPath(full, sizeof(full), src.curPath, sel->name);
if (!LaunchXbeA(full)) app.SetStatusLastErr("Launch failed");
}
}
break;
2025-11-27 03:32:49 -08:00
}
2025-08-30 02:48:10 +10:00
2025-09-02 14:26:19 +10:00
// ---- Copy -----------------------------------------------------------------
2025-11-27 03:32:49 -08:00
case ACT_COPY:
{
if (src.mode != 1) { app.SetStatus("Open a folder"); break; }
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// Resolve destination (other pane preferred)
char dstDir[512];
2025-12-15 01:57:09 -08:00
if (!app.ResolveDestDir(dstDir, sizeof(dstDir))) {
app.SetStatus("Pick a destination"); break;
}
if (IsDPath(dstDir)) {
app.SetStatus("Cannot copy to DVD-ROM:\\");
break;
}
2025-11-27 03:32:49 -08:00
NormalizeDirA(dstDir);
if (!CanWriteHereA(dstDir)) { app.SetStatusLastErr("Dest not writable"); break; }
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// Gather sources
std::vector<std::string> srcs;
GatherMarkedOrSelectedFullPaths(src, srcs);
if (srcs.empty()) { app.SetStatus("Nothing to copy"); break; }
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// Compute total bytes for progress bar
ULONGLONG total = 0;
for (size_t i = 0; i < srcs.size(); ++i) total += DirSizeRecursiveA(srcs[i].c_str());
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// --- preflight free-space check on destination (kept from your version) ---
{
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(dstDir, freeB, totB);
if (total > freeB) {
char need[64], have[64];
FormatSize(total, need, sizeof(need));
FormatSize(freeB, have, sizeof(have));
app.SetStatus("Not enough space: need %s, have %s", need, have);
break;
}
}
// --- end preflight ---
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// Begin progress + set callback
app.BeginProgress(total, srcs[0].c_str(), "Copying...");
2025-12-15 00:18:06 -08:00
CopyProgCtx ctx = { 0, false, false, 0, false };
2025-11-27 03:32:49 -08:00
SetCopyProgressCallback(CopyProgThunk, &ctx);
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
ULONGLONG base = 0; // cumulative bytes completed
char lastDstTop[512] = { 0 }; // for cancel cleanup
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
// NEW: track results so the final toast reflects reality
size_t copiedOk = 0, failed = 0, skipped = 0;
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
for (size_t i = 0; i < srcs.size(); ++i) {
const char* sp = srcs[i].c_str();
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// Compute top-level destination path (dstDir\basename(sp)) for cleanup
const char* bn = BaseNameOf(sp);
JoinPath(lastDstTop, sizeof(lastDstTop), dstDir, bn);
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
ULONGLONG thisSize = DirSizeRecursiveA(sp);
ctx.base = base;
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// --- prevent copying a folder into its own subfolder (or itself) ---
if (IsSubPathCaseI(sp, lastDstTop)) {
app.SetStatus("Cannot copy a folder into its own subfolder");
++skipped;
continue; // skip this item, proceed to next
}
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
// --- per-item free-space check (extra safety) ---
{
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(lastDstTop, freeB, totB); // any path on dest volume is fine
if (thisSize > freeB) {
char need[64], have[64];
FormatSize(thisSize, need, sizeof(need));
FormatSize(freeB, have, sizeof(have));
app.SetStatus("Not enough space for %s: need %s, have %s", bn, need, have);
break; // stop the whole batch (change to 'continue;' to try remaining items)
}
}
// --- end per-item check ---
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
if (!CopyRecursiveWithProgressA(sp, dstDir, total)) {
if (ctx.canceled) {
// Remove partial destination of the current item, then stop
DeleteRecursiveA(lastDstTop);
break;
}
// Non-cancel failure
++failed;
}
else {
base += thisSize;
++copiedOk;
}
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// End progress and clear callback
SetCopyProgressCallback(NULL, NULL);
app.EndProgress();
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
if (ctx.canceled) {
app.SetStatus("Copy canceled (%u done, %u skipped, %u failed)",
(unsigned)copiedOk, (unsigned)skipped, (unsigned)failed);
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
break;
}
2025-09-02 17:03:03 +10:00
2025-11-27 03:32:49 -08:00
// If we copied into the currently displayed dest folder, refresh it
Pane& dstp = app.m_pane[1 - app.m_active];
if (dstp.mode == 1 && _stricmp(dstp.curPath, dstDir) == 0) ListDirectory(dstp.curPath, dstp.items);
2025-09-02 17:03:03 +10:00
2025-11-27 03:32:49 -08:00
// Clear marks and refresh both panes
for (size_t i = 0; i < src.items.size(); ++i) src.items[i].marked = false;
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// Final toast that reflects what actually happened
if (failed == 0 && skipped == 0) {
app.SetStatus("Copied %u item(s)", (unsigned)copiedOk);
}
else {
app.SetStatus("Copied %u, %u skipped, %u failed",
(unsigned)copiedOk, (unsigned)skipped, (unsigned)failed);
}
break;
}
2025-08-31 15:46:31 +10:00
2025-09-02 14:26:19 +10:00
// ---- Move -----------------------------------------------------------------
2025-11-27 03:32:49 -08:00
case ACT_MOVE:
{
if (src.mode != 1) { app.SetStatus("Open a folder"); break; }
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
char dstDir[512];
2025-12-15 01:57:09 -08:00
if (!app.ResolveDestDir(dstDir, sizeof(dstDir))) {
app.SetStatus("Pick a destination");
break;
}
if (IsDPath(dstDir)) {
app.SetStatus("Cannot move to DVD-ROM:\\");
break;
}
2025-11-27 03:32:49 -08:00
NormalizeDirA(dstDir);
if (!CanWriteHereA(dstDir)) { app.SetStatusLastErr("Dest not writable"); break; }
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
std::vector<std::string> srcs;
GatherMarkedOrSelectedFullPaths(src, srcs);
if (srcs.empty()) { app.SetStatus("Nothing to move"); break; }
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
// Total bytes for progress
ULONGLONG total = 0;
for (size_t i = 0; i < srcs.size(); ++i) total += DirSizeRecursiveA(srcs[i].c_str());
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
// --- preflight space only when cross-volume (move will copy+delete) ---
{
const bool sameVol = SameDriveLetter(src.curPath, dstDir) != 0;
if (!sameVol) {
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(dstDir, freeB, totB);
if (total > freeB) {
char need[64], have[64];
FormatSize(total, need, sizeof(need));
FormatSize(freeB, have, sizeof(have));
app.SetStatus("Not enough space: need %s, have %s", need, have);
break;
}
}
}
// --- end preflight ---
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
app.BeginProgress(total, srcs[0].c_str(), "Moving...");
2025-12-15 00:18:06 -08:00
CopyProgCtx ctx = { 0, false, false, 0, false };
2025-11-27 03:32:49 -08:00
SetCopyProgressCallback(CopyProgThunk, &ctx);
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
size_t movedOk = 0, failed = 0, skipped = 0; // NEW: track results
ULONGLONG base = 0;
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
for (size_t i = 0; i < srcs.size(); ++i) {
const char* sp = srcs[i].c_str();
ULONGLONG thisSize = DirSizeRecursiveA(sp);
ctx.base = base;
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// Destination top (dstDir\basename(sp))
const char* baseName = BaseNameOf(sp);
char dstTop[512]; JoinPath(dstTop, sizeof(dstTop), dstDir, baseName);
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
// --- NEW: prevent moving a folder into its own subfolder (or onto itself)
if (IsSubPathCaseI(sp, dstTop)) {
app.SetStatus("Cannot move a folder into its own subfolder");
++skipped;
continue; // skip this item and go on
}
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
BOOL doneThis = FALSE;
2025-08-31 16:23:57 +10:00
2025-11-27 03:32:49 -08:00
// --- NEW: per-item free-space check only if not a fast same-volume rename
const BOOL canFastRename =
SameDriveLetter(sp, dstDir) && !IsSubPathCaseI(sp, dstTop);
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
if (!canFastRename) {
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(dstTop, freeB, totB);
if (thisSize > freeB) {
char need[64], have[64];
FormatSize(thisSize, need, sizeof(need));
FormatSize(freeB, have, sizeof(have));
app.SetStatus("Not enough space to move %s: need %s, have %s", baseName, need, have);
break; // stop the whole batch (use 'continue;' if you prefer to try remaining items)
}
}
// --- end NEW ---
2025-09-02 17:45:58 +10:00
2025-11-27 03:32:49 -08:00
// Fast path: same drive and not moving into own subfolder -> MoveFileA
if (SameDriveLetter(sp, dstDir) && !IsSubPathCaseI(sp, dstTop)) {
if (MoveFileA(sp, dstTop)) {
doneThis = TRUE; // instant rename/move within volume
}
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// Fallback: copy -> delete original (only delete if copy succeeded)
if (!doneThis) {
if (!CopyRecursiveWithProgressA(sp, dstDir, total)) {
if (ctx.canceled) {
// Clean partial dest and stop
DeleteRecursiveA(dstTop);
break;
}
// Non-cancel failure
++failed;
}
else {
if (DeleteRecursiveA(sp)) {
doneThis = TRUE;
}
else {
// Optional: consider removing dstTop if source delete failed
++failed;
}
}
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
if (doneThis) ++movedOk;
base += thisSize;
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
SetCopyProgressCallback(NULL, NULL);
app.EndProgress();
2025-11-27 03:32:49 -08:00
if (ctx.canceled) {
app.SetStatus("Move canceled (%u done, %u skipped, %u failed)",
(unsigned)movedOk, (unsigned)skipped, (unsigned)failed);
// On cancel we keep originals; clear marks and refresh UI
for (size_t i = 0; i < src.items.size(); ++i) src.items[i].marked = false;
if (src.mode == 1) ListDirectory(src.curPath, src.items);
{
Pane& dstp = app.m_pane[1 - app.m_active];
if (dstp.mode == 1 && _stricmp(dstp.curPath, dstDir) == 0) ListDirectory(dstp.curPath, dstp.items);
}
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
break;
}
2025-09-02 17:03:03 +10:00
2025-11-27 03:32:49 -08:00
// Normal completion: refresh both panes, clear marks
for (size_t i = 0; i < src.items.size(); ++i) src.items[i].marked = false;
if (src.mode == 1) ListDirectory(src.curPath, src.items);
{
Pane& dstp = app.m_pane[1 - app.m_active];
if (dstp.mode == 1 && _stricmp(dstp.curPath, dstDir) == 0) ListDirectory(dstp.curPath, dstp.items);
}
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
2025-11-27 03:32:49 -08:00
// NEW: accurate final toast
if (failed == 0 && skipped == 0) {
app.SetStatus("Moved %u item(s)", (unsigned)movedOk);
}
else {
app.SetStatus("Moved %u, %u skipped, %u failed",
(unsigned)movedOk, (unsigned)skipped, (unsigned)failed);
}
break;
}
2025-08-31 16:23:57 +10:00
2025-09-02 14:26:19 +10:00
// ---- Delete ---------------------------------------------------------------
2025-08-30 02:48:10 +10:00
case ACT_DELETE:
{
if (src.mode != 1) { app.SetStatus("Open a folder"); break; }
2025-08-31 15:46:31 +10:00
std::vector<std::string> srcs;
GatherMarkedOrSelectedFullPaths(src, srcs);
if (srcs.empty()) { app.SetStatus("Nothing to delete"); break; }
2025-08-31 15:46:31 +10:00
2025-11-27 03:32:49 -08:00
int ok = 0;
for (size_t i = 0; i < srcs.size(); ++i) if (DeleteRecursiveA(srcs[i].c_str())) ++ok;
2025-08-31 15:46:31 +10:00
2025-09-02 14:26:19 +10:00
// Clear marks, refresh, and toast result
2025-11-27 03:32:49 -08:00
for (size_t i = 0; i < src.items.size(); ++i) src.items[i].marked = false;
if (src.mode == 1) ListDirectory(src.curPath, src.items);
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
app.SetStatus("Deleted %d / %d", ok, (int)srcs.size());
break;
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// ---- Rename (opens modal OSK) ---------------------------------------------
2025-08-30 02:48:10 +10:00
case ACT_RENAME:
2025-11-27 03:32:49 -08:00
{
if (sel && src.mode == 1 && !sel->isUpEntry) {
2025-08-30 02:48:10 +10:00
app.BeginRename(src.curPath, sel->name);
2025-11-27 03:32:49 -08:00
}
else {
2025-08-30 02:48:10 +10:00
app.SetStatus("Open a folder and select an item");
}
break;
2025-11-27 03:32:49 -08:00
}
2025-08-30 02:48:10 +10:00
2025-09-02 14:26:19 +10:00
// ---- Make new folder ------------------------------------------------------
2025-08-30 02:48:10 +10:00
case ACT_MKDIR:
{
2025-11-27 03:32:49 -08:00
char baseDir[512] = { 0 };
2025-08-30 02:48:10 +10:00
if (src.mode == 1) {
_snprintf(baseDir, sizeof(baseDir), "%s", src.curPath);
2025-11-27 03:32:49 -08:00
}
else if (!src.items.empty()) {
2025-08-30 02:48:10 +10:00
const Item& di = src.items[src.sel];
if (di.isDir && !di.isUpEntry) {
2025-09-02 14:26:19 +10:00
_snprintf(baseDir, sizeof(baseDir), "%s", di.name); // drive root, e.g. "E:\"
2025-08-30 02:48:10 +10:00
}
}
2025-11-27 03:32:49 -08:00
baseDir[sizeof(baseDir) - 1] = 0;
2025-12-15 01:57:09 -08:00
if (!baseDir[0]) {
app.SetStatus("Open a folder or select a drive first");
break;
}
2025-08-30 02:48:10 +10:00
2025-12-15 01:57:09 -08:00
if (IsDPath(baseDir)) {
app.SetStatus("Cannot create on DVD-ROM:\\");
break;
2025-08-30 02:48:10 +10:00
}
NormalizeDirA(baseDir);
if (!CanWriteHereA(baseDir)) { app.SetStatusLastErr("Dest not writable"); break; }
2025-09-02 14:26:19 +10:00
// Auto-name NewFolder[/N] without clobbering existing names
2025-08-30 02:48:10 +10:00
char nameBuf[64];
char target[512];
int idx = 0;
2025-11-27 03:32:49 -08:00
for (;;) {
2025-08-30 02:48:10 +10:00
if (idx == 0) _snprintf(nameBuf, sizeof(nameBuf), "NewFolder");
else _snprintf(nameBuf, sizeof(nameBuf), "NewFolder%d", idx);
2025-11-27 03:32:49 -08:00
nameBuf[sizeof(nameBuf) - 1] = 0;
2025-08-30 02:48:10 +10:00
JoinPath(target, sizeof(target), baseDir, nameBuf);
if (!DirExistsA(target)) {
if (CreateDirectoryA(target, NULL)) {
app.SetStatus("Created %s", nameBuf);
2025-11-27 03:32:49 -08:00
}
else {
2025-08-30 02:48:10 +10:00
app.SetStatusLastErr("Create folder failed");
}
break;
}
2025-11-27 03:32:49 -08:00
if (++idx > 999) { app.SetStatus("Create folder failed (names exhausted)"); break; }
2025-08-30 02:48:10 +10:00
}
app.RefreshPane(app.m_pane[0]); app.RefreshPane(app.m_pane[1]);
2025-09-02 14:26:19 +10:00
// If we created inside the active folder, select the new dir
2025-08-30 02:48:10 +10:00
if (src.mode == 1 && _stricmp(src.curPath, baseDir) == 0) {
app.SelectItemInPane(src, nameBuf);
}
break;
}
2025-09-02 14:26:19 +10:00
// ---- Calculate size -------------------------------------------------------
2025-08-30 02:48:10 +10:00
case ACT_CALCSIZE:
2025-09-02 17:45:58 +10:00
{
if (!sel) break;
if (!srcFull[0]) {
// Try to derive from selection directly (covers any future cases)
char tmpPath[512] = "";
if (src.mode == 0 && sel->isDir && !sel->isUpEntry) {
_snprintf(tmpPath, sizeof(tmpPath), "%s", sel->name);
2025-11-27 03:32:49 -08:00
tmpPath[sizeof(tmpPath) - 1] = 0;
2025-09-02 17:45:58 +10:00
NormalizeDirA(tmpPath);
}
if (!tmpPath[0]) { app.SetStatus("Open a folder or select a drive"); break; }
ULONGLONG bytes = DirSizeRecursiveA(tmpPath);
char tmp[64]; FormatSize(bytes, tmp, sizeof(tmp));
app.SetStatus("%s", tmp);
break;
}
2025-09-02 17:03:03 +10:00
2025-09-02 17:45:58 +10:00
ULONGLONG bytes = DirSizeRecursiveA(srcFull);
char tmp[64]; FormatSize(bytes, tmp, sizeof(tmp));
app.SetStatus("%s", tmp);
break;
}
2025-08-30 02:48:10 +10:00
2025-11-27 03:32:49 -08:00
// ---- Go to root (or back to drive list) -----------------------------------
2025-08-30 02:48:10 +10:00
case ACT_GOROOT:
2025-11-27 03:32:49 -08:00
{
if (src.mode == 1) {
if (!IsDriveRoot(src.curPath)) {
2025-08-30 02:48:10 +10:00
char root[4] = { src.curPath[0], ':', '\\', 0 };
2025-11-27 03:32:49 -08:00
strncpy(src.curPath, root, sizeof(src.curPath) - 1);
2025-08-30 02:48:10 +10:00
src.curPath[3] = 0;
src.sel = 0; src.scroll = 0;
ListDirectory(src.curPath, src.items);
2025-11-27 03:32:49 -08:00
}
else {
2025-09-02 14:26:19 +10:00
// Already at root -> switch to drive list
2025-08-30 02:48:10 +10:00
src.mode = 0; src.curPath[0] = 0; src.sel = 0; src.scroll = 0;
BuildDriveItems(src.items);
}
2025-11-27 03:32:49 -08:00
}
else {
2025-09-02 14:26:19 +10:00
// In drive list: refresh it
2025-08-30 02:48:10 +10:00
BuildDriveItems(src.items);
}
break;
2025-11-27 03:32:49 -08:00
}
2025-08-30 02:48:10 +10:00
2025-09-02 14:26:19 +10:00
// ---- Marking operations ---------------------------------------------------
case ACT_MARK_ALL:
{
Pane& p = app.m_pane[app.m_active];
2025-11-27 03:32:49 -08:00
if (p.mode==1 && !p.items.empty()) {
int n=0;
2025-11-27 03:32:49 -08:00
for (size_t i=0; i<p.items.size(); ++i) {
if (!p.items[i].isUpEntry && !p.items[i].marked) {
p.items[i].marked = true; ++n;
}
}
if (n>0) app.SetStatus("Marked %d item%s", n, (n==1?"":"s"));
else app.SetStatus("All already marked");
}
break;
}
2025-08-31 15:46:31 +10:00
case ACT_INVERT_MARKS:
{
Pane& p = app.m_pane[app.m_active];
2025-11-27 03:32:49 -08:00
if (p.mode==1 && !p.items.empty()) {
int toggled=0;
2025-11-27 03:32:49 -08:00
for (size_t i=0;i<p.items.size(); ++i) {
if (!p.items[i].isUpEntry) {
p.items[i].marked = !p.items[i].marked;
++toggled;
}
}
app.SetStatus("Inverted marks (%d)", toggled);
}
break;
}
2025-08-31 15:46:31 +10:00
case ACT_CLEAR_MARKS:
{
Pane& p = app.m_pane[app.m_active];
2025-11-27 03:32:49 -08:00
if (p.mode==1 && !p.items.empty()) {
int cleared=0;
2025-11-27 03:32:49 -08:00
for (size_t i=0;i<p.items.size(); ++i) {
if (p.items[i].marked) {
p.items[i].marked=false;
++cleared;
}
}
app.SetStatus(cleared ? "Cleared marks (%d)" : "No marks", cleared);
}
break;
}
2025-08-31 15:46:31 +10:00
2025-09-02 14:26:19 +10:00
// ---- Format cache (X/Y/Z + clear E:\CACHE) --------------------------------
case ACT_FORMAT_CACHE:
{
2025-12-15 01:01:15 -08:00
app.SetStatus("Formatting cache partitions and directories...");
2025-12-15 00:35:06 -08:00
if (DeleteRecursiveA("HDD0-E:\\CACHE")) EnsureDirA("HDD0-E:\\CACHE");
if (DeleteRecursiveA("HDD1-E:\\CACHE")) EnsureDirA("HDD1-E:\\CACHE");
2025-12-15 01:01:15 -08:00
if (FormatCacheXYZ(0)) { // 0 => default 16KiB
2025-12-14 19:45:19 -08:00
app.SetStatus("Format cache failed");
break;
2025-09-02 14:26:19 +10:00
}
2025-12-14 19:45:19 -08:00
2025-09-02 14:26:19 +10:00
app.RefreshPane(app.m_pane[0]);
app.RefreshPane(app.m_pane[1]);
app.SetStatus("Formatted X/Y/Z and E:\\CACHE");
break;
}
2025-11-24 02:19:47 -08:00
2025-11-27 03:32:49 -08:00
// ---- Apply ips patch ------------------------------------------------------
2025-12-04 01:04:40 -08:00
case ACT_APPLYIPS:
{
if (ext && _stricmp(ext, "ips") == 0 && ext2 && _stricmp(ext2, "xbe") == 0) {
2025-11-24 02:19:47 -08:00
2025-11-27 03:32:49 -08:00
bool bkcr = false;
bool bkcrfail = false;
bool bkcrcanc = false;
bool ptchfail = false;
char dst[1024];
strcpy(dst, dstFull);
strcat(dst, ".bak");
while (true) {
// Resolve destination
char dstDir[512];
app.ResolveDestDir(dstDir, sizeof(dstDir));
NormalizeDirA(dstDir);
if (!CanWriteHereA(dstDir)) {
2025-12-04 01:04:40 -08:00
bkcrfail = true;
2025-11-27 03:32:49 -08:00
break;
}
ULONGLONG total = DirSizeRecursiveA(dstFull);
// --- preflight free-space check on destination ---
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(dstDir, freeB, totB);
if (total > freeB) {
bkcrfail = true;
break;
}
// --- end preflight ---
ULONGLONG base = 0; // cumulative bytes completed
char dstName[47];
strcpy(dstName, sel2->name);
strcat(dstName, ".bak");
// Begin progress + set callback
app.BeginProgress(total, dstName, "Creaking bak...");
2025-12-15 00:18:06 -08:00
CopyProgCtx ctx = { 0, false, false, 0, false };
2025-11-27 03:32:49 -08:00
SetCopyProgressCallback(CopyProgThunk, &ctx);
int cb = CreateBak(dstFull, false, UpdateBakProgress);
// End progress and clear callback
SetCopyProgressCallback(NULL, NULL);
app.EndProgress();
2025-11-27 03:32:49 -08:00
switch (cb) {
case E_NO_ERROR:
bkcr = true;
bkcrfail = false;
break;
case E_CANNOT_OVR:
bkcr = false;
bkcrfail = false;
break;
default:
bkcr = false;
bkcrfail = true;
}
if (bkcrfail) break;
if (ctx.canceled) {
bkcrcanc = true;
break;
}
if (ApplyIPS(srcFull, dstFull) != E_NO_ERROR) ptchfail = true;
break;
}//while
//Give result
if (bkcrcanc) {
remove(dst);
app.SetStatus("Bak canceled, patch skipped");
}
else if (bkcrfail) {
app.SetStatus("Bak failed, patch skipped");
}
else if (ptchfail) {
if (bkcr) {
if (RestoreBak(dst, true) == E_NO_ERROR) app.SetStatus("Patch failed, bak restored");
else app.SetStatus("Patch failed, bak restore failed");
}
else app.SetStatus("Failed to apply patch");
}
else if (!bkcr) {
app.SetStatus("Patch applied, bak skipped");
}
else {
app.SetStatus("Patch applied, bak created");
}
2025-12-04 01:04:40 -08:00
}
2025-11-27 03:32:49 -08:00
app.RefreshPane(app.m_pane[0]);
2025-11-24 02:19:47 -08:00
app.RefreshPane(app.m_pane[1]);
2025-12-04 01:04:40 -08:00
break;
}
2025-11-27 03:32:49 -08:00
// ---- Restore .bak file ----------------------------------------------------
case ACT_RESTOREBAK:
{
if (ext && _stricmp(ext, "bak") == 0) {
if (RestoreBak(srcFull, true) == E_NO_ERROR) app.SetStatus("Bak restored");
else app.SetStatus("Restore failed");
}
app.RefreshPane(app.m_pane[0]);
2025-11-24 02:19:47 -08:00
app.RefreshPane(app.m_pane[1]);
2025-11-27 03:32:49 -08:00
break;
}
2025-09-02 14:26:19 +10:00
2025-11-27 03:32:49 -08:00
// ---- Unzip zip file -------------------------------------------------------
2025-11-26 16:07:44 -08:00
case ACT_UNZIPHERE:
case ACT_UNZIPTO:
case ACT_UNZIPTOOTHER:
2025-11-27 03:32:49 -08:00
{
if (ext && _stricmp(ext, "zip") == 0) {
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
// Resolve destination
char dstDir[512];
2025-11-26 16:07:44 -08:00
if (act == ACT_UNZIPHERE) {
app.ResolveSrcDir(dstDir, sizeof(dstDir));
if (!CanWriteHereA(dstDir)) {
app.SetStatusLastErr("Dest not writable");
2025-11-26 16:07:44 -08:00
break;
}
}
else if (act == ACT_UNZIPTO) {
app.ResolveSrcDir(dstDir, sizeof(dstDir));
if (!CanWriteHereA(dstDir)) {
app.SetStatusLastErr("Dest not writable");
break;
}
char name[64];
strcpy(name, sel->name);
name[strlen(name) - 4] = '\0';
strcat(dstDir, name);
}
else if (act == ACT_UNZIPTOOTHER) {
2025-11-26 16:07:44 -08:00
if (!app.ResolveDestDir(dstDir, sizeof(dstDir))) {
app.SetStatus("Pick a destination");
break;
}
if (!CanWriteHereA(dstDir)) {
app.SetStatusLastErr("Dest not writable");
break;
}
2025-12-04 01:04:40 -08:00
}
2025-12-15 01:57:09 -08:00
if (IsDPath(dstDir)) {
app.SetStatus("Cannot unzip to DVD-ROM:\\");
2025-12-04 01:04:40 -08:00
break;
}
2025-11-27 03:32:49 -08:00
UNZIP* zip = new UNZIP;
2025-11-26 16:07:44 -08:00
2025-11-30 16:14:43 -08:00
if (zip->openZIP(srcFull, ZipFile_Open, ZipFile_Close, ZipFile_Read, ZipFile_Seek) != UNZ_OK) {
2025-11-27 03:32:49 -08:00
zip->closeZIP();
app.SetStatus("Bad zip file");
break;
}
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
int rc = zip->gotoFirstFile();
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
unz_file_info fi;
ULONGLONG total = 0;
2025-11-26 16:07:44 -08:00
char szName[512];
2025-11-27 03:32:49 -08:00
// Compute total bytes for progress bar
while (rc == UNZ_OK) {
rc = zip->getFileInfo(&fi, szName, 512, NULL, 0, NULL, 0);
if (rc == UNZ_OK) {
total += fi.uncompressed_size;
rc = zip->gotoNextFile();
}
}
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
if (total == 0) {
app.SetStatus("Bad zip file");
break;
}
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
// --- preflight free-space check on destination ---
ULONGLONG freeB = 0, totB = 0;
GetDriveFreeTotal(dstDir, freeB, totB);
if (total > freeB) {
zip->closeZIP();
char need[64], have[64];
FormatSize(total, need, sizeof(need));
FormatSize(freeB, have, sizeof(have));
app.SetStatus("Not enough space: need %s, have %s", need, have);
break;
}
// --- end preflight ---
2025-11-26 16:07:44 -08:00
2025-12-15 15:22:02 -08:00
EnsureDirA(dstDir);
2025-11-26 16:07:44 -08:00
if ((rc = zip->gotoFirstFile()) == UNZ_OK) {
rc = zip->getFileInfo(&fi, szName, 512, NULL, 0, NULL, 0);
}
// Begin progress + set callback
app.BeginProgress(total, szName, "Extracting...");
2025-12-15 00:18:06 -08:00
CopyProgCtx ctx = { 0, false, false, 0, false };
2025-11-26 16:07:44 -08:00
SetCopyProgressCallback(CopyProgThunk, &ctx);
2025-11-27 03:32:49 -08:00
size_t extractedOk = 0, skipped = 0;
while (rc == UNZ_OK) {
2025-11-26 16:07:44 -08:00
if (ctx.canceled) break;
if ((rc = ExtractCurrentFileWithProgress(zip, dstDir, true)) != UNZ_OK) skipped += 1;
2025-11-27 03:32:49 -08:00
else extractedOk += 1;
2025-11-26 16:07:44 -08:00
if ((rc = zip->gotoNextFile()) == UNZ_OK) {
rc = zip->getFileInfo(&fi, szName, 512, NULL, 0, NULL, 0);
}
if (CopyProgress::g_copyProgFn) {
if (!CopyProgress::g_copyProgFn(LLONG_MIN, NULL, szName, CopyProgress::g_copyProgUser)) {
2025-11-26 16:07:44 -08:00
break; // canceled
}
}
2025-11-27 03:32:49 -08:00
} //end while
2025-11-26 16:07:44 -08:00
2025-11-30 16:14:43 -08:00
FreeUnZipBuffer();
2025-11-26 16:07:44 -08:00
// End progress and clear callback
SetCopyProgressCallback(NULL, NULL);
app.EndProgress();
if (ctx.canceled) {
while (rc == UNZ_OK) {
++skipped;
rc = zip->gotoNextFile();
}
app.SetStatus("Extraction canceled (%u extracted, %u skipped)", (unsigned)extractedOk, (unsigned)skipped);
}
else {
// Final toast that reflects what actually happened
app.SetStatus("%u extracted, %u skipped", (unsigned)extractedOk, (unsigned)skipped);
}
2025-11-27 03:32:49 -08:00
zip->closeZIP();
2025-11-26 16:07:44 -08:00
2025-11-27 03:32:49 -08:00
}
2025-11-26 16:07:44 -08:00
app.RefreshPane(app.m_pane[0]);
app.RefreshPane(app.m_pane[1]);
2025-11-27 03:32:49 -08:00
break;
}
2025-11-26 16:07:44 -08:00
2025-09-02 14:26:19 +10:00
} // switch
2025-08-30 02:48:10 +10:00
}