From 14e8e92f466d8dc8305d13c0a3ea0e38f31ef2f4 Mon Sep 17 00:00:00 2001 From: leocth Date: Sun, 20 Feb 2022 22:20:50 +0800 Subject: [PATCH] the first wave of refactors --- Cargo.lock | 23 ++- theseus/Cargo.toml | 5 +- theseus/src/launcher/download.rs | 166 ++++++++++---------- theseus/src/launcher/java.rs | 27 +--- theseus/src/lib.rs | 8 +- theseus/src/modpack/manifest.rs | 230 +++++++++++++--------------- theseus/src/modpack/mod.rs | 33 ++-- theseus/src/modpack/modrinth_api.rs | 61 ++++---- theseus/src/modpack/pack.rs | 5 +- 9 files changed, 264 insertions(+), 294 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e164ce914..385d8ea11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "const_format" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22bc6cd49b0ec407b680c3e380182b6ac63b73991cb7602de350352fc309b614" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.2" @@ -1165,13 +1185,12 @@ dependencies = [ "async-trait", "bytes", "chrono", + "const_format", "daedalus", "fs_extra", "futures", "json5", - "lazy_static", "log", - "once_cell", "path-clean", "regex", "reqwest", diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index 8feb2bd45..719d3ef00 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -9,7 +9,6 @@ edition = "2018" [dependencies] thiserror = "1.0" async-trait = "0.1.51" -once_cell = "1.9.0" daedalus = "0.1.12" @@ -27,7 +26,6 @@ path-clean = "0.1.0" fs_extra = "1.2.0" regex = "1.5" -lazy_static = "1.4" tokio = { version = "1", features = ["full"] } futures = "0.3" @@ -35,9 +33,12 @@ futures = "0.3" sys-info = "0.9.0" log = "0.4.14" +const_format = "0.2.22" [dev-dependencies] argh = "0.1.6" [[example]] name = "download-pack" + +[features] diff --git a/theseus/src/launcher/download.rs b/theseus/src/launcher/download.rs index e50de820a..85d3da439 100644 --- a/theseus/src/launcher/download.rs +++ b/theseus/src/launcher/download.rs @@ -9,30 +9,35 @@ use futures::future; use std::fs::File; use std::io::Write; use std::path::Path; +use std::time::Duration; pub async fn download_version_info( client_path: &Path, version: &Version, loader_version: Option<&LoaderVersion>, ) -> Result { - let id = loader_version.map(|x| &x.id).unwrap_or(&version.id); + let id = match loader_version { + Some(x) => &x.id, + None => &version.id, + }; - let path = &*client_path.join(id).join(format!("{}.json", id)); + let mut path = client_path.join(id); + path.push(id); + path.set_extension("json"); if path.exists() { - Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) + let contents = std::fs::read_to_string(path)?; + Ok(serde_json::from_str(&contents)?) } else { let mut info = fetch_version_info(version).await?; if let Some(loader_version) = loader_version { - let partial = fetch_partial_version(&*loader_version.url).await?; - + let partial = fetch_partial_version(&loader_version.url).await?; info = merge_partial_version(partial, info); - info.id = loader_version.id.clone(); } - - save_file(path, &bytes::Bytes::from(serde_json::to_string(&info)?))?; + let info_s = serde_json::to_string(&info)?; + save_file(&path, &bytes::Bytes::from(info_s))?; Ok(info) } @@ -42,22 +47,21 @@ pub async fn download_client( client_path: &Path, version_info: &VersionInfo, ) -> Result<(), LauncherError> { + let version = &version_info.id; let client_download = version_info .downloads .get(&DownloadType::Client) .ok_or_else(|| { LauncherError::InvalidInput(format!( - "Version {} does not have any client downloads", - &version_info.id + "Version {version} does not have any client downloads" )) })?; - let path = &*client_path - .join(&version_info.id) - .join(format!("{}.jar", &version_info.id)); - - save_and_download_file(path, &client_download.url, Some(&client_download.sha1)).await?; + let mut path = client_path.join(version); + path.push(version); + path.set_extension("jar"); + save_and_download_file(&path, &client_download.url, Some(&client_download.sha1)).await?; Ok(()) } @@ -65,16 +69,15 @@ pub async fn download_assets_index( assets_path: &Path, version: &VersionInfo, ) -> Result { - let path = &*assets_path - .join("indexes") - .join(format!("{}.json", &version.asset_index.id)); + let path = assets_path.join(format!("indexes/{}.json", &version.asset_index.id)); if path.exists() { - Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) + let content = std::fs::read_to_string(path)?; + Ok(serde_json::from_str(&content)?) } else { let index = fetch_assets_index(version).await?; - save_file(path, &bytes::Bytes::from(serde_json::to_string(&index)?))?; + save_file(&path, &bytes::Bytes::from(serde_json::to_string(&index)?))?; Ok(index) } @@ -89,7 +92,7 @@ pub async fn download_assets( index .objects .iter() - .map(|x| download_asset(assets_path, legacy_path, x.0, x.1)), + .map(|(name, asset)| download_asset(assets_path, legacy_path, name, asset)), ) .await .into_iter() @@ -104,23 +107,20 @@ async fn download_asset( name: &str, asset: &Asset, ) -> Result<(), LauncherError> { - let sub_hash = &&asset.hash[..2]; + let hash = &asset.hash; + let sub_hash = &hash[..2]; - let resource_path = assets_path.join("objects").join(sub_hash).join(&asset.hash); + let mut resource_path = assets_path.join("objects"); + resource_path.push(sub_hash); + resource_path.push(hash); - let resource = save_and_download_file( - &*resource_path, - &format!( - "https://resources.download.minecraft.net/{}/{}", - sub_hash, asset.hash - ), - Some(&*asset.hash), - ) - .await?; + let url = format!("https://resources.download.minecraft.net/{sub_hash}/{hash}"); + + let resource = save_and_download_file(&resource_path, &url, Some(hash)).await?; if let Some(legacy_path) = legacy_path { let resource_path = - legacy_path.join(name.replace('/', &*std::path::MAIN_SEPARATOR.to_string())); + legacy_path.join(name.replace('/', &std::path::MAIN_SEPARATOR.to_string())); save_file(resource_path.as_path(), &resource)?; } @@ -135,7 +135,7 @@ pub async fn download_libraries( future::join_all( libraries .iter() - .map(|x| download_library(libraries_path, natives_path, x)), + .map(|library| download_library(libraries_path, natives_path, library)), ) .await .into_iter() @@ -150,19 +150,16 @@ async fn download_library( library: &Library, ) -> Result<(), LauncherError> { if let Some(rules) = &library.rules { - if !super::rules::parse_rules(rules.as_slice()) { + if !super::rules::parse_rules(rules) { return Ok(()); } } - let (a, b) = future::join( + future::try_join( download_library_jar(libraries_path, library), download_native(natives_path, library), ) - .await; - - a?; - b?; + .await?; Ok(()) } @@ -171,55 +168,53 @@ async fn download_library_jar( libraries_path: &Path, library: &Library, ) -> Result<(), LauncherError> { - let mut path = libraries_path.to_path_buf(); - path.push(get_path_from_artifact(&*library.name)?); + let artifact_path = get_path_from_artifact(&library.name)?; + let path = libraries_path.join(&artifact_path); if let Some(downloads) = &library.downloads { if let Some(library) = &downloads.artifact { - save_and_download_file(&*path, &library.url, Some(&library.sha1)).await?; + save_and_download_file(&path, &library.url, Some(&library.sha1)).await?; } } else { - save_and_download_file( - &*path, - &format!( - "{}{}", - library - .url - .as_deref() - .unwrap_or("https://libraries.minecraft.net/"), - get_path_from_artifact(&*library.name)? - ), - None, - ) - .await?; + let url = format!( + "{}{artifact_path}", + library + .url + .as_deref() + .unwrap_or("https://libraries.minecraft.net/"), + ); + save_and_download_file(&path, &url, None).await?; } Ok(()) } async fn download_native(natives_path: &Path, library: &Library) -> Result<(), LauncherError> { - if let Some(natives) = &library.natives { - if let Some(os_key) = natives.get(&get_os()) { - if let Some(downloads) = &library.downloads { - if let Some(classifiers) = &downloads.classifiers { - #[cfg(target_pointer_width = "64")] - let parsed_key = os_key.replace("${arch}", "64"); - #[cfg(target_pointer_width = "32")] - let parsed_key = os_key.replace("${arch}", "32"); + use daedalus::minecraft::LibraryDownload; + use std::collections::HashMap; - if let Some(native) = classifiers.get(&*parsed_key) { - let file = download_file(&native.url, Some(&native.sha1)).await?; + // Try blocks in stable Rust when? + let optional_cascade = || -> Option<(&String, &HashMap)> { + let os_key = library.natives.as_ref()?.get(&get_os())?; + let classifiers = library.downloads.as_ref()?.classifiers.as_ref()?; + Some((os_key, classifiers)) + }; - let reader = std::io::Cursor::new(&*file); + if let Some((os_key, classifiers)) = optional_cascade() { + #[cfg(target_pointer_width = "64")] + let parsed_key = os_key.replace("${arch}", "64"); + #[cfg(target_pointer_width = "32")] + let parsed_key = os_key.replace("${arch}", "32"); - let mut archive = zip::ZipArchive::new(reader).unwrap(); - archive.extract(natives_path).unwrap(); - } - } - } + if let Some(native) = classifiers.get(&parsed_key) { + let file = download_file(&native.url, Some(&native.sha1)).await?; + + let reader = std::io::Cursor::new(&file); + + let mut archive = zip::ZipArchive::new(reader).unwrap(); + archive.extract(natives_path).unwrap(); } } - Ok(()) } @@ -228,27 +223,23 @@ async fn save_and_download_file( url: &str, sha1: Option<&str>, ) -> Result { - let read = std::fs::read(path).ok().map(bytes::Bytes::from); - - if let Some(bytes) = read { - Ok(bytes) - } else { - let file = download_file(url, sha1).await?; - - save_file(path, &file)?; - - Ok(file) + match std::fs::read(path) { + Ok(bytes) => Ok(bytes::Bytes::from(bytes)), + Err(_) => { + let file = download_file(url, sha1).await?; + save_file(path, &file)?; + Ok(file) + } } } -fn save_file(path: &Path, bytes: &bytes::Bytes) -> Result<(), std::io::Error> { +fn save_file(path: &Path, bytes: &bytes::Bytes) -> std::io::Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } let mut file = File::create(path)?; file.write_all(bytes)?; - Ok(()) } @@ -263,7 +254,7 @@ pub fn get_os() -> Os { pub async fn download_file(url: &str, sha1: Option<&str>) -> Result { let client = reqwest::Client::builder() - .tcp_keepalive(Some(std::time::Duration::from_secs(10))) + .tcp_keepalive(Some(Duration::from_secs(10))) .build() .map_err(|err| LauncherError::FetchError { inner: err, @@ -311,12 +302,11 @@ pub async fn download_file(url: &str, sha1: Option<&str>) -> Result Result { +async fn get_hash(bytes: bytes::Bytes) -> Result { let hash = tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest()).await?; Ok(hash) diff --git a/theseus/src/launcher/java.rs b/theseus/src/launcher/java.rs index 8848d66bf..e703b17f3 100644 --- a/theseus/src/launcher/java.rs +++ b/theseus/src/launcher/java.rs @@ -1,30 +1,17 @@ use crate::launcher::LauncherError; -use lazy_static::lazy_static; -use regex::Regex; use std::process::Command; -lazy_static! { - static ref JAVA_VERSION_REGEX: Regex = Regex::new(r#""(.*?)""#).unwrap(); -} -pub fn check_java() -> Result, LauncherError> { +pub fn check_java() -> Result { let child = Command::new("java") .arg("-version") .output() - .map_err(|err| LauncherError::ProcessError { - inner: err, - process: "java".to_string(), + .map_err(|inner| LauncherError::ProcessError { + inner, + process: "java".into(), })?; - let output = &*String::from_utf8_lossy(&*child.stderr); - - if let Some(version_raw) = JAVA_VERSION_REGEX.find(output) { - let mut raw = version_raw.as_str().chars(); - raw.next(); - raw.next_back(); - - return Ok(Some(raw.as_str().to_string())); - } - - Ok(None) + let output = String::from_utf8_lossy(&child.stderr); + let output = output.trim_matches('\"'); + Ok(output.into()) } diff --git a/theseus/src/lib.rs b/theseus/src/lib.rs index 315f5ed18..412566204 100644 --- a/theseus/src/lib.rs +++ b/theseus/src/lib.rs @@ -5,11 +5,7 @@ #![warn(missing_docs, unused_import_braces, missing_debug_implementations)] -use std::path::Path; - -lazy_static::lazy_static! { - pub static ref LAUNCHER_WORK_DIR: &'static Path = Path::new("./launcher"); -} +static LAUNCHER_WORK_DIR: &'static str = "./launcher"; mod data; pub mod launcher; @@ -29,7 +25,7 @@ pub enum Error { } pub async fn init() -> Result<(), Error> { - std::fs::create_dir_all(*LAUNCHER_WORK_DIR).expect("Unable to create launcher root directory!"); + std::fs::create_dir_all(LAUNCHER_WORK_DIR).expect("Unable to create launcher root directory!"); crate::data::Metadata::init().await?; crate::data::Settings::init().await?; diff --git a/theseus/src/modpack/manifest.rs b/theseus/src/modpack/manifest.rs index e0627d522..b3a2293d4 100644 --- a/theseus/src/modpack/manifest.rs +++ b/theseus/src/modpack/manifest.rs @@ -1,5 +1,4 @@ -use std::collections::HashSet; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::convert::TryFrom; @@ -10,48 +9,47 @@ use super::{pack, ModpackError, ModpackResult}; use serde::{Deserialize, Serialize}; pub const DEFAULT_FORMAT_VERSION: u32 = 1; +const MODRINTH_GAMEDATA_URL: &str = "https://staging-cdn.modrinth.com/gamedata"; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct Manifest<'a> { +pub struct Manifest { pub format_version: u32, - pub game: &'a str, - pub version_id: &'a str, - pub name: &'a str, - #[serde(borrow)] - pub summary: Option<&'a str>, - pub files: Vec>, - pub dependencies: ManifestDeps<'a>, + pub game: String, + pub version_id: String, + pub name: String, + pub summary: Option, + pub files: Vec, + pub dependencies: ManifestDeps, } -impl TryFrom> for pack::Modpack { +impl TryFrom for pack::Modpack { type Error = ModpackError; - fn try_from(manifest: Manifest<'_>) -> Result { + fn try_from(manifest: Manifest) -> Result { let files = manifest .files .into_iter() .map(pack::ModpackFile::try_from) - .collect::>>()?; + .collect::>()?; Ok(Self { - name: String::from(manifest.name), - version: String::from(manifest.version_id), - summary: manifest.summary.map(String::from), + name: manifest.name, + version: manifest.version_id, + summary: manifest.summary, game: ModpackGame::from(manifest.dependencies), files, }) } } -const MODRINTH_GAMEDATA_URL: &str = "https://staging-cdn.modrinth.com/gamedata"; fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult { let source = match loader { ModLoader::Vanilla => Err(ModpackError::VersionError(String::from( "Attempted to get mod loader version of Vanilla", ))), - ModLoader::Forge => Ok(format!("{}/forge/v0/manifest.json", MODRINTH_GAMEDATA_URL)), - ModLoader::Fabric => Ok(format!("{}/fabric/v0/manifest.json", MODRINTH_GAMEDATA_URL)), + ModLoader::Forge => Ok(format!("{MODRINTH_GAMEDATA_URL}/forge/v0/manifest.json")), + ModLoader::Fabric => Ok(format!("{MODRINTH_GAMEDATA_URL}/fabric/v0/manifest.json")), }?; let manifest = futures::executor::block_on(daedalus::modded::fetch_manifest(&source))?; @@ -63,96 +61,90 @@ fn get_loader_version(loader: ModLoader, version: &str) -> ModpackResult .flatten() .ok_or_else(|| { ModpackError::VersionError(format!( - "No versions of modloader {:?} exist for Minecraft {}", - loader, version + "No versions of modloader {loader:?} exist for Minecraft {version}", )) })? - .id - .clone()) + .id) } -impl<'a> TryFrom<&'a pack::Modpack> for Manifest<'a> { +impl TryFrom for Manifest { type Error = ModpackError; - fn try_from(pack: &'a pack::Modpack) -> Result { - let game_field: &'a str = match pack.game { - ModpackGame::Minecraft(..) => "minecraft", + fn try_from(pack: pack::Modpack) -> Result { + let pack::Modpack { + game, + version, + name, + summary, + files, + } = pack; + + let game = match game { + ModpackGame::Minecraft(..) => "minecraft".into(), }; - let files = pack - .files - .iter() - .map(ManifestFile::from) - .collect::>(); + let files: Vec<_> = pack.files.into_iter().map(ManifestFile::from).collect(); Ok(Manifest { format_version: DEFAULT_FORMAT_VERSION, - game: game_field, - version_id: &pack.version, - name: &pack.name, - summary: pack.summary.as_deref(), + game, + version_id: version, + name, + summary, files, - dependencies: ManifestDeps::try_from(&pack.game)?, + dependencies: ManifestDeps::try_from(pack.game)?, }) } } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct ManifestFile<'a> { - #[serde(borrow)] - pub path: &'a Path, - pub hashes: Option>, +pub struct ManifestFile { + pub path: PathBuf, + pub hashes: Option, #[serde(default)] pub env: ManifestEnvs, - #[serde(borrow)] - pub downloads: Vec<&'a str>, + pub downloads: Vec, } -impl TryFrom> for pack::ModpackFile { +impl TryFrom for pack::ModpackFile { type Error = ModpackError; - fn try_from(file: ManifestFile<'_>) -> Result { + fn try_from(file: ManifestFile) -> Result { Ok(Self { - path: PathBuf::from(file.path), + path: file.path, hashes: file.hashes.map(pack::ModpackFileHashes::from), env: pack::ModpackEnv::try_from(file.env)?, - downloads: file.downloads.into_iter().map(ToOwned::to_owned).collect(), + downloads: file.downloads.into_iter().collect(), }) } } -impl<'a> From<&'a pack::ModpackFile> for ManifestFile<'a> { - fn from(file: &'a pack::ModpackFile) -> Self { +impl From for ManifestFile { + fn from(file: pack::ModpackFile) -> Self { Self { - path: file.path.as_path(), - hashes: file.hashes.as_ref().map(ManifestHashes::from), + path: file.path, + hashes: file.hashes.map(ManifestHashes::from), env: file.env.into(), - downloads: file - .downloads - .iter() - .map(String::as_str) - .collect::>(), + downloads: file.downloads.into_iter().collect(), } } } -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] -pub struct ManifestHashes<'a> { - pub sha1: &'a str, +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct ManifestHashes { + pub sha1: String, } -impl From> for pack::ModpackFileHashes { - fn from(hashes: ManifestHashes<'_>) -> Self { - Self { - sha1: String::from(hashes.sha1), - } +impl From for pack::ModpackFileHashes { + fn from(hashes: ManifestHashes) -> Self { + Self { sha1: hashes.sha1 } } } -impl<'a> From<&'a pack::ModpackFileHashes> for ManifestHashes<'a> { - fn from(hashes: &'a pack::ModpackFileHashes) -> Self { - Self { sha1: &hashes.sha1 } +impl From for ManifestHashes { + fn from(hashes: pack::ModpackFileHashes) -> Self { + Self { sha1: hashes.sha1 } } } @@ -213,55 +205,47 @@ impl From for ManifestEnvs { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(untagged)] -// HACK: I've tried for hours to get this working zero-copy, but I'm beat. If someone else wants to -// go through the # { +pub enum ManifestDeps { MinecraftFabric { - minecraft: &'a str, + minecraft: String, #[serde(rename = "fabric-loader")] fabric_loader: String, }, MinecraftForge { - minecraft: &'a str, + minecraft: String, forge: String, }, MinecraftVanilla { - minecraft: &'a str, + minecraft: String, }, } -impl From> for pack::ModpackGame { - fn from(deps: ManifestDeps<'_>) -> Self { +impl From for pack::ModpackGame { + fn from(deps: ManifestDeps) -> Self { use ManifestDeps::*; match deps { - MinecraftVanilla { minecraft } => { - Self::Minecraft(String::from(minecraft), ModLoader::Vanilla) - } - MinecraftFabric { minecraft, .. } => { - Self::Minecraft(String::from(minecraft), ModLoader::Fabric) - } - MinecraftForge { minecraft, .. } => { - Self::Minecraft(String::from(minecraft), ModLoader::Forge) - } + MinecraftVanilla { minecraft } => Self::Minecraft(minecraft, ModLoader::Vanilla), + MinecraftFabric { minecraft, .. } => Self::Minecraft(minecraft, ModLoader::Fabric), + MinecraftForge { minecraft, .. } => Self::Minecraft(minecraft, ModLoader::Forge), } } } -impl<'a> TryFrom<&'a pack::ModpackGame> for ManifestDeps<'a> { +impl TryFrom for ManifestDeps { type Error = ModpackError; - fn try_from(game: &'a pack::ModpackGame) -> Result { + fn try_from(game: pack::ModpackGame) -> Result { use super::pack::ModpackGame::*; Ok(match game { - Minecraft(ref ver, ModLoader::Vanilla) => Self::MinecraftVanilla { minecraft: ver }, - Minecraft(ref ver, loader @ ModLoader::Fabric) => Self::MinecraftFabric { - minecraft: ver, - fabric_loader: get_loader_version(*loader, ver)?, + Minecraft(minecraft, ModLoader::Vanilla) => Self::MinecraftVanilla { minecraft }, + Minecraft(minecraft, ModLoader::Fabric) => Self::MinecraftFabric { + minecraft, + fabric_loader: get_loader_version(ModLoader::Fabric, &minecraft)?, }, - Minecraft(ref ver, loader @ ModLoader::Forge) => Self::MinecraftForge { - minecraft: ver, - forge: get_loader_version(*loader, ver)?, + Minecraft(minecraft, ModLoader::Forge) => Self::MinecraftForge { + minecraft, + forge: get_loader_version(ModLoader::Fabric, &minecraft)?, }, }) } @@ -287,13 +271,13 @@ mod tests { "#; let expected_manifest = Manifest { format_version: 1, - game: "minecraft", - version_id: "deadbeef", - name: "Example Pack", + game: "minecraft".into(), + version_id: "deadbeef".into(), + name: "Example Pack".into(), summary: None, - files: Vec::new(), + files: vec![], dependencies: ManifestDeps::MinecraftVanilla { - minecraft: "1.17.1", + minecraft: "1.17.1".into(), }, }; let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON"); @@ -329,21 +313,21 @@ mod tests { "#; let expected_manifest = Manifest { format_version: 1, - game: "minecraft", - version_id: "deadbeef", - name: "Example Pack", + game: "minecraft".into(), + version_id: "deadbeef".into(), + name: "Example Pack".into(), summary: None, files: vec![ManifestFile { - path: Path::new("mods/testmod.jar"), + path: "mods/testmod.jar".into(), hashes: Some(ManifestHashes { - sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(), }), env: ManifestEnvs::default(), - downloads: vec!["https://example.com/testmod.jar"], + downloads: vec!["https://example.com/testmod.jar".into()], }], dependencies: ManifestDeps::MinecraftForge { - minecraft: "1.17.1", - forge: String::from("37.0.110"), + minecraft: "1.17.1".into(), + forge: "37.0.110".into(), }, }; let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON"); @@ -379,21 +363,21 @@ mod tests { "#; let expected_manifest = Manifest { format_version: 1, - game: "minecraft", - version_id: "deadbeef", - name: "Example Pack", + game: "minecraft".into(), + version_id: "deadbeef".into(), + name: "Example Pack".into(), summary: None, files: vec![ManifestFile { - path: Path::new("mods/testmod.jar"), + path: "mods/testmod.jar".into(), hashes: Some(ManifestHashes { - sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(), }), env: ManifestEnvs::default(), - downloads: vec!["https://example.com/testmod.jar"], + downloads: vec!["https://example.com/testmod.jar".into()], }], dependencies: ManifestDeps::MinecraftFabric { - minecraft: "1.17.1", - fabric_loader: String::from("0.9.0"), + minecraft: "1.17.1".into(), + fabric_loader: "0.9.0".into(), }, }; let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON"); @@ -434,24 +418,24 @@ mod tests { "#; let expected_manifest = Manifest { format_version: 1, - game: "minecraft", - version_id: "deadbeef", - name: "Example Pack", - summary: Some("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."), + game: "minecraft".into(), + version_id: "deadbeef".into(), + name: "Example Pack".into(), + summary: Some("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".into()), files: vec![ManifestFile { - path: Path::new("mods/testmod.jar"), + path: "mods/testmod.jar".into(), hashes: Some(ManifestHashes { - sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + sha1: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(), }), env: ManifestEnvs { client: ManifestEnv::Required, server: ManifestEnv::Unsupported, }, - downloads: vec!["https://example.com/testmod.jar"], + downloads: vec!["https://example.com/testmod.jar".into()], }], dependencies: ManifestDeps::MinecraftForge { - minecraft: "1.17.1", - forge: String::from("37.0.110"), + minecraft: "1.17.1".into(), + forge: "37.0.110".into(), }, }; let manifest: Manifest = serde_json::from_str(PACK_JSON).expect("Error parsing pack JSON"); diff --git a/theseus/src/modpack/mod.rs b/theseus/src/modpack/mod.rs index d6c41c196..74842c64c 100644 --- a/theseus/src/modpack/mod.rs +++ b/theseus/src/modpack/mod.rs @@ -2,7 +2,6 @@ use daedalus::download_file; use fs_extra::dir::CopyOptions; -use serde::Deserialize; use std::{convert::TryFrom, env, io, path::Path}; use tokio::{fs, try_join}; use uuid::Uuid; @@ -23,6 +22,10 @@ pub const COMPILED_ZIP: &str = "compiled.mrpack"; pub const MANIFEST_PATH: &str = "modrinth.index.json"; pub const OVERRIDES_PATH: &str = "overrides/"; pub const PACK_JSON5_PATH: &str = "modpack.json5"; +const PACK_GITIGNORE: &'static str = const_format::formatcp!(r#" +{COMPILED_PATH} +{COMPILED_ZIP} +"#); #[derive(thiserror::Error, Debug)] pub enum ModpackError { @@ -86,7 +89,8 @@ pub async fn realise_modpack_zip( dest: &Path, side: pack::ModpackSide, ) -> ModpackResult<()> { - let tmp = env::temp_dir().join(format!("theseus-{}/", Uuid::new_v4())); + let mut tmp = env::temp_dir(); + tmp.push(format!("theseus-{}/", Uuid::new_v4())); archive.extract(&tmp)?; realise_modpack(&tmp, dest, side).await } @@ -102,7 +106,7 @@ pub async fn realise_modpack( "Output is not a directory", ))); } - if dest.exists() && std::fs::read_dir(dest).map_or(false, |it| it.count() != 0) { + if std::fs::read_dir(dest).map_or(false, |it| it.count() != 0) { return Err(ModpackError::InvalidDirectory(String::from( "Output directory is non-empty", ))); @@ -112,22 +116,22 @@ pub async fn realise_modpack( } // Copy overrides - let overrides = Some(dir.join(OVERRIDES_PATH)).filter(|it| it.exists() && it.is_dir()); - if let Some(overrides) = overrides { + let overrides = dir.join(OVERRIDES_PATH); + if overrides.is_dir() { fs_extra::dir::copy(overrides, dest, &CopyOptions::new())?; } // Parse manifest // NOTE: I'm using standard files here, since Serde does not support async readers let manifest_path = Some(dir.join(MANIFEST_PATH)) - .filter(|it| it.exists() && it.is_file()) + .filter(|it| it.is_file()) .ok_or_else(|| { ModpackError::ManifestError(String::from("Manifest missing or is not a file")) })?; let manifest_file = std::fs::File::open(manifest_path)?; let reader = io::BufReader::new(manifest_file); - let mut deserializer = serde_json::Deserializer::from_reader(reader); - let manifest = Manifest::deserialize(&mut deserializer)?; + + let manifest: Manifest = serde_json::from_reader(reader)?; let modpack = Modpack::try_from(manifest)?; // Realise modpack @@ -137,14 +141,7 @@ pub async fn realise_modpack( pub fn to_pack_json5(pack: &Modpack) -> ModpackResult { let json5 = json5::to_string(pack)?; - Ok(format!("// This modpack is managed using Theseus. It can be edited using either a Theseus-compatible launcher or manually.\n{}", json5)) -} - -lazy_static::lazy_static! { - static ref PACK_GITIGNORE: String = format!(r#" - {0} - {1} - "#, COMPILED_PATH, COMPILED_ZIP); + Ok(format!("// This modpack is managed using Theseus. It can be edited using either a Theseus-compatible launcher or manually.\n{json5}")) } pub async fn create_modpack( @@ -158,7 +155,7 @@ pub async fn create_modpack( try_join!( fs::create_dir(&output_dir), fs::create_dir(output_dir.join(OVERRIDES_PATH)), - fs::write(output_dir.join(".gitignore"), PACK_GITIGNORE.as_str()), + fs::write(output_dir.join(".gitignore"), PACK_GITIGNORE), fs::write(output_dir.join(PACK_JSON5_PATH), to_pack_json5(&pack)?), )?; @@ -177,7 +174,7 @@ pub async fn compile_modpack(dir: &Path) -> ModpackResult<()> { &CopyOptions::new(), )?; } - let manifest = Manifest::try_from(&pack)?; + let manifest = Manifest::try_from(pack)?; fs::write( result_dir.join(MANIFEST_PATH), serde_json::to_string(&manifest)?, diff --git a/theseus/src/modpack/modrinth_api.rs b/theseus/src/modpack/modrinth_api.rs index 7f4fd6b49..a99fd9181 100644 --- a/theseus/src/modpack/modrinth_api.rs +++ b/theseus/src/modpack/modrinth_api.rs @@ -28,42 +28,35 @@ pub trait ModrinthAPI { pub struct ModrinthV1(pub String); #[derive(Debug, Deserialize)] -struct ModrinthV1Project<'a> { - title: &'a str, - client_side: &'a str, - server_side: &'a str, +struct ModrinthV1Project { + title: String, + client_side: String, + server_side: String, } #[derive(Debug, Deserialize)] -struct ModrinthV1ProjectVersion<'a> { - #[serde(borrow)] - dependencies: HashSet<&'a str>, - #[serde(borrow)] - game_versions: HashSet<&'a str>, - version_type: &'a str, - files: Vec>, - #[serde(borrow)] - loaders: HashSet<&'a str>, +struct ModrinthV1ProjectVersion { + dependencies: HashSet, + game_versions: HashSet, + version_type: String, + files: Vec, + loaders: HashSet, } #[derive(Clone, Debug, Deserialize)] -struct ModrinthV1ProjectVersionFile<'a> { - hashes: ManifestHashes<'a>, - url: &'a str, - filename: &'a str, +struct ModrinthV1ProjectVersionFile { + hashes: ManifestHashes, + url: String, + filename: String, } -impl From> for ModpackFile { - fn from(file: ModrinthV1ProjectVersionFile<'_>) -> Self { +impl From for ModpackFile { + fn from(file: ModrinthV1ProjectVersionFile) -> Self { Self { hashes: Some(ModpackFileHashes::from(file.hashes)), - downloads: { - let mut downloads: HashSet = HashSet::new(); - downloads.insert(String::from(file.url)); - downloads - }, + downloads: HashSet::from([file.url]), path: PathBuf::from(file.filename), - // WARNING: Since the sidedness of version 1 API requests is unknown, the environemnt is + // WARNING: Since the sidedness of version 1 API requests is unknown, the environment is // set here as both. env: ModpackEnv::Both, } @@ -78,10 +71,11 @@ impl ModrinthAPI for ModrinthV1 { channel: &str, game: &ModpackGame, ) -> ModpackResult> { + let domain = &self.0; // Fetch metadata let (project_json, versions_json): (Bytes, Bytes) = try_join!( - try_get_json(format!("{}/api/v1/mod/{}", self.0, project)), - try_get_json(format!("{}/api/v1/mod/{}/version", self.0, project)), + try_get_json(format!("{domain}/api/v1/mod/{project}")), + try_get_json(format!("{domain}/api/v1/mod/{project}/version")), )?; let (mut project_deserializer, mut versions_deserializer) = ( @@ -113,8 +107,8 @@ impl ModrinthAPI for ModrinthV1 { ModLoader::Vanilla => unreachable!(), }; it.version_type == channel - && it.game_versions.contains(&game_version.as_str()) - && it.loaders.contains(&loader_str) + && it.game_versions.contains(game_version) + && it.loaders.contains(loader_str) }) .ok_or_else(|| { ModpackError::VersionError(format!( @@ -125,8 +119,8 @@ impl ModrinthAPI for ModrinthV1 { // Project fields let envs = ModpackEnv::try_from(ManifestEnvs { - client: serde_json::from_str(project.client_side)?, - server: serde_json::from_str(project.server_side)?, + client: serde_json::from_str(&project.client_side)?, + server: serde_json::from_str(&project.server_side)?, })?; // Conversions @@ -155,7 +149,8 @@ impl ModrinthAPI for ModrinthV1 { } async fn get_version(&self, version: &str) -> ModpackResult> { - let version_json = try_get_json(format!("{}/api/v1/version/{}", self.0, version)).await?; + let domain = &self.0; + let version_json = try_get_json(format!("{domain}/api/v1/version/{version}")).await?; let mut version_deserializer = serde_json::Deserializer::from_slice(&version_json); let version = ModrinthV1ProjectVersion::deserialize(&mut version_deserializer)?; let base_path = PathBuf::from("mods/"); @@ -164,7 +159,7 @@ impl ModrinthAPI for ModrinthV1 { .files .into_iter() .map(ModpackFile::from) - .collect::>()) + .collect::>()) } } diff --git a/theseus/src/modpack/pack.rs b/theseus/src/modpack/pack.rs index 2cd017083..f9bff1dba 100644 --- a/theseus/src/modpack/pack.rs +++ b/theseus/src/modpack/pack.rs @@ -144,6 +144,7 @@ impl Modpack { } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[non_exhaustive] pub enum ModpackGame { // TODO: Currently, the launcher does not support specifying mod loader versions, so I just // store the loader here. @@ -256,9 +257,9 @@ mod tests { let mut files = HashSet::new(); files.insert(ModpackFile { path: PathBuf::from("mods/gravestones-v1.9.jar"), - hashes: ModpackFileHashes { + hashes: Some(ModpackFileHashes { sha1: String::from("3f0f6d523d218460310b345be03ab3f1d294e04d"), - }, + }), env: ModpackEnv::Both, downloads: { let mut downloads = HashSet::new();