feat(peer): remove downloaded game files safely
Downloaded but uninstalled games can still occupy significant disk space. Add a separate removal path for that state instead of overloading uninstall, which is reserved for deleting only `local/` installs. The peer runtime now exposes `RemoveDownloadedGame` with matching lifecycle and active-operation events. The filesystem delete is intentionally strict: the id must be a catalog game and a single path component, the target must be a direct child of the configured game directory, the root must not be a symlink, it must have a regular root-level `version.ini`, and it must not contain `local/`, `.local.installing/`, or `.local.backup/`. Only then do we recursively remove the game root. The Tauri bridge exposes this as `remove_downloaded_game`, the frontend shows a matching danger action only for downloaded-but-uninstalled games, and a confirmation dialog warns that re-downloading can take a long time. Test Plan: - git diff --check - just fmt - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just test - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just clippy - RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just build Refs: user redesign nitpick about removing downloaded uninstalled games
This commit is contained in:
@@ -18,7 +18,9 @@ const CHECKING_PEERS_TIMEOUT_MS = 5000;
|
||||
|
||||
interface PendingPatch {
|
||||
install_status?: InstallStatus;
|
||||
downloaded?: boolean;
|
||||
installed?: boolean;
|
||||
local_version?: string | null;
|
||||
status_message?: string;
|
||||
status_level?: StatusLevel | undefined;
|
||||
clearStatus?: boolean;
|
||||
@@ -27,7 +29,9 @@ interface PendingPatch {
|
||||
const applyPatch = (game: Game, patch: PendingPatch): Game => {
|
||||
let next: Game = { ...game };
|
||||
if (patch.install_status !== undefined) next.install_status = patch.install_status;
|
||||
if (patch.downloaded !== undefined) next.downloaded = patch.downloaded;
|
||||
if (patch.installed !== undefined) next.installed = patch.installed;
|
||||
if (patch.local_version !== undefined) next.local_version = patch.local_version ?? undefined;
|
||||
if (patch.clearStatus) {
|
||||
next.status_message = undefined;
|
||||
next.status_level = undefined;
|
||||
@@ -41,7 +45,7 @@ const applyPatch = (game: Game, patch: PendingPatch): Game => {
|
||||
|
||||
/**
|
||||
* Owns the games list and reflects every backend event (download/install/
|
||||
* uninstall lifecycle, peer count) into local React state. Returns a
|
||||
* uninstall/remove lifecycle, peer count) into local React state. Returns a
|
||||
* fire-and-forget `markChecking` helper so action calls can immediately show a
|
||||
* "Checking peers…" state with an automatic fall-back if the backend never
|
||||
* emits a follow-up event.
|
||||
@@ -227,6 +231,30 @@ export const useGames = (rescanGameDir: () => void): UseGamesResult => {
|
||||
handleErrorEvent(e.payload as string, 'Uninstall failed. Please try again.');
|
||||
}));
|
||||
|
||||
unlisteners.push(await listen('game-remove-download-begin', (e) => {
|
||||
updateById(e.payload as string, {
|
||||
install_status: InstallStatus.Removing,
|
||||
clearStatus: true,
|
||||
});
|
||||
}));
|
||||
|
||||
unlisteners.push(await listen('game-remove-download-finished', (e) => {
|
||||
updateById(e.payload as string, {
|
||||
install_status: InstallStatus.NotInstalled,
|
||||
downloaded: false,
|
||||
installed: false,
|
||||
local_version: null,
|
||||
clearStatus: true,
|
||||
});
|
||||
rescanRef.current();
|
||||
}));
|
||||
|
||||
unlisteners.push(await listen('game-remove-download-failed', (e) => {
|
||||
handleErrorEvent(e.payload as string, 'Remove failed. Please try again.', {
|
||||
triggerRescan: true,
|
||||
});
|
||||
}));
|
||||
|
||||
unlisteners.push(await listen('peer-count-updated', (e) => {
|
||||
setTotalPeerCount(e.payload as number);
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user