Skip to content

Commit

Permalink
impl source maven
Browse files Browse the repository at this point in the history
  • Loading branch information
TheAlan404 committed Jun 18, 2024
1 parent 15b2e48 commit 64a3e51
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 0 deletions.
171 changes: 171 additions & 0 deletions src/api/sources/maven/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use anyhow::{anyhow, Result};

mod models;
pub use models::*;

use crate::api::{app::App, step::{CacheLocation, FileMeta, Step}, utils::url::url_to_folder};

pub struct MavenAPI<'a>(pub &'a App);

Check warning on line 8 in src/api/sources/maven/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `MavenAPI` is never constructed

warning: struct `MavenAPI` is never constructed --> src/api/sources/maven/mod.rs:8:12 | 8 | pub struct MavenAPI<'a>(pub &'a App); | ^^^^^^^^

impl<'a> MavenAPI<'a> {
pub fn get_metadata_url(url: &str, group_id: &str, artifact_id: &str) -> String {

Check warning on line 11 in src/api/sources/maven/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

multiple associated items are never used

warning: multiple associated items are never used --> src/api/sources/maven/mod.rs:11:12 | 10 | impl<'a> MavenAPI<'a> { | --------------------- associated items in this implementation 11 | pub fn get_metadata_url(url: &str, group_id: &str, artifact_id: &str) -> String { | ^^^^^^^^^^^^^^^^ ... 18 | pub async fn find_maven_artifact(&self, url: &str) -> Result<MavenMetadata> { | ^^^^^^^^^^^^^^^^^^^ ... 29 | pub fn guess_metadata_url(url: &str) -> Result<String> { | ^^^^^^^^^^^^^^^^^^ ... 53 | pub async fn fetch_metadata( | ^^^^^^^^^^^^^^ ... 63 | pub async fn fetch_metadata_url(&self, url: &str) -> Result<MavenMetadata> { | ^^^^^^^^^^^^^^^^^^ ... 76 | pub async fn fetch_versions( | ^^^^^^^^^^^^^^ ... 103 | pub async fn fetch_version( | ^^^^^^^^^^^^^ ... 129 | pub async fn resolve_steps( | ^^^^^^^^^^^^^
format!(
"{url}/{}/{artifact_id}/maven-metadata.xml",
group_id.replace(['.', ':'], "/")
)
}

pub async fn find_maven_artifact(&self, url: &str) -> Result<MavenMetadata> {
let metadata_url = if url.ends_with("/maven-metadata.xml") {
url.to_string()
} else {
Self::guess_metadata_url(url)?
};

self.fetch_metadata_url(&metadata_url).await
}

// @author ChatGPT
pub fn guess_metadata_url(url: &str) -> Result<String> {
// Attempt to construct the Maven metadata URL based on the provided URL
let segments: Vec<&str> = url.trim_end_matches('/').rsplit('/').collect();

if let Some(last_segment) = segments.first() {
if last_segment.is_empty() {
// If the last segment is empty, skip it
let metadata_url = format!("{}/maven-metadata.xml", url.trim_end_matches('/'));
return Ok(metadata_url);
}
}

if segments.len() >= 2 {
// Construct the Maven metadata URL by going up one level
let metadata_url = format!(
"{}/maven-metadata.xml",
url.trim_end_matches(segments[0]).trim_end_matches('/')
);
Ok(metadata_url)
} else {
Err(anyhow!("Invalid URL format"))
}
}

pub async fn fetch_metadata(
&self,
url: &str,
group_id: &str,
artifact_id: &str,
) -> Result<MavenMetadata> {
self.fetch_metadata_url(&Self::get_metadata_url(url, group_id, artifact_id))
.await
}

pub async fn fetch_metadata_url(&self, url: &str) -> Result<MavenMetadata> {
let xml = self.0.http_client.get(url).send().await?.text().await?;

let doc = roxmltree::Document::parse(&xml)?;

Ok(MavenMetadata {
latest: doc.get_text("latest").ok(),
artifact_id: doc.get_text("artifactId").ok(),
group_id: doc.get_text("groupId").ok(),
versions: doc.get_text_all("version"),
})
}

pub async fn fetch_versions(
&self,
url: &str,
group_id: &str,
artifact_id: &str,
) -> Result<(String, Vec<String>)> {
let xml = self
.0
.http_client
.get(Self::get_metadata_url(url, group_id, artifact_id))
.send()
.await?
.text()
.await?;

let doc = roxmltree::Document::parse(&xml)?;

let latest = doc.get_text("latest").ok();

let list = doc.get_text_all("version");

Ok((
latest.unwrap_or_else(|| list.first().cloned().unwrap_or_default()),
list,
))
}

pub async fn fetch_version(
&self,
url: &str,
group_id: &str,
artifact_id: &str,
version: &str,
) -> Result<String> {
let (latest, versions) = self.fetch_versions(url, group_id, artifact_id).await?;

let version = match version {
"latest" => latest,
id => {
let id = id
.replace("${artifact}", artifact_id);
versions
.iter()
.find(|v| *v == &id)
.or_else(|| versions.iter().find(|v| v.contains(&id)))
.ok_or(anyhow!("Couldn't resolve maven artifact version (url={url},g={group_id},a={artifact_id})"))?
.clone()
}
};

Ok(version)
}

pub async fn resolve_steps(
&self,
url: &str,
group_id: &str,
artifact_id: &str,
version: &str,
file: &str,
) -> Result<Vec<Step>> {
let version = self
.fetch_version(url, group_id, artifact_id, version)
.await?;

let file = file
.replace("${artifact}", artifact_id)
.replace("${version}", &version);

let file = if file.contains('.') {
file
} else {
format!("{file}.jar")
};

let download_url = format!(
"{url}/{}/{artifact_id}/{version}/{file}",
group_id.replace('.', "/"),
);

let metadata = FileMeta {
cache: Some(CacheLocation("maven".into(), format!(
"{}/{}/{artifact_id}/{version}/{file}",
url_to_folder(url),
group_id.replace('.', "/"),
))),
filename: file,
..Default::default()
};

Ok(vec![
Step::CacheCheck(metadata.clone()),
Step::Download { url: download_url, metadata },
])
}
}
51 changes: 51 additions & 0 deletions src/api/sources/maven/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use anyhow::{anyhow, Result};

pub trait XMLExt {
fn get_text(&self, k: &str) -> Result<String>;

Check warning on line 4 in src/api/sources/maven/models.rs

View workflow job for this annotation

GitHub Actions / clippy

methods `get_text` and `get_text_all` are never used

warning: methods `get_text` and `get_text_all` are never used --> src/api/sources/maven/models.rs:4:8 | 3 | pub trait XMLExt { | ------ methods in this trait 4 | fn get_text(&self, k: &str) -> Result<String>; | ^^^^^^^^ 5 | fn get_text_all(&self, k: &str) -> Vec<String>; | ^^^^^^^^^^^^
fn get_text_all(&self, k: &str) -> Vec<String>;
}

impl XMLExt for roxmltree::Document<'_> {
fn get_text(&self, k: &str) -> Result<String> {
self.descendants()
.find_map(|elem| {
if elem.tag_name().name() == k {
Some(elem.text()?.to_owned())
} else {
None
}
})
.ok_or(anyhow!("XML element not found: {}", k))
}

fn get_text_all(&self, k: &str) -> Vec<String> {
self.descendants()
.filter_map(|t| {
if t.tag_name().name() == k {
Some(t.text()?.to_owned())
} else {
None
}
})
.collect::<Vec<_>>()
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct MavenMetadata {
pub latest: Option<String>,
pub group_id: Option<String>,
pub artifact_id: Option<String>,
pub versions: Vec<String>,
}

impl MavenMetadata {
pub fn find_url(&self, url: &str) -> Option<(String, String)> {

Check warning on line 43 in src/api/sources/maven/models.rs

View workflow job for this annotation

GitHub Actions / clippy

method `find_url` is never used

warning: method `find_url` is never used --> src/api/sources/maven/models.rs:43:12 | 42 | impl MavenMetadata { | ------------------ method in this implementation 43 | pub fn find_url(&self, url: &str) -> Option<(String, String)> { | ^^^^^^^^
let t = url.split_once(&format!(
"{}/{}",
self.group_id.clone()?.replace(['.', ':'], "/"),
self.artifact_id.clone()?
))?;
Some((t.0.to_owned(), t.1.to_owned()))
}
}
1 change: 1 addition & 0 deletions src/api/sources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub mod url;
pub mod vanilla;
pub mod mclogs;
pub mod papermc;
pub mod maven;
9 changes: 9 additions & 0 deletions src/api/utils/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,12 @@ pub fn get_filename_from_url(url: &str) -> String {
let url_clean = url.split(&['?', '#'][..]).next().unwrap();
url_clean.split('/').last().unwrap().to_string()
}

/// ci.luckto.me => ci-lucko-me
pub fn url_to_folder(url: &str) -> String {

Check warning on line 7 in src/api/utils/url.rs

View workflow job for this annotation

GitHub Actions / clippy

function `url_to_folder` is never used

warning: function `url_to_folder` is never used --> src/api/utils/url.rs:7:8 | 7 | pub fn url_to_folder(url: &str) -> String { | ^^^^^^^^^^^^^
url.replace("https://", "")
.replace("http://", "")
.replace('/', " ")
.trim()
.replace(' ', "-")
}

0 comments on commit 64a3e51

Please sign in to comment.