194 lines
4.4 KiB
Rust

#![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<std::cmp::Ordering> {
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<u64, Game>,
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<Game>) -> 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<S: Into<String>>(
&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<S: Into<String>>(
&mut self,
id: u64,
name: Option<S>,
description: Option<S>,
install_archive: Option<S>,
) -> 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<Self> {
let file = File::open(path)?;
let games: Vec<Game> = serde_json::from_reader(file)?;
let db = GameDB::from(games);
Ok(db)
}
}
impl Default for GameDB {
fn default() -> Self {
Self::new()
}
}