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:
@@ -65,11 +65,14 @@ When the UI asks to download a game:
|
||||
archives. The selected peers are queried via
|
||||
`request_game_details_from_peer`, and their file manifests are merged inside
|
||||
`PeerGameDB`.
|
||||
2. Once the UI receives `PeerEvent::GotGameFiles`, it forwards the selected file
|
||||
list back with `PeerCommand::DownloadGameFiles`.
|
||||
3. `download_game_files` starts a version-sentinel transaction, parks any old
|
||||
`version.ini` as `.version.ini.discarded`, prepares non-sentinel files, emits
|
||||
`PeerEvent::DownloadGameFilesBegin`, and builds a per-peer plan
|
||||
2. Once the UI receives `PeerEvent::GotGameFiles`, it requests the download by
|
||||
game ID only. The peer core validates every source manifest before consensus,
|
||||
chooses the complete authoritative description, and constructs one
|
||||
root-confined `ValidatedDownloadManifest` before any filesystem mutation.
|
||||
3. `download_game_files` recovers any earlier attempt, parks an old
|
||||
`version.ini` as `.version.ini.discarded`, and durably journals the exact old
|
||||
and proposed downloader-owned file sets before preparing non-sentinel files.
|
||||
It then emits `PeerEvent::DownloadGameFilesBegin` and builds a per-peer plan
|
||||
(`build_peer_plans`) that round-robins file chunks across the available peers
|
||||
that advertise the latest version.
|
||||
4. Each plan is executed in its own task (`download_from_peer`). Chunk requests
|
||||
@@ -80,13 +83,20 @@ When the UI asks to download a game:
|
||||
5. `DownloadProgressTracker` samples byte counters, transfer speed, and the
|
||||
number of unique peers that are actively streaming chunks. The Tauri UI sees
|
||||
those values together through the regular download-progress event.
|
||||
6. `version.ini` chunks are buffered in memory and committed last via
|
||||
`.version.ini.tmp` followed by an atomic rename. Failures are accumulated and
|
||||
retried (up to `MAX_RETRY_COUNT`) via `retry_failed_chunks`; failed downloads
|
||||
sweep `.version.ini.tmp` and `.version.ini.discarded` without restoring the
|
||||
previous sentinel. Cancelled downloads also discard the peer-owned download
|
||||
payload while preserving `local/` and install transaction metadata.
|
||||
7. After a successful sentinel commit, `PeerEvent::DownloadGameFilesFinished` is
|
||||
6. `version.ini` chunks are buffered in memory. After transfer, paths owned by
|
||||
the previous successful download but absent from the new manifest are
|
||||
removed. Payload files and their directories are synced before the new
|
||||
sentinel is committed last via `.version.ini.tmp` followed by an atomic
|
||||
rename. A sentinel rename whose directory sync fails leaves ownership pending
|
||||
for recovery instead of being reported as a durable success. Transfer
|
||||
failures are accumulated and retried (up to `MAX_RETRY_COUNT`) via
|
||||
`retry_failed_chunks`.
|
||||
7. Failure, cancellation, and startup recovery use the journal to remove only
|
||||
exact downloader-owned files. Unknown user files, `local/`, and install
|
||||
transaction state are preserved. A regular `version.ini` beside a pending
|
||||
journal proves that the final rename landed; otherwise recovery aborts the
|
||||
incomplete payload.
|
||||
8. After a successful sentinel commit, `PeerEvent::DownloadGameFilesFinished` is
|
||||
emitted and the peer auto-runs the install transaction.
|
||||
|
||||
### Streamed Install Pipeline
|
||||
@@ -129,6 +139,11 @@ that are direct children of the configured game directory, refuses installed or
|
||||
in-flight roots, and deletes the whole game root only after finding a regular
|
||||
root-level `version.ini` sentinel.
|
||||
|
||||
Download provenance is stored separately at
|
||||
`games/<game_id>/download_ownership.json` in the peer state directory. It is
|
||||
bound to the canonical configured games directory so switching library roots
|
||||
cannot make an old record authorize deletion in a different tree.
|
||||
|
||||
Legacy launcher-owned files in game directories are migrated by a dedicated
|
||||
pre-start phase. Normal install, recovery, scan, and transfer paths use only the
|
||||
configured state directory for launcher-owned metadata.
|
||||
|
||||
Reference in New Issue
Block a user