fix(peer): preserve untracked download files

Reject exact manifest destinations that are not covered by the last committed ownership set before creating a baseline or parking version.ini. Align Windows device-name validation with the confined filesystem backend and keep cleanup capability-relative.

Replace recursive downloaded-game removal with an empty ownership generation. The operation now removes only proven-owned files and the sentinel, preserves unknown files and directories, and remains recoverable and idempotent across crashes.

Test Plan:
- just clippy
- just test
- just fmt (Rust, TOML, and Prettier completed; rumdl still reports 39 pre-existing issues)
This commit is contained in:
ddidderr committed 2026-08-09 19:46:10 +02:00
1 parent 691176e1d5
commit 08b1cb5c1d
10 files changed
+630 -301

No files matched your search

+12 -7
View File
@@ -19,8 +19,8 @@ use crate::{
context::{Ctx, OperationGuard, OperationKind},
download::{
ValidatedDownloadManifest,
clear_download_ownership,
download_game_files,
remove_downloaded_payload,
validate_protocol_v7_descriptions,
},
events,
@@ -1097,11 +1097,7 @@ async fn run_remove_downloaded_operation(
ctx.active_operations.clone(),
tx_notify_ui.clone(),
);
let result = async {
install::remove_downloaded(&game_dir, &id).await?;
clear_download_ownership(ctx.state_dir.as_ref(), &id).await
}
.await;
let result = remove_downloaded_payload(&game_dir, ctx.state_dir.as_ref(), &id).await;
match result {
Ok(()) => {
@@ -2288,6 +2284,13 @@ mod tests {
write_file(&root.join("game.eti"), b"archive");
let ctx = test_ctx(temp.path().to_path_buf());
crate::download::seed_download_ownership_for_test(
ctx.state_dir.as_ref(),
temp.path(),
"game",
&["game.eti"],
)
.await;
let (tx, mut rx) = mpsc::unbounded_channel();
let catalog = ctx.catalog.read().await.clone();
let scan = scan_local_library(temp.path(), ctx.state_dir.as_ref(), &catalog)
@@ -2311,7 +2314,9 @@ mod tests {
recv_event(&mut rx).await,
PeerEvent::RemoveDownloadedGameFinished { id } if id == "game"
));
assert!(!root.exists());
assert!(root.is_dir());
assert!(!root.join("version.ini").exists());
assert!(!root.join("game.eti").exists());
assert!(ctx.active_operations.read().await.is_empty());
}