feat(peer): journal download file ownership

Track the exact regular files owned by each completed and in-flight peer
download instead of sweeping every non-reserved path after cancellation. Bind
the record to the canonical games directory, publish pending ownership before
payload mutation, and use the final version.ini rename as the recovery commit
point.

Make replacement, cancellation, and startup recovery preserve unknown files
and install state while removing stale or partial downloader-owned bytes. Add a
new-format baseline so legacy discarded sentinels cannot make partially
modified payloads ready, sync payload and journal state in transaction order,
and serialize startup recovery against operation admission.

Document ambiguous legacy target adoption, portable alias transitions, and the
other ownership tradeoffs in the refactor decision log.

Test Plan:
- `just clippy` -- passed
- `just test` -- passed (182 peer-core tests plus the full workspace)
- `just fmt` -- Rust, TOML, and Prettier formatting completed; the command then
  stopped on 40 pre-existing rumdl findings in unrelated Markdown content
- `git diff --cached --check` -- passed
This commit is contained in:
ddidderr committed 2026-08-09 18:44:40 +02:00
1 parent a1013b028d
commit 62cd9306bd
14 files changed
+1478 -265

No files matched your search

+15 -3
View File
@@ -17,7 +17,12 @@ use crate::{
InstallOperation,
PeerEvent,
context::{Ctx, OperationGuard, OperationKind},
download::{ValidatedDownloadManifest, download_game_files, validate_protocol_v7_descriptions},
download::{
ValidatedDownloadManifest,
clear_download_ownership,
download_game_files,
validate_protocol_v7_descriptions,
},
events,
install,
local_games::{
@@ -392,6 +397,7 @@ pub async fn handle_download_game_files_command(
let result = download_game_files(
manifest,
ctx_clone.state_dir.as_ref(),
peer_whitelist,
file_peer_map,
tx_notify_ui_clone.clone(),
@@ -1091,7 +1097,11 @@ async fn run_remove_downloaded_operation(
ctx.active_operations.clone(),
tx_notify_ui.clone(),
);
let result = install::remove_downloaded(&game_dir, &id).await;
let result = async {
install::remove_downloaded(&game_dir, &id).await?;
clear_download_ownership(ctx.state_dir.as_ref(), &id).await
}
.await;
match result {
Ok(()) => {
@@ -1387,8 +1397,10 @@ async fn load_local_library_with_policy(
event_policy: LocalLibraryEventPolicy,
) -> eyre::Result<()> {
let game_dir = { ctx.game_dir.read().await.clone() };
let active_ids = active_operation_ids(ctx).await;
let active_operations = ctx.active_operations.read().await;
let active_ids = active_operations.keys().cloned().collect();
install::recover_on_startup(&game_dir, ctx.state_dir.as_ref(), &active_ids).await?;
drop(active_operations);
scan_and_announce_local_library(ctx, tx_notify_ui, &game_dir, event_policy).await
}