pub mod apis; pub mod cache; pub mod commands; pub mod config; pub mod db; pub mod error; pub mod files; use std::{ fmt::Display, fs::{self, remove_file, File}, io::{Read, Write}, time::Duration, }; use apis::modrinth::{get_game_versions, GameVersion, GameVersionType}; pub use apis::*; pub use commands::*; use error::{EType, MLErr, MLE}; use indicatif::{ProgressBar, ProgressStyle}; use serde::{Deserialize, Serialize}; pub static STYLE_BAR_BYTE: &str = "{spinner:.green}{wide_msg}{bytes}/{total_bytes} [{bar:.green/lime}]"; pub static STYLE_BAR_POS: &str = " {wide_msg}{pos}/{len} [{bar:.green/lime}]"; pub static STYLE_SPINNER: &str = "{spinner:.green}{wide_msg}"; pub static STYLE_OPERATION: &str = " {wide_msg}"; pub static STYLE_MESSAGE: &str = "{wide_msg}"; pub static PROGRESS_CHARS: &str = "#>-"; #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum Modloader { #[serde(rename(serialize = "fabric", deserialize = "fabric"))] Fabric, #[serde(rename(serialize = "forge", deserialize = "forge"))] Forge, #[serde(rename(serialize = "quilt", deserialize = "quilt"))] Quilt, } impl Modloader { /// # Errors pub fn from(string: &str) -> MLE { match string { "forge" => Ok(Modloader::Forge), "fabric" => Ok(Modloader::Fabric), "quilt" => Ok(Modloader::Quilt), _ => { Err(MLErr::new(EType::ArgumentError, "UNKNOWN_MODLOADER")) } } } } impl Display for Modloader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Modloader::Fabric => write!(f, "fabric"), Modloader::Forge => write!(f, "forge"), Modloader::Quilt => write!(f, "quilt"), } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub enum VersionLevel { #[serde(rename(serialize = "release", deserialize = "release"))] Release, #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))] Snapshot, Version(String), } /// Checks if update needed (time) /// if yes: get versions, update /// # Errors pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> { let p = ProgressBar::new(1); p.set_style(ProgressStyle::with_template(STYLE_MESSAGE).map_err(|_| { MLErr::new(EType::LibIndicatif, "template error") })?); p.set_message("Update minecraft versions"); let creation_time = fs::metadata(path)?.created()?; if !force && creation_time.elapsed().map_err(|_| MLErr::new(EType::LibIndicatif, "SystemTimeError"))? < Duration::from_secs(60 * 60 * 24) { return Ok(()); } let versions = get_game_versions().await?; remove_file(path)?; let mut file = File::create(path)?; file.write_all(serde_json::to_string_pretty(&versions)?.as_bytes())?; p.finish_with_message("Updated minecraft versions"); Ok(()) } /// Loads game versions from file /// # Errors pub fn load_game_versions(path: &str) -> MLE> { let mut file = File::open(path)?; let mut data = String::new(); file.read_to_string(&mut data)?; let versions: Vec = serde_json::from_str(&data)?; Ok(versions) } impl VersionLevel { #[must_use] pub fn from(str: &str) -> Self { match str { "release" => VersionLevel::Release, "snapshot" => VersionLevel::Snapshot, _ => VersionLevel::Version(String::from(str)), } } /// . /// /// Panics if . /// # Errors pub async fn get( self, versions_path: &str, force_update: bool, ) -> MLE { let path = format!("{versions_path}/versions.json"); check_game_versions(&path, force_update).await?; let mut versions = load_game_versions(&path)?.into_iter(); match self { VersionLevel::Release => { if let Some(release) = versions .find(|ver| ver.version_type == GameVersionType::release) { Ok(release.version) } else { Err(MLErr::new( EType::Other, "no minecraft release version found", )) } } VersionLevel::Snapshot => { if let Some(snapshot) = versions .find(|ver| ver.version_type == GameVersionType::snapshot) { Ok(snapshot.version) } else { Err(MLErr::new( EType::Other, "no minecraft snapshot version found", )) } } VersionLevel::Version(v) => { if versions.any(|ver| ver.version == v) { Ok(v) } else { Err(MLErr::new( EType::ConfigError, "unknown minecraft version", )) } } } } }