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
+39 -5
View File
@@ -7,9 +7,7 @@ use std::{
time::{Duration, Instant},
};
#[cfg(test)]
use lanspread_db::db::Availability;
use lanspread_db::db::{Game, GameFileDescription};
use lanspread_db::db::{Availability, Game, GameFileDescription};
use lanspread_proto::{GameSummary, LibraryDelta, LibrarySnapshot};
use crate::library::compute_library_digest;
@@ -36,6 +34,18 @@ pub struct PeerInfo {
pub files: HashMap<String, Vec<GameFileDescription>>,
}
/// Immutable peer state suitable for CLI assertions and tests.
#[derive(Clone, Debug)]
pub struct PeerSnapshot {
pub peer_id: PeerId,
pub addr: SocketAddr,
pub library_rev: u64,
pub library_digest: u64,
pub features: Vec<String>,
pub game_count: usize,
pub games: Vec<GameSummary>,
}
/// Database tracking all discovered peers and their games.
#[derive(Debug)]
pub struct PeerGameDB {
@@ -363,6 +373,30 @@ impl PeerGameDB {
.collect()
}
/// Returns immutable snapshots for all known peers.
#[must_use]
pub fn peer_snapshots(&self) -> Vec<PeerSnapshot> {
let mut peers = self
.peers
.values()
.map(|peer| {
let mut games = peer.games.values().cloned().collect::<Vec<_>>();
games.sort_by(|a, b| a.id.cmp(&b.id));
PeerSnapshot {
peer_id: peer.peer_id.clone(),
addr: peer.addr,
library_rev: peer.library_rev,
library_digest: peer.library_digest,
features: peer.features.clone(),
game_count: games.len(),
games,
}
})
.collect::<Vec<_>>();
peers.sort_by(|a, b| a.peer_id.cmp(&b.peer_id));
peers
}
/// Checks if a peer is in the database.
#[must_use]
pub fn contains_peer(&self, peer_id: &PeerId) -> bool {
@@ -744,7 +778,7 @@ fn create_peer_whitelist(peer_scores: HashMap<SocketAddr, usize>) -> Vec<SocketA
}
fn game_is_ready(summary: &GameSummary) -> bool {
summary.availability.is_downloaded()
summary.availability == Availability::Ready
}
fn summary_to_game(summary: &GameSummary) -> Game {
@@ -762,7 +796,7 @@ fn summary_to_game(summary: &GameSummary) -> Game {
version: "1.0".to_string(),
genre: String::new(),
size: summary.size,
downloaded: summary.availability.is_downloaded(),
downloaded: game_is_ready(summary),
installed: summary.installed,
availability: summary.availability.clone(),
eti_game_version,