From 9e09e716236acd4be7985ba4db7340e0713ecbfa Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Wed, 7 May 2025 03:27:18 +0200 Subject: [PATCH] Support diffing against an older version --- src/main.rs | 95 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 98a5ee9..57dbf11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::env::args; +use std::ffi::OsStr; use std::io::Read; use std::path::PathBuf; use zip::ZipArchive; @@ -48,7 +49,8 @@ impl Dependency { .json::>() .await? .first() - .ok_or("No fabric installer version found")?.version + .ok_or("No fabric installer version found")? + .version ), Dependency::Minecraft => { let version_map = reqwest::get("https://raw.githubusercontent.com/liebki/MinecraftServerForkDownloads/refs/heads/main/release_vanilla_downloads.json") @@ -65,7 +67,7 @@ impl Dependency { let target = target.into(); if target.exists() { - println!("File {} already exists, skipping download", target.display()); + eprintln!("File {} already exists, skipping download", target.display()); return Ok(()); } let response = reqwest::get(url.clone()).await?; @@ -80,7 +82,7 @@ impl Dependency { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] struct ConfigFile { path: String, hashes: HashMap, @@ -94,21 +96,24 @@ impl ConfigFile { async fn download(&self, target: impl Into) -> Result<(), Box> { let target = target.into(); let target_path = target.join(&self.path); - if target_path.exists() { - println!("File {} already exists, skipping download", target_path.display()); + let mut disabled_path = target_path.extension().unwrap_or(OsStr::new("jar")).to_os_string(); + disabled_path.push(".disabled"); + if target_path.exists() || target_path.with_extension(disabled_path).exists() { + eprintln!("File {} already exists (or is disabled), skipping download", target_path.display()); return Ok(()); } for link in &self.downloads { let response = reqwest::get(link).await?; if response.status().is_success() { + println!("Downloading file from {}", link); let content = response.bytes().await?; let mut file = File::create(&target_path).await?; tokio::io::copy(&mut content.as_ref(), &mut file).await?; return Ok(()) } else { - println!("Failed to download file from {}", link); + eprintln!("Failed to download file from {}", link); } } @@ -143,6 +148,33 @@ struct FabricInstallerVersion { stable: bool, } +#[derive(Debug, Clone)] +struct GameSetupParams { + dependency: Dependency, + version: String, + mc_version: String, +} + +impl GameSetupParams { + fn new(config: &ModrinthConfig) -> Self { + let (server, server_version, mc_version) = if config.is_modded() { + let (dep, version) = config.dependencies.iter().find(|(k, _)| **k != Dependency::Minecraft).expect("Modded dependency version not found"); + let mc_version = config.dependencies.iter().find(|(k, _)| **k == Dependency::Minecraft).map(|(_, v)| v.clone()).expect("Minecraft version not found"); + + (dep.clone(), version.clone(), mc_version) + } else { + let (dep, version) = config.dependencies.iter().find(|(k, _)| **k == Dependency::Minecraft).expect("Modded dependency version not found"); + (dep.clone(), version.clone(), version.clone()) + }; + + Self { + dependency: server, + version: server_version, + mc_version, + } + } +} + async fn get_config_from_archive(archive: &mut ZipArchive, out_dir: impl Into) -> Option { let overrides = "overrides/"; @@ -155,7 +187,6 @@ async fn get_config_from_archive(archive: &mut ZipArchive, out_di if file.name() == modrinth_index { let mut contents = String::new(); if let Ok(_) = file.read_to_string(&mut contents) { - println!("Found modrinth index file"); return Some(serde_json::from_str::(contents.as_str()).expect("Unable to parse modrinth config")) } else { eprintln!("Can't read contents"); @@ -178,17 +209,26 @@ async fn get_config_from_archive(archive: &mut ZipArchive, out_di None } +async fn get_config_from_file(file: &str) -> Option { + let file = std::fs::File::open(file).expect("Unable to open file"); + let mut archive = ZipArchive::new(file).expect("Unable to read zip archive"); + get_config_from_archive(&mut archive, "out/").await +} + #[tokio::main] async fn main() { let args: Vec = args().collect(); - if args.len() != 2 && args.len() != 3 { - eprintln!("Usage: {} [target dir]", args[0]); + if args.len() < 2 || args.len() > 4 { + eprintln!("Usage: {} [target dir] [previous version]", args[0]); return; } - let out_dir = if args.len() == 3 { - PathBuf::from(&args[2]) - } else { - PathBuf::from("out/") + let out_dir = match args.len() { + 3 | 4 => PathBuf::from(&args[2]), + _ => PathBuf::from("out/"), + }; + let previous_version = match args.len() { + 4 => Some(get_config_from_file(args[3].as_str()).await.unwrap()), + _ => None, }; let file = std::fs::File::open(&args[1]).expect("Unable to open file"); let mut archive = ZipArchive::new(file).expect("Unable to read zip archive"); @@ -196,22 +236,31 @@ async fn main() { // Create directory "out" and subdirectory "mods" std::fs::create_dir_all(&out_dir).expect("Unable to create directory"); + println!("Extracting files from {}", args[1]); let config = get_config_from_archive(&mut archive, &out_dir).await.unwrap(); for entry in &config.files { entry.download(&out_dir).await.unwrap(); } - let (server, server_version, mc_version) = if config.is_modded() { - let (dep, version) = config.dependencies.iter().find(|(k, _)| **k != Dependency::Minecraft).expect("Modded dependency version not found"); - let mc_version = config.dependencies.iter().find(|(k, _)| **k == Dependency::Minecraft).map(|(_, v)| v.clone()).expect("Minecraft version not found"); - - (dep.clone(), version.clone(), mc_version) - } else { - let (dep, version) = config.dependencies.iter().find(|(k, _)| **k == Dependency::Minecraft).expect("Modded dependency version not found"); - (dep.clone(), version.clone(), version.clone()) - }; + if let Some(ref previous_version) = previous_version { + for removal in previous_version.files.iter().filter(|v| config.files.iter().filter(|v1| v1.path == v.path).count() == 0).map(|c| { + tokio::fs::remove_file(c.path.clone()) + }) { + let _ = removal.await; + } + } - server.download(out_dir.join("server.jar"), server_version, mc_version).await.unwrap(); + let server_jar = out_dir.join("server.jar"); + let old_params = previous_version.as_ref().map(|prev| GameSetupParams::new(prev)); + let params = GameSetupParams::new(&config); + + if match old_params { None => true, Some(old_params) => old_params.dependency != params.dependency || !server_jar.exists() } { + let _ = tokio::fs::remove_file(&server_jar).await; + println!("Downloading server jar"); + params.dependency.download(server_jar, params.version, params.mc_version).await.unwrap(); + } else { + println!("Server jar already exists, skipping download"); + } let eula = "#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA). #Thu Jan 01 00:00:00 GMT 1970