fix(peer): settle current-protocol local state cleanup

The follow-up backlog had drifted into three settled peer/runtime issues: the
legacy game-list fallback contradicted the one-wire-version policy, the Tauri
shell still re-derived local install state from disk after peer snapshots, and
`Availability::Downloading` existed even though active operations are already
reported through a separate operation table.

Remove the legacy `AnnounceGames` request and fallback service. Discovery now
ignores peers that do not advertise the current protocol and a peer id, and
library changes are sent through the current delta path only. This keeps the
runtime aligned with the documented current-build-only interoperability model.

Make peer `LocalGamesUpdated` snapshots authoritative for local fields in the
Tauri database. The GUI-side catalog still owns static metadata such as names,
sizes, and descriptions, but downloaded, installed, local version, and
availability now come from the peer runtime instead of a second whole-library
filesystem scan. Snapshot reconciliation also pins the missing-begin and
missing-finish lifecycle cases in tests.

Collapse availability back to the settled `Ready` and `LocalOnly` states.
Aggregation now counts only `Ready` peers as download sources, and the frontend
no longer carries a dead `Downloading` enum value.

The core peer also exposes the small non-GUI hooks needed by scripted callers:
startup options for state and mDNS, a local-ready event, direct connection, peer
snapshots, and an explicit post-download install policy. Those hooks reuse the
same current protocol path and do not add compatibility shims.

Test Plan:
- `git diff --check`
- `just fmt`
- `just clippy`
- `just test`

Refs: BACKLOG.md, FINDINGS.md, IMPL_DECISIONS.md
This commit is contained in:
2026-05-16 18:32:24 +02:00
parent 6242d64583
commit e711cf3454
23 changed files with 531 additions and 723 deletions
+2 -29
View File
@@ -33,9 +33,6 @@ pub fn read_version_from_ini(game_dir: &Path) -> eyre::Result<Option<String>> {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Availability {
Ready,
/// Wire-compatible transitional state. Local library summaries currently
/// suppress active operations instead of advertising this value.
Downloading,
#[default]
LocalOnly,
}
@@ -49,11 +46,6 @@ impl Availability {
Self::LocalOnly
}
}
#[must_use]
pub fn is_downloaded(&self) -> bool {
matches!(self, Self::Ready)
}
}
/// A game
@@ -95,8 +87,7 @@ pub struct Game {
}
impl Game {
/// Sets sentinel-derived download state and collapses any non-ready
/// availability, including `Downloading`, back to `LocalOnly`.
/// Sets sentinel-derived download state and matching availability.
pub fn set_downloaded(&mut self, downloaded: bool) {
self.downloaded = downloaded;
self.availability = Availability::from_downloaded(downloaded);
@@ -106,10 +97,8 @@ impl Game {
pub fn normalized_availability(&self) -> Availability {
if self.downloaded {
Availability::Ready
} else if self.availability.is_downloaded() {
Availability::LocalOnly
} else {
self.availability.clone()
Availability::LocalOnly
}
}
}
@@ -201,14 +190,6 @@ impl GameDB {
games.sort_by(|a, b| a.name.cmp(&b.name));
games
}
pub fn set_all_uninstalled(&mut self) {
for game in self.games.values_mut() {
game.set_downloaded(false);
game.installed = false;
game.local_version = None;
}
}
}
impl Default for GameDB {
@@ -326,14 +307,6 @@ mod tests {
game.availability = Availability::Ready;
assert_eq!(game.normalized_availability(), Availability::LocalOnly);
game.availability = Availability::Downloading;
game.set_downloaded(false);
assert!(!game.downloaded);
assert_eq!(game.availability, Availability::LocalOnly);
game.availability = Availability::Downloading;
assert_eq!(game.normalized_availability(), Availability::Downloading);
game.downloaded = true;
assert_eq!(game.normalized_availability(), Availability::Ready);
}