From e14228dc1fa86f019b5fc964e623da2b1dc2f12b Mon Sep 17 00:00:00 2001 From: TheAlan404 Date: Sun, 21 Jul 2024 09:12:27 +0000 Subject: [PATCH] Lockfile support for addons --- src/api/app/actions/build/addons.rs | 32 ++++++++++++------------ src/api/app/actions/build/mod.rs | 4 +++ src/api/app/actions/lockfile/mod.rs | 38 +++++++++++++++++++++++++++++ src/api/app/actions/mod.rs | 1 + src/api/app/io.rs | 2 +- src/api/app/step/remove_file.rs | 10 +++++++- src/api/models/addon/addon.rs | 15 +++++++++++- src/api/models/lockfile/mod.rs | 2 ++ src/api/sources/buildtools/mod.rs | 34 ++++++++++++++++++-------- src/api/sources/curseforge/mod.rs | 16 ++++++++++++ src/api/sources/github/mod.rs | 31 ++++++++++++++++++----- src/api/sources/hangar/mod.rs | 18 +++++++++++--- src/api/sources/jenkins/mod.rs | 35 +++++++++++++++++++++----- src/api/sources/maven/mod.rs | 32 +++++++++++++++++++++--- src/api/sources/modrinth/mod.rs | 9 +++++++ src/api/sources/spigot/mod.rs | 6 +++++ src/api/sources/url/mod.rs | 12 ++++++++- src/api/step/filemeta.rs | 7 ++++++ 18 files changed, 255 insertions(+), 49 deletions(-) create mode 100644 src/api/app/actions/lockfile/mod.rs diff --git a/src/api/app/actions/build/addons.rs b/src/api/app/actions/build/addons.rs index 8e0313c..c1cc700 100644 --- a/src/api/app/actions/build/addons.rs +++ b/src/api/app/actions/build/addons.rs @@ -3,7 +3,7 @@ use std::{collections::HashSet, path::Path, sync::Arc}; use anyhow::{Context, Result}; use futures::{stream, StreamExt, TryStreamExt}; -use crate::api::{app::App, models::Addon, step::Step}; +use crate::api::{app::App, models::Addon}; impl App { /// Installs new addons and removes old removed addons @@ -11,6 +11,8 @@ impl App { let addons = self.collect_addons().await?; let base = Arc::new(base.to_owned()); + println!("Found {} addons", addons.len()); + let (addons_to_add, addons_to_remove): (Vec, Vec) = if let Some(lockfile) = &*self.existing_lockfile.read().await { let mut old = HashSet::new(); old.extend(lockfile.addons.clone()); @@ -23,6 +25,8 @@ impl App { (addons, vec![]) }; + println!("Installing {} addons, removing {} addons", addons_to_add.len(), addons_to_remove.len()); + for addon in &addons_to_remove { self.clone().action_remove_addon(&base, addon).await?; } @@ -33,8 +37,14 @@ impl App { let app = self.clone(); let base = base.clone(); async move { - app.action_install_addon(&base, &addon).await - .with_context(|| format!("{addon:#?}")) + let x = app.clone().action_install_addon(&base, &addon).await + .with_context(|| format!("{addon:#?}")); + + if x.is_ok() { + app.add_addon_to_lockfile(addon).await; + } + + x } } ).await?; @@ -52,21 +62,9 @@ impl App { /// Removes a single addon pub async fn action_remove_addon(self: Arc, base: &Path, addon: &Addon) -> Result<()> { - let steps = addon.resolve_steps(&self).await?; + let steps = addon.resolve_remove_steps(&self).await?; let dir = base.join(addon.target.as_str()); - - // TODO - - if let Some(meta) = steps.iter().find_map(|x| match x { - Step::CacheCheck(meta) => Some(meta), - Step::Download { metadata, .. } => Some(metadata), - _ => None, - }) { - tokio::fs::remove_file(dir.join(&meta.filename)).await?; - } else { - log::error!("Couldn't remove addon: {addon:#?}"); - } - + self.execute_steps(&dir, &steps).await?; Ok(()) } } diff --git a/src/api/app/actions/build/mod.rs b/src/api/app/actions/build/mod.rs index a486c28..155b2e8 100644 --- a/src/api/app/actions/build/mod.rs +++ b/src/api/app/actions/build/mod.rs @@ -12,11 +12,15 @@ pub mod bootstrap; impl App { /// Builds the entire server pub async fn action_build(self: Arc, base: &Path) -> Result<()> { + self.read_existing_lockfile(base).await?; + self.action_install_jar(base).await?; self.clone().action_install_addons(base).await?; self.clone().action_bootstrap(base).await?; self.action_generate_scripts(base).await?; + self.write_lockfile(base).await?; + Ok(()) } } diff --git a/src/api/app/actions/lockfile/mod.rs b/src/api/app/actions/lockfile/mod.rs new file mode 100644 index 0000000..cac6044 --- /dev/null +++ b/src/api/app/actions/lockfile/mod.rs @@ -0,0 +1,38 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Result; + +use crate::api::{app::App, models::{lockfile::{Lockfile, LOCKFILE}, Addon}}; + +impl App { + pub async fn reset_lockfile(&self) -> Result<()> { + let mut new_lockfile = self.new_lockfile.write().await; + *new_lockfile = Lockfile::default(); + + Ok(()) + } + + pub async fn read_existing_lockfile(&self, base: &Path) -> Result<()> { + let path = base.join(LOCKFILE); + + if path.exists() { + let mut existing_lockfile = self.existing_lockfile.write().await; + *existing_lockfile = Some(serde_json::from_str::(&tokio::fs::read_to_string(base.join(LOCKFILE)).await?)?); + } + + Ok(()) + } + + pub async fn write_lockfile(&self, base: &Path) -> Result<()> { + let lockfile = self.new_lockfile.read().await; + + tokio::fs::write(base.join(LOCKFILE), serde_json::to_vec(&*lockfile)?).await?; + + Ok(()) + } + + pub async fn add_addon_to_lockfile(self: Arc, addon: Addon) { + println!("Added Addon to lockfile"); + self.new_lockfile.write().await.addons.push(addon); + } +} diff --git a/src/api/app/actions/mod.rs b/src/api/app/actions/mod.rs index 0b8d58d..2393082 100644 --- a/src/api/app/actions/mod.rs +++ b/src/api/app/actions/mod.rs @@ -2,3 +2,4 @@ mod build; mod init; mod markdown; mod script; +mod lockfile; diff --git a/src/api/app/io.rs b/src/api/app/io.rs index 1ac4f88..f735ef8 100644 --- a/src/api/app/io.rs +++ b/src/api/app/io.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Result; use tokio::sync::RwLock; -use crate::api::{models::{network::{Network, NETWORK_TOML}, server::{Server, SERVER_TOML}}, utils::toml::{try_find_toml_upwards, write_toml}}; +use crate::api::{models::{lockfile::Lockfile, network::{Network, NETWORK_TOML}, server::{Server, SERVER_TOML}}, utils::toml::{try_find_toml_upwards, write_toml}}; use super::App; diff --git a/src/api/app/step/remove_file.rs b/src/api/app/step/remove_file.rs index e567707..7265957 100644 --- a/src/api/app/step/remove_file.rs +++ b/src/api/app/step/remove_file.rs @@ -10,7 +10,15 @@ impl App { dir: &Path, metadata: &FileMeta, ) -> Result { - + println!("Deleting {}", metadata.filename); + + let path = dir.join(&metadata.filename); + + if path.exists() { + tokio::fs::remove_file(path).await?; + } else { + println!("{path:?} does not exist, cant delete"); + } Ok(StepResult::Continue) } diff --git a/src/api/models/addon/addon.rs b/src/api/models/addon/addon.rs index c80356d..11cf7b6 100644 --- a/src/api/models/addon/addon.rs +++ b/src/api/models/addon/addon.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::api::{app::App, models::Environment, sources::url::resolve_steps_for_url, step::Step}; +use crate::api::{app::App, models::Environment, sources::url::{resolve_remove_steps_for_url, resolve_steps_for_url}, step::Step}; use super::{AddonTarget, AddonType}; @@ -27,4 +27,17 @@ impl Addon { AddonType::MavenArtifact { url, group, artifact, version, filename } => app.maven().resolve_steps(url, group, artifact, version, filename).await, } } + + pub async fn resolve_remove_steps(&self, app: &App) -> Result> { + match &self.addon_type { + AddonType::Url { url } => resolve_remove_steps_for_url(app, url, None).await, + AddonType::Modrinth { id, version } => app.modrinth().resolve_remove_steps(id, version).await, + AddonType::Curseforge { id, version } => app.curseforge().resolve_remove_steps(id, version).await, + AddonType::Spigot { id, version } => app.spigot().resolve_remove_steps(id, version).await, + AddonType::Hangar { id, version } => app.hangar().resolve_remove_steps(id, version).await, + AddonType::GithubRelease { repo, version, filename } => app.github().resolve_remove_steps(repo, version, filename).await, + AddonType::Jenkins { url, job, build, artifact } => app.jenkins().resolve_remove_steps(url, job, build, artifact, None).await, + AddonType::MavenArtifact { url, group, artifact, version, filename } => app.maven().resolve_remove_steps(url, group, artifact, version, filename).await, + } + } } diff --git a/src/api/models/lockfile/mod.rs b/src/api/models/lockfile/mod.rs index 0ca01a8..8eb4d15 100644 --- a/src/api/models/lockfile/mod.rs +++ b/src/api/models/lockfile/mod.rs @@ -6,6 +6,8 @@ pub use bootstrapped_file::*; use super::Addon; +pub const LOCKFILE: &str = ".mcman.lock"; + #[derive(Debug, Serialize, Deserialize)] pub struct Lockfile { pub vars: HashMap, diff --git a/src/api/sources/buildtools/mod.rs b/src/api/sources/buildtools/mod.rs index 84bde5a..27649c7 100644 --- a/src/api/sources/buildtools/mod.rs +++ b/src/api/sources/buildtools/mod.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; -use crate::api::{app::App, step::Step, tools::java::JavaVersion}; +use crate::api::{app::App, step::{FileMeta, Step}, tools::java::JavaVersion}; pub const BUILDTOOLS_JENKINS_URL: &str = "https://hub.spigotmc.org/jenkins"; pub const BUILDTOOLS_JENKINS_JOB: &str = "BuildTools"; @@ -12,21 +12,35 @@ pub async fn resolve_steps( custom_args: &Vec, mc_version: &str, ) -> Result> { - let jar = resolve_steps_jar(app).await?; + let (url, metadata) = resolve_buildtools_jar(app).await?; - let meta = jar.iter().find_map(|v| if let Step::CacheCheck(meta) = v { Some(meta) } else { None }).unwrap(); + let exec_steps = resolve_compile_steps(app, &metadata.filename, craftbukkit, custom_args, mc_version).await?; - let exec = resolve_steps_build(app, &meta.filename, craftbukkit, custom_args, mc_version).await?; + let mut steps = vec![ + Step::CacheCheck(metadata.clone()), + Step::Download { url, metadata }, + ]; - Ok(vec![jar, exec].concat()) + steps.extend(exec_steps); + + Ok(steps) } -/// Resolve steps for the BuildTools.jar -pub async fn resolve_steps_jar( - app: &App, +pub async fn resolve_remove_steps( + _app: &App, + _craftbukkit: bool, + _custom_args: &Vec, + _mc_version: &str, ) -> Result> { + Ok(vec![Step::RemoveFile(FileMeta::filename(String::from("server.jar")))]) +} + +/// Resolve BuildTools.jar from jenkins +pub async fn resolve_buildtools_jar( + app: &App, +) -> Result<(String, FileMeta)> { app.jenkins() - .resolve_steps( + .resolve_artifact( BUILDTOOLS_JENKINS_URL, BUILDTOOLS_JENKINS_JOB, "latest", @@ -37,7 +51,7 @@ pub async fn resolve_steps_jar( } /// Resolve steps for using BuildTools to compile a server jar -pub async fn resolve_steps_build( +pub async fn resolve_compile_steps( _app: &App, jar_name: &str, craftbukkit: bool, diff --git a/src/api/sources/curseforge/mod.rs b/src/api/sources/curseforge/mod.rs index b44bfcd..33ae47d 100644 --- a/src/api/sources/curseforge/mod.rs +++ b/src/api/sources/curseforge/mod.rs @@ -55,6 +55,22 @@ impl<'a> CurseforgeAPI<'a> { Step::Download { url: file.download_url, metadata } ]) } + + pub async fn resolve_remove_steps(&self, mod_id: &str, file_id: &str) -> Result> { + let file = self.fetch_file(mod_id, file_id).await?; + + let metadata = FileMeta { + cache: Some(CacheLocation("curseforge".into(), format!("{mod_id}/{file_id}/{}", file.display_name))), + filename: file.display_name, + hashes: convert_hashes(file.hashes), + size: Some(file.file_length), + ..Default::default() + }; + + Ok(vec![ + Step::RemoveFile(metadata) + ]) + } } pub fn convert_hashes(hashes: Vec) -> HashMap { diff --git a/src/api/sources/github/mod.rs b/src/api/sources/github/mod.rs index e030e15..d74f583 100644 --- a/src/api/sources/github/mod.rs +++ b/src/api/sources/github/mod.rs @@ -138,12 +138,12 @@ impl<'a> GithubAPI<'a> { Ok((release, asset)) } - pub async fn resolve_steps( + pub async fn resolve( &self, repo: &str, release_tag: &str, asset_name: &str, - ) -> Result> { + ) -> Result<(String, FileMeta)> { let (release, asset) = self.fetch_asset(repo, release_tag, asset_name).await?; let metadata = FileMeta { @@ -161,12 +161,31 @@ impl<'a> GithubAPI<'a> { release.tag_name, asset.name ); + Ok((url, metadata)) + } + + pub async fn resolve_steps( + &self, + repo: &str, + release_tag: &str, + asset_name: &str, + ) -> Result> { + let (url, metadata) = self.resolve(repo, release_tag, asset_name).await?; + Ok(vec![ Step::CacheCheck(metadata.clone()), - Step::Download { - url, - metadata: metadata.clone(), - }, + Step::Download { url, metadata } ]) } + + pub async fn resolve_remove_steps( + &self, + repo: &str, + release_tag: &str, + asset_name: &str, + ) -> Result> { + let (_, metadata) = self.resolve(repo, release_tag, asset_name).await?; + + Ok(vec![Step::RemoveFile(metadata)]) + } } diff --git a/src/api/sources/hangar/mod.rs b/src/api/sources/hangar/mod.rs index daf57c0..042215a 100644 --- a/src/api/sources/hangar/mod.rs +++ b/src/api/sources/hangar/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context, Ok, Result}; mod models; pub use models::*; @@ -34,7 +34,7 @@ impl<'a> HangarAPI<'a> { ) } - pub async fn resolve_steps(&self, id: &str, version_id: &str) -> Result> { + pub async fn resolve(&self, id: &str, version_id: &str) -> Result<(String, FileMeta)> { let version = self .fetch_project_version(id, version_id) .await @@ -69,9 +69,21 @@ impl<'a> HangarAPI<'a> { let url = download.get_url(); + Ok((url, metadata)) + } + + pub async fn resolve_steps(&self, id: &str, version_id: &str) -> Result> { + let (url, metadata) = self.resolve(id, version_id).await?; + Ok(vec![ Step::CacheCheck(metadata.clone()), - Step::Download { url, metadata }, + Step::Download { url, metadata } ]) } + + pub async fn resolve_remove_steps(&self, id: &str, version_id: &str) -> Result> { + let (_, metadata) = self.resolve(id, version_id).await?; + + Ok(vec![Step::RemoveFile(metadata)]) + } } diff --git a/src/api/sources/jenkins/mod.rs b/src/api/sources/jenkins/mod.rs index 6a301af..2c3a88a 100644 --- a/src/api/sources/jenkins/mod.rs +++ b/src/api/sources/jenkins/mod.rs @@ -93,14 +93,14 @@ impl<'a> JenkinsAPI<'a> { Ok((selected_build, selected_artifact)) } - pub async fn resolve_steps( + pub async fn resolve_artifact( &self, url: &str, job: &str, build: &str, artifact: &str, custom_filename: Option, - ) -> Result> { + ) -> Result<(String, FileMeta)> { let (build, artifact) = self .fetch_artifact(url, job, build, artifact) .await @@ -132,10 +132,33 @@ impl<'a> JenkinsAPI<'a> { let url = format!("{}artifact/{}", build.url, artifact.relative_path); - Ok(vec![ - Step::CacheCheck(metadata.clone()), - Step::Download { url, metadata }, - ]) + Ok((url, metadata)) + } + + pub async fn resolve_steps( + &self, + url: &str, + job: &str, + build: &str, + artifact: &str, + custom_filename: Option, + ) -> Result> { + let (url, metadata) = self.resolve_artifact(url, job, build, artifact, custom_filename).await?; + + Ok(vec![Step::CacheCheck(metadata.clone()), Step::Download { url, metadata }]) + } + + pub async fn resolve_remove_steps( + &self, + url: &str, + job: &str, + build: &str, + artifact: &str, + custom_filename: Option, + ) -> Result> { + let (_, metadata) = self.resolve_artifact(url, job, build, artifact, custom_filename).await?; + + Ok(vec![Step::RemoveFile(metadata)]) } pub async fn fetch_description(&self, url: &str, job: &str) -> Result> { diff --git a/src/api/sources/maven/mod.rs b/src/api/sources/maven/mod.rs index b2999d2..aa30a81 100644 --- a/src/api/sources/maven/mod.rs +++ b/src/api/sources/maven/mod.rs @@ -80,14 +80,14 @@ impl<'a> MavenAPI<'a> { }) } - pub async fn resolve_steps( + pub async fn resolve( &self, url: &str, group_id: &str, artifact_id: &str, version: &str, file: &str, - ) -> Result> { + ) -> Result<(String, FileMeta)> { let file = file .replace("${artifact}", artifact_id) .replace("${version}", version); @@ -113,9 +113,35 @@ impl<'a> MavenAPI<'a> { ..Default::default() }; + Ok((download_url, metadata)) + } + + pub async fn resolve_steps( + &self, + url: &str, + group_id: &str, + artifact_id: &str, + version: &str, + file: &str, + ) -> Result> { + let (url, metadata) = self.resolve(url, group_id, artifact_id, version, file).await?; + Ok(vec![ Step::CacheCheck(metadata.clone()), - Step::Download { url: download_url, metadata }, + Step::Download { url, metadata } ]) } + + pub async fn resolve_remove_steps( + &self, + url: &str, + group_id: &str, + artifact_id: &str, + version: &str, + file: &str, + ) -> Result> { + let (_, metadata) = self.resolve(url, group_id, artifact_id, version, file).await?; + + Ok(vec![Step::RemoveFile(metadata)]) + } } diff --git a/src/api/sources/modrinth/mod.rs b/src/api/sources/modrinth/mod.rs index b558d12..e7ae1da 100644 --- a/src/api/sources/modrinth/mod.rs +++ b/src/api/sources/modrinth/mod.rs @@ -134,4 +134,13 @@ impl<'a> ModrinthAPI<'a> { }, ]) } + + pub async fn resolve_remove_steps(&self, id: &str, version: &str) -> Result> { + let id = self.get_id(id).await?; + let (file, _) = self.fetch_file(&id, version).await?; + + Ok(vec![ + Step::RemoveFile(FileMeta::filename(file.filename)) + ]) + } } diff --git a/src/api/sources/spigot/mod.rs b/src/api/sources/spigot/mod.rs index 7d73a95..b070460 100644 --- a/src/api/sources/spigot/mod.rs +++ b/src/api/sources/spigot/mod.rs @@ -67,6 +67,12 @@ impl<'a> SpigotAPI<'a> { Step::Download { url, metadata }, ]) } + + pub async fn resolve_remove_steps(&self, id: &str, version: &str) -> Result> { + Ok(vec![ + Step::RemoveFile(FileMeta::filename(format!("spigot-{}-{}.jar", resource_id(id), version))) + ]) + } } diff --git a/src/api/sources/url/mod.rs b/src/api/sources/url/mod.rs index bf77f2e..55a75bc 100644 --- a/src/api/sources/url/mod.rs +++ b/src/api/sources/url/mod.rs @@ -7,7 +7,7 @@ use crate::api::{ }; pub async fn resolve_steps_for_url( - app: &App, + _app: &App, url: impl Into, filename: Option, ) -> Result> { @@ -26,3 +26,13 @@ pub async fn resolve_steps_for_url( metadata, }]) } + +pub async fn resolve_remove_steps_for_url( + _app: &App, + url: impl Into, + filename: Option, +) -> Result> { + let filename = filename.unwrap_or_else(|| get_filename_from_url(&url.into())); + + Ok(vec![Step::RemoveFile(FileMeta::filename(filename))]) +} diff --git a/src/api/step/filemeta.rs b/src/api/step/filemeta.rs index 17b0c9d..c83007a 100644 --- a/src/api/step/filemeta.rs +++ b/src/api/step/filemeta.rs @@ -16,6 +16,13 @@ pub struct FileMeta { } impl FileMeta { + pub fn filename(filename: String) -> Self { + Self { + filename, + ..Default::default() + } + } + pub fn get_hasher(&self) -> Option<(HashFormat, Box, String)> { get_best_hash(&self.hashes).map(|(format, content)| (format, format.get_digest(), content)) }