#![allow(clippy::missing_errors_doc)] use std::{ collections::HashMap, fmt, fs::{File, OpenOptions}, path::Path, }; use serde::{Deserialize, Serialize}; use crate::serialization::version_serde; /// A game #[derive(Clone, Serialize, Deserialize)] pub struct Game { /// example: 1 pub id: u64, /// example: Call of Duty 3 pub name: String, /// example: A shooter game in war. pub description: String, /// example: `call_of_duty.tar.zst` pub install_archive: String, /// example: 8 pub max_players: u32, /// example: 1.0.0 #[serde(with = "version_serde")] pub version: semver::Version, /// size (bytes) (not serialized) #[serde(skip)] pub size: u64, } impl fmt::Debug for Game { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}: {} {} ({} players) ({}: {} MB) {}", self.id, self.name, self.version, self.max_players, self.install_archive, self.size, self.description, ) } } impl fmt::Display for Game { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name) } } impl PartialEq for Game { fn eq(&self, other: &Self) -> bool { self.name == other.name } } impl Eq for Game {} impl PartialOrd for Game { fn partial_cmp(&self, other: &Self) -> Option { Some(self.name.cmp(&other.name)) } } impl Ord for Game { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.name.cmp(&other.name) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameDB { pub games: HashMap, next_id: u64, } impl GameDB { #[must_use] pub fn new() -> Self { GameDB { games: HashMap::new(), next_id: 1, } } #[must_use] pub fn from(games: Vec) -> Self { let mut db = GameDB::new(); for game in games { let id = game.id; db.games.insert(game.id, game); db.next_id = db.next_id.max(id + 1); } db } pub fn add_game>( &mut self, name: S, description: S, install_archive: S, max_players: u32, version: semver::Version, ) -> u64 { let id = self.next_id; self.next_id += 1; let game = Game { id, name: name.into(), description: description.into(), install_archive: install_archive.into(), max_players, version, size: 0, }; self.games.insert(id, game); id } #[must_use] pub fn get_game_by_id(&self, id: u64) -> Option<&Game> { self.games.get(&id) } #[must_use] pub fn get_game_by_name(&self, name: &str) -> Option<&Game> { self.games.values().find(|game| game.name == name) } pub fn update_game>( &mut self, id: u64, name: Option, description: Option, install_archive: Option, ) -> bool { if let Some(game) = self.games.get_mut(&id) { if let Some(new_name) = name { game.name = new_name.into(); } if let Some(new_description) = description { game.description = new_description.into(); } if let Some(archive) = install_archive { game.install_archive = archive.into(); } true } else { false } } pub fn delete_game(&mut self, id: u64) -> bool { self.games.remove(&id).is_some() } #[must_use] pub fn all_games(&self) -> Vec<&Game> { self.games.values().collect() } pub fn save_to_file(&self, path: &Path) -> eyre::Result<()> { let file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path)?; let games: Vec<&Game> = self.games.values().collect(); serde_json::to_writer(file, &games)?; Ok(()) } pub fn load_from_file(path: &Path) -> eyre::Result { let file = File::open(path)?; let games: Vec = serde_json::from_reader(file)?; let db = GameDB::from(games); Ok(db) } } impl Default for GameDB { fn default() -> Self { Self::new() } }