diff --git a/src/main.rs b/src/main.rs index 4935cf0..77587f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -174,6 +174,7 @@ pub enum FileSource { Cached { path: PathBuf, + filename: String, } } diff --git a/src/sources/curserinth.rs b/src/sources/curserinth.rs index 0c07500..2f380bb 100644 --- a/src/sources/curserinth.rs +++ b/src/sources/curserinth.rs @@ -120,7 +120,10 @@ impl<'a> CurserinthAPI<'a> { let cached_file_path = format!("{id}/{}/{}", version.id, file.filename); if self.0.has_in_cache("curserinth", &cached_file_path) { - Ok(FileSource::Cached { path: self.0.get_cache("curserinth").unwrap().0.join(cached_file_path) }) + Ok(FileSource::Cached { + path: self.0.get_cache("curserinth").unwrap().0.join(cached_file_path), + filename: file.filename, + }) } else { Ok(FileSource::Download { url: file.url, diff --git a/src/sources/fabric.rs b/src/sources/fabric.rs index 0064762..9f043e9 100644 --- a/src/sources/fabric.rs +++ b/src/sources/fabric.rs @@ -38,7 +38,10 @@ impl<'a> FabricAPI<'a> { let cached_file_path = format!("fabric-server-{}-{installer}-{loader}.jar", self.0.mc_version()); if self.0.has_in_cache("fabric", &cached_file_path) { - Ok(FileSource::Cached { path: self.0.get_cache("fabric").unwrap().0.join(cached_file_path) }) + Ok(FileSource::Cached { + path: self.0.get_cache("fabric").unwrap().0.join(cached_file_path), + filename: cached_file_path.clone(), + }) } else { Ok(FileSource::Download { url: format!( diff --git a/src/sources/github.rs b/src/sources/github.rs index 3707297..6b929f5 100644 --- a/src/sources/github.rs +++ b/src/sources/github.rs @@ -206,7 +206,8 @@ impl<'a> GithubAPI<'a> { if has_in_cache { Ok(FileSource::Cached { - path: self.0.get_cache(CACHE_DIR).unwrap().0.join(cached_file_path) + path: self.0.get_cache(CACHE_DIR).unwrap().0.join(cached_file_path), + filename: asset.name, }) } else { Ok(FileSource::Download { diff --git a/src/sources/hangar.rs b/src/sources/hangar.rs index fb7be4a..5238bf0 100644 --- a/src/sources/hangar.rs +++ b/src/sources/hangar.rs @@ -1,19 +1,25 @@ use std::collections::HashMap; -use anyhow::{anyhow, Result, Context}; -use mcapi::hangar::{VersionsFilter, ProjectVersion, Platform}; +use anyhow::{anyhow, Context, Result}; +use mcapi::hangar::{Platform, ProjectVersion}; -use crate::{App, FileSource, CacheStrategy}; +use crate::{App, CacheStrategy, FileSource}; pub struct HangarAPI<'a>(&'a App); impl<'a> HangarAPI<'a> { pub async fn fetch_hangar_version(&self, id: &str, version: &str) -> Result { - let filter = self.0.server.jar.get_hangar_versions_filter(&self.0.server.mc_version); + let filter = self + .0 + .server + .jar + .get_hangar_versions_filter(&self.0.server.mc_version); let version = if version == "latest" { - let versions = mcapi::hangar::fetch_project_versions(&self.0.http_client, id, Some(filter)).await?; - + let versions = + mcapi::hangar::fetch_project_versions(&self.0.http_client, id, Some(filter)) + .await?; + versions .result .iter() @@ -21,12 +27,14 @@ impl<'a> HangarAPI<'a> { .ok_or(anyhow!("No compatible versions for Hangar project '{id}'"))? .clone() } else if version.contains('$') { - let versions = mcapi::hangar::fetch_project_versions(&self.0.http_client, id, Some(filter)).await?; - + let versions = + mcapi::hangar::fetch_project_versions(&self.0.http_client, id, Some(filter)) + .await?; + let version = version .replace("${mcver}", &self.0.mc_version()) .replace("${mcversion}", &self.0.mc_version()); - + versions .result .iter() @@ -43,39 +51,54 @@ impl<'a> HangarAPI<'a> { } else { mcapi::hangar::fetch_project_version(&self.0.http_client, id, version).await? }; - + Ok(version) } pub async fn resolve_source(&self, id: &str, version: &str) -> Result { - let version = self.fetch_hangar_version(id, version) - .await.context("Fetching project version")?; + let version = self + .fetch_hangar_version(id, version) + .await + .context("Fetching project version")?; let download = version .downloads - .get(&self.0.server.jar.get_hangar_platform().unwrap_or(Platform::Paper)) + .get( + &self + .0 + .server + .jar + .get_hangar_platform() + .unwrap_or(Platform::Paper), + ) .ok_or(anyhow!( "Platform unsupported for Hangar project '{id}' version '{}'", version.name ))?; - + let cached_file_path = format!("{id}/{}/{}", version.name, download.get_file_info().name); if self.0.has_in_cache("hangar", &cached_file_path) { - Ok(FileSource::Cached { path: self.0.get_cache("hangar").unwrap().0.join(cached_file_path) }) + Ok(FileSource::Cached { + path: self.0.get_cache("hangar").unwrap().0.join(cached_file_path), + filename: download.get_file_info().name, + }) } else { Ok(FileSource::Download { url: download.get_url(), filename: download.get_file_info().name, cache: if let Some(cache) = self.0.get_cache("hangar") { - CacheStrategy::File { path: cache.0.join(cached_file_path) } + CacheStrategy::File { + path: cache.0.join(cached_file_path), + } } else { CacheStrategy::None }, size: Some(download.get_file_info().size_bytes as i32), - hashes: HashMap::from([ - ("sha256".to_owned(), download.get_file_info().sha256_hash) - ]) + hashes: HashMap::from([( + "sha256".to_owned(), + download.get_file_info().sha256_hash, + )]), }) } } diff --git a/src/sources/jenkins.rs b/src/sources/jenkins.rs index b066be2..d91ebfd 100644 --- a/src/sources/jenkins.rs +++ b/src/sources/jenkins.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; -use crate::{util::match_artifact_name, FileSource, CacheStrategy, App}; +use crate::{util::match_artifact_name, App, CacheStrategy, FileSource}; //pub static API_MAGIC_JOB: &str = "/api/json?tree=url,name,builds[*[url,number,result,artifacts[relativePath,fileName]]]"; static API_MAGIC_JOB: &str = "/api/json?tree=builds[*[url,number,result]]"; @@ -32,24 +32,27 @@ pub async fn resolve_source( let cached_file_path = format!("{folder}/{job}/{build_number}/{filename}"); if app.has_in_cache("jenkins", &cached_file_path) { - Ok(FileSource::Cached { path: app.get_cache("jenkins").unwrap().0.join(cached_file_path) }) + Ok(FileSource::Cached { + path: app.get_cache("jenkins").unwrap().0.join(cached_file_path), + filename, + }) } else { Ok(FileSource::Download { url: format!("{build_url}artifact/{relative_path}"), filename, cache: if let Some(cache) = app.get_cache("jenkins") { - CacheStrategy::File { path: cache.0.join(cached_file_path) } + CacheStrategy::File { + path: cache.0.join(cached_file_path), + } } else { CacheStrategy::None }, size: None, hashes: if let Some(md5) = md5hash { - HashMap::from([ - ("md5".to_owned(), md5.clone()) - ]) + HashMap::from([("md5".to_owned(), md5.clone())]) } else { HashMap::new() - } + }, }) } } @@ -135,7 +138,9 @@ pub async fn get_jenkins_filename( ))?; let md5hash = if let Some(serde_json::Value::Array(values)) = matched_build.get("fingerprint") { - values.iter().find(|v| v["fileName"].as_str().unwrap() == artifact["fileName"].as_str().unwrap()) + values + .iter() + .find(|v| v["fileName"].as_str().unwrap() == artifact["fileName"].as_str().unwrap()) .map(|v| v["hash"].as_str().unwrap().to_owned()) } else { None @@ -146,7 +151,7 @@ pub async fn get_jenkins_filename( artifact["fileName"].as_str().unwrap().to_owned(), artifact["relativePath"].as_str().unwrap().to_owned(), matched_build["number"].as_i64().unwrap(), - md5hash + md5hash, )) } @@ -157,7 +162,7 @@ pub async fn get_jenkins_download_url( build: &str, artifact: &str, ) -> Result { - let (build_url, _, relative_path, _build_number) = + let (build_url, _, relative_path, _build_number, _md5) = get_jenkins_filename(client, url, job, build, artifact).await?; Ok(build_url + "artifact/" + &relative_path) diff --git a/src/sources/modrinth.rs b/src/sources/modrinth.rs index 67cd470..003a5e3 100644 --- a/src/sources/modrinth.rs +++ b/src/sources/modrinth.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; -use anyhow::{bail, Result}; -use serde::{Deserialize, Serialize}; +use anyhow::{Result, anyhow}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; + +use crate::{App, FileSource, CacheStrategy}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ModrinthProject { @@ -96,105 +98,89 @@ pub struct ModrinthFile { // file_type omitted } -pub async fn fetch_modrinth_project(client: &reqwest::Client, id: &str) -> Result { - Ok(client - .get("https://api.modrinth.com/v2/project/".to_owned() + id) - .send() - .await? - .error_for_status()? - .json::() - .await?) -} - -pub async fn fetch_modrinth_filename( - id: &str, - version: &str, - client: &reqwest::Client, - query: Option<(&str, &str)>, -) -> Result { - let project = fetch_modrinth_versions(client, id, query).await?; - - let verdata = match version { - "latest" => project.first(), - id => project - .iter() - .find(|&v| v.id == id || v.version_number == id), - }; - - let Some(verdata) = verdata else { - bail!("Release '{version}' for project '{id}' not found"); - }; - - let Some(file) = verdata.files.first() else { - bail!("No files for project '{id}' version '{version}'"); - }; - - Ok(file.filename.clone()) -} - -pub async fn fetch_modrinth_versions( - client: &reqwest::Client, - id: &str, - query: Option<(&str, &str)>, -) -> Result> { - let versions: Vec = client - .get( - "https://api.modrinth.com/v2/project/".to_owned() - + id - + "/version" - + &(match query { - Some((jar, mcver)) => { - format!("?loaders=[\"{jar}\"]&game_versions=[\"{mcver}\"]") +pub struct ModrinthAPI<'a>(&'a App); + +static API_URL: &str = "https://api.modrinth.com/v2"; + +impl<'a> ModrinthAPI<'a> { + pub async fn fetch_api(&self, url: &str) -> Result { + let json: T = self.0.http_client.get(url).send().await?.error_for_status()?.json().await?; + + Ok(json) + } + + pub async fn fetch_project(&self, id: &str) -> Result { + self.fetch_api(&format!("{API_URL}/project/{id}")).await + } + + pub async fn fetch_all_versions(&self, id: &str) -> Result> { + self.fetch_api(&format!("{API_URL}/project/{id}/version")).await + } + + pub async fn fetch_versions(&self, id: &str) -> Result> { + let versions = self.fetch_all_versions(id).await?; + + Ok(self.0.server.filter_modrinth_versions(&versions)) + } + + pub async fn fetch_version(&self, id: &str, version: &str) -> Result { + let versions = self.fetch_versions(id).await?; + + let ver = version.replace("${mcver}", &self.0.mc_version()); + let ver = ver.replace("${mcversion}", &self.0.mc_version()); + + let version_data = match ver.as_str() { + "latest" => versions.first(), + ver => versions.iter().find(|v| v.id == ver || v.name == ver || v.version_number == ver) + }.ok_or(anyhow!("Couln't find version '{ver}' ('{version}') for Modrinth project '{id}'"))?.clone(); + + Ok(version_data) + } + + pub async fn fetch_file(&self, id: &str, version: &str) -> Result<(ModrinthFile, ModrinthVersion)> { + let version = self.fetch_version(id, version).await?; + + Ok(( + version.files.iter().find(|f| f.primary) + .ok_or(anyhow!("No primary file found on modrinth:{id}/{} ({})", version.id, version.name))?.clone(), + version + )) + } + + pub async fn search(&self, query: &str) -> Result> { + Ok(self.0.http_client.get(format!("{API_URL}/search")) + .query(&[("query", query), ("facets", &self.0.server.jar.get_modrinth_facets(&self.0.mc_version())?)]) + .send() + .await? + .error_for_status()? + .json() + .await?) + } + + pub async fn resolve_source(&self, id: &str, version: &str) -> Result { + let (file, version) = self.fetch_file(id, version).await?; + + let cached_file_path = format!("{id}/{}/{}", version.id, file.filename); + + if self.0.has_in_cache("modrinth", &cached_file_path) { + Ok(FileSource::Cached { + path: self.0.get_cache("modrinth").unwrap().0.join(cached_file_path), + filename: file.filename, + }) + } else { + Ok(FileSource::Download { + url: file.url, + filename: file.filename, + cache: if let Some(cache) = self.0.get_cache("modrinth") { + CacheStrategy::File { + path: cache.0.join(cached_file_path), } - None => String::new(), - }), - ) - .send() - .await? - .error_for_status()? - .json() - .await?; - - Ok(versions) -} - -pub async fn get_modrinth_url( - id: &str, - version: &str, - client: &reqwest::Client, - query: Option<(&str, &str)>, -) -> Result { - let project = fetch_modrinth_versions(client, id, query).await?; - - let verdata = match version { - "latest" => project.first(), - id => project.iter().find(|&v| v.id == id), - }; - - let Some(verdata) = verdata else { - bail!("Release '{version}' for project '{id}' not found"); - }; - - let Some(file) = verdata.files.first() else { - bail!("No files for project '{id}' version '{version}'"); - }; - - Ok(file.url.clone()) -} - -pub async fn search_modrinth( - client: &reqwest::Client, - query: &str, - facets: &str, -) -> Result> { - let res: ModrinthSearchResults = client - .get("https://api.modrinth.com/v2/search".to_owned()) - .query(&[("query", query), ("facets", facets)]) - .send() - .await? - .error_for_status()? - .json() - .await?; - - Ok(res.hits) + } else { + CacheStrategy::None + }, + size: Some(file.size), + hashes: file.hashes, + }) + } + } }