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:
@@ -460,31 +460,24 @@ async fn update_state_from_event(shared: &SharedState, event: PeerEvent) -> (&'s
|
||||
"length": length,
|
||||
}),
|
||||
),
|
||||
PeerEvent::DownloadGameFilesFinished { id } => {
|
||||
("download-finished", json!({"game_id": id}))
|
||||
}
|
||||
PeerEvent::DownloadGameFilesFailed { id } => ("download-failed", json!({"game_id": id})),
|
||||
PeerEvent::DownloadGameFilesAllPeersGone { id } => {
|
||||
("download-peers-gone", json!({"game_id": id}))
|
||||
}
|
||||
PeerEvent::DownloadGameFilesFinished { id } => game_id_event("download-finished", id),
|
||||
PeerEvent::DownloadGameFilesFailed { id } => game_id_event("download-failed", id),
|
||||
PeerEvent::DownloadGameFilesAllPeersGone { id } => game_id_event("download-peers-gone", id),
|
||||
PeerEvent::InstallGameBegin { id, operation } => (
|
||||
"install-begin",
|
||||
json!({"game_id": id, "operation": install_operation_name(operation)}),
|
||||
),
|
||||
PeerEvent::InstallGameFinished { id } => ("install-finished", json!({"game_id": id})),
|
||||
PeerEvent::InstallGameFailed { id } => ("install-failed", json!({"game_id": id})),
|
||||
PeerEvent::UninstallGameBegin { id } => ("uninstall-begin", json!({"game_id": id})),
|
||||
PeerEvent::UninstallGameFinished { id } => ("uninstall-finished", json!({"game_id": id})),
|
||||
PeerEvent::UninstallGameFailed { id } => ("uninstall-failed", json!({"game_id": id})),
|
||||
PeerEvent::NoPeersHaveGame { id } => {
|
||||
shared
|
||||
.state
|
||||
.write()
|
||||
.await
|
||||
.unavailable_games
|
||||
.insert(id.clone());
|
||||
("no-peers-have-game", json!({"game_id": id}))
|
||||
PeerEvent::InstallGameFinished { id } => game_id_event("install-finished", id),
|
||||
PeerEvent::InstallGameFailed { id } => game_id_event("install-failed", id),
|
||||
PeerEvent::UninstallGameBegin { id } => game_id_event("uninstall-begin", id),
|
||||
PeerEvent::UninstallGameFinished { id } => game_id_event("uninstall-finished", id),
|
||||
PeerEvent::UninstallGameFailed { id } => game_id_event("uninstall-failed", id),
|
||||
PeerEvent::RemoveDownloadedGameBegin { id } => game_id_event("remove-download-begin", id),
|
||||
PeerEvent::RemoveDownloadedGameFinished { id } => {
|
||||
game_id_event("remove-download-finished", id)
|
||||
}
|
||||
PeerEvent::RemoveDownloadedGameFailed { id } => game_id_event("remove-download-failed", id),
|
||||
PeerEvent::NoPeersHaveGame { id } => no_peers_event(shared, id).await,
|
||||
PeerEvent::PeerConnected(addr) => ("peer-connected", peer_addr_json(addr)),
|
||||
PeerEvent::PeerDisconnected(addr) => ("peer-disconnected", peer_addr_json(addr)),
|
||||
PeerEvent::PeerDiscovered(addr) => ("peer-discovered", peer_addr_json(addr)),
|
||||
@@ -497,6 +490,20 @@ async fn update_state_from_event(shared: &SharedState, event: PeerEvent) -> (&'s
|
||||
}
|
||||
}
|
||||
|
||||
fn game_id_event(kind: &'static str, id: String) -> (&'static str, Value) {
|
||||
(kind, json!({"game_id": id}))
|
||||
}
|
||||
|
||||
async fn no_peers_event(shared: &SharedState, id: String) -> (&'static str, Value) {
|
||||
shared
|
||||
.state
|
||||
.write()
|
||||
.await
|
||||
.unavailable_games
|
||||
.insert(id.clone());
|
||||
game_id_event("no-peers-have-game", id)
|
||||
}
|
||||
|
||||
fn peer_addr_json(addr: SocketAddr) -> Value {
|
||||
json!({"addr": addr.to_string()})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user