ChatGPT Codex 5.2 xhigh refactored > 45min
This commit is contained in:
@@ -8,16 +8,28 @@ use std::{
|
||||
};
|
||||
|
||||
use lanspread_db::db::{Game, GameFileDescription};
|
||||
use lanspread_proto::{GameSummary, LibraryDelta, LibrarySnapshot};
|
||||
|
||||
use crate::library::compute_library_digest;
|
||||
pub type PeerId = String;
|
||||
|
||||
/// Information about a discovered peer.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PeerInfo {
|
||||
/// Stable peer identifier.
|
||||
pub peer_id: PeerId,
|
||||
/// Network address of the peer.
|
||||
pub addr: SocketAddr,
|
||||
/// Last time we heard from this peer.
|
||||
pub last_seen: Instant,
|
||||
/// Latest library revision advertised by the peer.
|
||||
pub library_rev: u64,
|
||||
/// Digest of the peer library state.
|
||||
pub library_digest: u64,
|
||||
/// Capability flags advertised by the peer.
|
||||
pub features: Vec<String>,
|
||||
/// Games this peer has available, keyed by game ID.
|
||||
pub games: HashMap<String, Game>,
|
||||
pub games: HashMap<String, GameSummary>,
|
||||
/// File descriptions for each game, keyed by game ID.
|
||||
pub files: HashMap<String, Vec<GameFileDescription>>,
|
||||
}
|
||||
@@ -25,7 +37,14 @@ pub struct PeerInfo {
|
||||
/// Database tracking all discovered peers and their games.
|
||||
#[derive(Debug)]
|
||||
pub struct PeerGameDB {
|
||||
peers: HashMap<SocketAddr, PeerInfo>,
|
||||
peers: HashMap<PeerId, PeerInfo>,
|
||||
addr_index: HashMap<SocketAddr, PeerId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PeerUpsert {
|
||||
pub is_new: bool,
|
||||
pub addr_changed: bool,
|
||||
}
|
||||
|
||||
impl Default for PeerGameDB {
|
||||
@@ -39,59 +58,205 @@ impl PeerGameDB {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
peers: HashMap::new(),
|
||||
addr_index: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new peer to the database.
|
||||
pub fn add_peer(&mut self, addr: SocketAddr) {
|
||||
/// Adds a new peer to the database or updates its address.
|
||||
pub fn upsert_peer(&mut self, peer_id: PeerId, addr: SocketAddr) -> PeerUpsert {
|
||||
if let Some(existing_id) = self.addr_index.get(&addr).cloned()
|
||||
&& existing_id != peer_id
|
||||
{
|
||||
self.peers.remove(&existing_id);
|
||||
self.addr_index.remove(&addr);
|
||||
}
|
||||
|
||||
if let Some(peer) = self.peers.get_mut(&peer_id) {
|
||||
let addr_changed = peer.addr != addr;
|
||||
if addr_changed {
|
||||
self.addr_index.remove(&peer.addr);
|
||||
self.addr_index.insert(addr, peer_id.clone());
|
||||
peer.addr = addr;
|
||||
}
|
||||
peer.last_seen = Instant::now();
|
||||
return PeerUpsert {
|
||||
is_new: false,
|
||||
addr_changed,
|
||||
};
|
||||
}
|
||||
|
||||
let peer_info = PeerInfo {
|
||||
peer_id: peer_id.clone(),
|
||||
addr,
|
||||
last_seen: Instant::now(),
|
||||
library_rev: 0,
|
||||
library_digest: 0,
|
||||
features: Vec::new(),
|
||||
games: HashMap::new(),
|
||||
files: HashMap::new(),
|
||||
};
|
||||
self.peers.insert(addr, peer_info);
|
||||
self.peers.insert(peer_id.clone(), peer_info);
|
||||
self.addr_index.insert(addr, peer_id);
|
||||
log::info!("Added peer: {addr}");
|
||||
PeerUpsert {
|
||||
is_new: true,
|
||||
addr_changed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a peer from the database.
|
||||
pub fn remove_peer(&mut self, addr: &SocketAddr) -> Option<PeerInfo> {
|
||||
self.peers.remove(addr)
|
||||
/// Removes a peer from the database by id.
|
||||
pub fn remove_peer(&mut self, peer_id: &PeerId) -> Option<PeerInfo> {
|
||||
if let Some(peer) = self.peers.remove(peer_id) {
|
||||
self.addr_index.remove(&peer.addr);
|
||||
return Some(peer);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Removes a peer by address.
|
||||
pub fn remove_peer_by_addr(&mut self, addr: &SocketAddr) -> Option<PeerInfo> {
|
||||
let peer_id = self.addr_index.remove(addr)?;
|
||||
self.peers.remove(&peer_id)
|
||||
}
|
||||
|
||||
/// Returns the peer id for an address if known.
|
||||
#[must_use]
|
||||
pub fn peer_id_for_addr(&self, addr: &SocketAddr) -> Option<&PeerId> {
|
||||
self.addr_index.get(addr)
|
||||
}
|
||||
|
||||
/// Returns the library state for a peer if known.
|
||||
#[must_use]
|
||||
pub fn peer_library_state(&self, peer_id: &PeerId) -> Option<(u64, u64)> {
|
||||
self.peers
|
||||
.get(peer_id)
|
||||
.map(|peer| (peer.library_rev, peer.library_digest))
|
||||
}
|
||||
|
||||
/// Returns the number of games known for a peer.
|
||||
#[must_use]
|
||||
pub fn peer_game_count(&self, peer_id: &PeerId) -> usize {
|
||||
self.peers.get(peer_id).map_or(0, |peer| peer.games.len())
|
||||
}
|
||||
|
||||
/// Returns the feature list for a peer.
|
||||
#[must_use]
|
||||
pub fn peer_features(&self, peer_id: &PeerId) -> Vec<String> {
|
||||
self.peers
|
||||
.get(peer_id)
|
||||
.map(|peer| peer.features.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the address for a peer id.
|
||||
#[must_use]
|
||||
pub fn peer_addr(&self, peer_id: &PeerId) -> Option<SocketAddr> {
|
||||
self.peers.get(peer_id).map(|peer| peer.addr)
|
||||
}
|
||||
|
||||
/// Updates the games list for a peer.
|
||||
pub fn update_peer_games(&mut self, addr: SocketAddr, games: Vec<Game>) {
|
||||
if let Some(peer) = self.peers.get_mut(&addr) {
|
||||
pub fn update_peer_games(&mut self, peer_id: &PeerId, games: Vec<GameSummary>) {
|
||||
if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||
let mut map = HashMap::with_capacity(games.len());
|
||||
for game in games {
|
||||
map.insert(game.id.clone(), game);
|
||||
}
|
||||
peer.games = map;
|
||||
peer.last_seen = Instant::now();
|
||||
log::info!("Updated games for peer: {addr}");
|
||||
log::info!("Updated games for peer: {}", peer.addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the file descriptions for a specific game from a peer.
|
||||
pub fn update_peer_game_files(
|
||||
&mut self,
|
||||
addr: SocketAddr,
|
||||
peer_id: &PeerId,
|
||||
game_id: &str,
|
||||
files: Vec<GameFileDescription>,
|
||||
) {
|
||||
if let Some(peer) = self.peers.get_mut(&addr) {
|
||||
if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||
peer.files.insert(game_id.to_string(), files);
|
||||
peer.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the last seen timestamp for a peer.
|
||||
pub fn update_last_seen(&mut self, addr: &SocketAddr) {
|
||||
if let Some(peer) = self.peers.get_mut(addr) {
|
||||
pub fn update_last_seen(&mut self, peer_id: &PeerId) {
|
||||
if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||
peer.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the last seen timestamp for a peer by address.
|
||||
pub fn update_last_seen_by_addr(&mut self, addr: &SocketAddr) {
|
||||
if let Some(peer_id) = self.addr_index.get(addr).cloned()
|
||||
&& let Some(peer) = self.peers.get_mut(&peer_id)
|
||||
{
|
||||
peer.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the library metadata for a peer.
|
||||
pub fn update_peer_library(
|
||||
&mut self,
|
||||
peer_id: &PeerId,
|
||||
library_rev: u64,
|
||||
library_digest: u64,
|
||||
features: Vec<String>,
|
||||
) {
|
||||
if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||
peer.library_rev = library_rev;
|
||||
peer.library_digest = library_digest;
|
||||
peer.features = features;
|
||||
peer.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a full library snapshot for a peer.
|
||||
pub fn apply_library_snapshot(&mut self, peer_id: &PeerId, snapshot: LibrarySnapshot) {
|
||||
if let Some(peer) = self.peers.get_mut(peer_id) {
|
||||
let mut map = HashMap::with_capacity(snapshot.games.len());
|
||||
for game in snapshot.games {
|
||||
map.insert(game.id.clone(), game);
|
||||
}
|
||||
let digest = compute_library_digest(&map);
|
||||
peer.games = map;
|
||||
peer.library_rev = snapshot.library_rev;
|
||||
peer.library_digest = digest;
|
||||
peer.last_seen = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a library delta for a peer. Returns true when applied.
|
||||
pub fn apply_library_delta(&mut self, peer_id: &PeerId, delta: LibraryDelta) -> bool {
|
||||
let Some(peer) = self.peers.get_mut(peer_id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if delta.to_rev <= peer.library_rev {
|
||||
return false;
|
||||
}
|
||||
|
||||
if delta.from_rev != peer.library_rev {
|
||||
return false;
|
||||
}
|
||||
|
||||
for game in delta.added {
|
||||
peer.games.insert(game.id.clone(), game);
|
||||
}
|
||||
for game in delta.updated {
|
||||
peer.games.insert(game.id.clone(), game);
|
||||
}
|
||||
for game_id in delta.removed {
|
||||
peer.games.remove(&game_id);
|
||||
}
|
||||
|
||||
peer.library_rev = delta.to_rev;
|
||||
peer.library_digest = compute_library_digest(&peer.games);
|
||||
peer.last_seen = Instant::now();
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns all games aggregated from all peers.
|
||||
#[must_use]
|
||||
pub fn get_all_games(&self) -> Vec<Game> {
|
||||
@@ -112,19 +277,27 @@ impl PeerGameDB {
|
||||
.entry(game.id.clone())
|
||||
.and_modify(|existing| {
|
||||
if let (Some(new_version), Some(current)) =
|
||||
(&game.eti_game_version, &existing.eti_game_version)
|
||||
(&game.eti_version, &existing.eti_game_version)
|
||||
{
|
||||
if new_version > current {
|
||||
existing.eti_game_version = Some(new_version.clone());
|
||||
}
|
||||
} else if existing.eti_game_version.is_none() {
|
||||
existing.eti_game_version.clone_from(&game.eti_game_version);
|
||||
existing.eti_game_version.clone_from(&game.eti_version);
|
||||
}
|
||||
// Update peer count
|
||||
existing.peer_count = peer_counts[&game.id];
|
||||
if game.size > existing.size {
|
||||
existing.size = game.size;
|
||||
}
|
||||
if game.downloaded {
|
||||
existing.downloaded = true;
|
||||
}
|
||||
if game.installed {
|
||||
existing.installed = true;
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
let mut game_clone = game.clone();
|
||||
let mut game_clone = summary_to_game(game);
|
||||
game_clone.peer_count = peer_counts[&game.id];
|
||||
game_clone
|
||||
});
|
||||
@@ -143,7 +316,7 @@ impl PeerGameDB {
|
||||
|
||||
for peer in self.peers.values() {
|
||||
if let Some(game) = peer.games.get(game_id)
|
||||
&& let Some(ref version) = game.eti_game_version
|
||||
&& let Some(ref version) = game.eti_version
|
||||
{
|
||||
match &latest_version {
|
||||
None => latest_version = Some(version.clone()),
|
||||
@@ -162,13 +335,37 @@ impl PeerGameDB {
|
||||
/// Returns all peer addresses.
|
||||
#[must_use]
|
||||
pub fn get_peer_addresses(&self) -> Vec<SocketAddr> {
|
||||
self.peers.keys().copied().collect()
|
||||
self.peers.values().map(|peer| peer.addr).collect()
|
||||
}
|
||||
|
||||
/// Returns peer liveness info for ping scheduling.
|
||||
#[must_use]
|
||||
pub fn peer_liveness_snapshot(&self) -> Vec<(PeerId, SocketAddr, Instant)> {
|
||||
self.peers
|
||||
.values()
|
||||
.map(|peer| (peer.peer_id.clone(), peer.addr, peer.last_seen))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns peer ids with their current addresses.
|
||||
#[must_use]
|
||||
pub fn peer_identities(&self) -> Vec<(PeerId, SocketAddr)> {
|
||||
self.peers
|
||||
.values()
|
||||
.map(|peer| (peer.peer_id.clone(), peer.addr))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Checks if a peer is in the database.
|
||||
#[must_use]
|
||||
pub fn contains_peer(&self, addr: &SocketAddr) -> bool {
|
||||
self.peers.contains_key(addr)
|
||||
pub fn contains_peer(&self, peer_id: &PeerId) -> bool {
|
||||
self.peers.contains_key(peer_id)
|
||||
}
|
||||
|
||||
/// Checks if a peer address is in the database.
|
||||
#[must_use]
|
||||
pub fn contains_peer_addr(&self, addr: &SocketAddr) -> bool {
|
||||
self.addr_index.contains_key(addr)
|
||||
}
|
||||
|
||||
/// Returns addresses of peers that have a specific game.
|
||||
@@ -177,7 +374,7 @@ impl PeerGameDB {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, peer)| peer.games.contains_key(game_id))
|
||||
.map(|(addr, _)| *addr)
|
||||
.map(|(_, peer)| peer.addr)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -191,7 +388,7 @@ impl PeerGameDB {
|
||||
.iter()
|
||||
.filter(|(_, peer)| {
|
||||
if let Some(game) = peer.games.get(game_id) {
|
||||
if let Some(ref version) = game.eti_game_version {
|
||||
if let Some(ref version) = game.eti_version {
|
||||
version == latest
|
||||
} else {
|
||||
false
|
||||
@@ -200,7 +397,7 @@ impl PeerGameDB {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|(addr, _)| *addr)
|
||||
.map(|(_, peer)| peer.addr)
|
||||
.collect()
|
||||
} else {
|
||||
// If no version info is available, fall back to all peers with the game
|
||||
@@ -213,7 +410,12 @@ impl PeerGameDB {
|
||||
pub fn game_files_for(&self, game_id: &str) -> Vec<(SocketAddr, Vec<GameFileDescription>)> {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter_map(|(addr, peer)| peer.files.get(game_id).cloned().map(|files| (*addr, files)))
|
||||
.filter_map(|(_, peer)| {
|
||||
peer.files
|
||||
.get(game_id)
|
||||
.cloned()
|
||||
.map(|files| (peer.addr, files))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -373,10 +575,8 @@ impl PeerGameDB {
|
||||
peers: &[SocketAddr],
|
||||
) -> Option<GameFileDescription> {
|
||||
if let Some(first_peer) = peers.first()
|
||||
&& let Some(files) = self
|
||||
.peers
|
||||
.get(first_peer)
|
||||
.and_then(|p| p.files.get(game_id))
|
||||
&& let Some(peer_id) = self.addr_index.get(first_peer)
|
||||
&& let Some(files) = self.peers.get(peer_id).and_then(|p| p.files.get(game_id))
|
||||
&& let Some(file_desc) = files
|
||||
.iter()
|
||||
.find(|f| f.relative_path == relative_path && f.size == size)
|
||||
@@ -390,9 +590,19 @@ impl PeerGameDB {
|
||||
#[must_use]
|
||||
pub fn get_stale_peers(&self, timeout: Duration) -> Vec<SocketAddr> {
|
||||
self.peers
|
||||
.iter()
|
||||
.filter(|(_, peer)| peer.last_seen.elapsed() > timeout)
|
||||
.map(|(addr, _)| *addr)
|
||||
.values()
|
||||
.filter(|peer| peer.last_seen.elapsed() > timeout)
|
||||
.map(|peer| peer.addr)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns stale peer ids that exceeded the timeout.
|
||||
#[must_use]
|
||||
pub fn get_stale_peer_ids(&self, timeout: Duration) -> Vec<PeerId> {
|
||||
self.peers
|
||||
.values()
|
||||
.filter(|peer| peer.last_seen.elapsed() > timeout)
|
||||
.map(|peer| peer.peer_id.clone())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -500,3 +710,22 @@ fn create_peer_whitelist(peer_scores: HashMap<SocketAddr, usize>) -> Vec<SocketA
|
||||
|
||||
peers.into_iter().map(|(peer, _)| peer).collect()
|
||||
}
|
||||
|
||||
fn summary_to_game(summary: &GameSummary) -> Game {
|
||||
Game {
|
||||
id: summary.id.clone(),
|
||||
name: summary.name.clone(),
|
||||
description: String::new(),
|
||||
release_year: String::new(),
|
||||
publisher: String::new(),
|
||||
max_players: 1,
|
||||
version: "1.0".to_string(),
|
||||
genre: String::new(),
|
||||
size: summary.size,
|
||||
downloaded: summary.downloaded,
|
||||
installed: summary.installed,
|
||||
eti_game_version: summary.eti_version.clone(),
|
||||
local_version: None,
|
||||
peer_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user