use async_trait::async_trait;
use serde::Deserialize;
use super::{DownloadSource, Downloader};
use crate::{package::PackageName, prelude::*, version::structs::VersionMeta};
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct LoaderMetaItem {
pub loader: LoaderStruct,
pub intermediary: IntermediaryStruct,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct IntermediaryStruct {
pub maven: String,
pub version: String,
pub stable: bool,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct LoaderStruct {
pub maven: String,
pub version: String,
pub stable: bool,
}
#[async_trait]
pub trait FabricDownloadExt: Sync {
async fn get_avaliable_loaders(&self, vanilla_version: &str) -> DynResult<Vec<LoaderMetaItem>>;
async fn download_library(&self, name: &str) -> DynResult;
async fn download_fabric_pre(
&self,
version_name: &str,
version_id: &str,
loader_version: &str,
) -> DynResult;
async fn download_fabric_post(&self, version_name: &str) -> DynResult;
}
#[async_trait]
impl<R: Reporter> FabricDownloadExt for Downloader<R> {
async fn get_avaliable_loaders(&self, vanilla_version: &str) -> DynResult<Vec<LoaderMetaItem>> {
let mut result = crate::http::retry_get(match self.source {
DownloadSource::Default => {
format!("https://meta.fabricmc.net/v2/versions/loader/{vanilla_version}")
}
DownloadSource::BMCLAPI => format!(
"https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/{vanilla_version}"
),
DownloadSource::MCBBS => format!(
"https://download.mcbbs.net/fabric-meta/v2/versions/loader/{vanilla_version}"
),
_ => format!("https://meta.fabricmc.net/v2/versions/loader/{vanilla_version}"),
})
.await
.map_err(|e| {
anyhow::anyhow!(
"获取为原版 {} 可用的 Fabric Loader 版本失败 {:?}",
vanilla_version,
e
)
})?;
if result.status().is_success() {
let result = result.body_json().await.map_err(|e| anyhow::anyhow!(e))?;
Ok(result)
} else {
Ok(vec![])
}
}
async fn download_library(&self, name: &str) -> DynResult {
let package_name = name.parse::<PackageName>().unwrap();
let full_path = package_name.to_maven_jar_path(self.minecraft_library_path.as_str());
let r = self.reporter.sub();
inner_future::fs::create_dir_all(
&full_path[..full_path.rfind('/').unwrap_or(full_path.len())],
)
.await
.unwrap_or_default();
r.set_message(format!("正在下载 Fabric 支持库 {name}"));
r.add_max_progress(1.);
if std::path::Path::new(&full_path).is_file() {
if self.verify_data {
let mut file = inner_future::fs::OpenOptions::new()
.read(true)
.open(&full_path)
.await?;
r.set_message(format!("正在获取数据摘要以验证完整性 {name}"));
r.add_max_progress(1.);
let sha1 = crate::http::retry_get_string(format!(
"{}.sha1",
package_name.to_maven_jar_path(match self.source {
DownloadSource::BMCLAPI => "https://bmclapi2.bangbang93.com/maven",
DownloadSource::MCBBS => "https://download.mcbbs.net/maven",
_ => "https://maven.fabricmc.net",
})
))
.await?;
let current_sha1 = crate::utils::get_data_sha1(&mut file).await?;
if sha1 == current_sha1 {
r.add_progress(1.);
return Ok(());
}
} else {
r.add_progress(1.);
return Ok(());
}
}
let uris = [
package_name.to_maven_jar_path(match self.source {
DownloadSource::BMCLAPI => "https://bmclapi2.bangbang93.com/maven",
DownloadSource::MCBBS => "https://download.mcbbs.net/maven",
_ => "https://maven.fabricmc.net",
}),
package_name.to_maven_jar_path("https://bmclapi2.bangbang93.com/maven"),
package_name.to_maven_jar_path("https://download.mcbbs.net/maven"),
package_name.to_maven_jar_path("https://maven.fabricmc.net"),
];
crate::http::download(&uris, &full_path, 0)
.await
.map_err(|e| anyhow::anyhow!("下载 Fabric 依赖库失败:{:?}", e))?;
Ok(())
}
async fn download_fabric_pre(
&self,
version_name: &str,
version_id: &str,
loader_version: &str,
) -> DynResult {
let mut loader_meta_res = crate::http::retry_get(format!(
"https://meta.fabricmc.net/v2/versions/loader/{version_id}/{loader_version}/profile/json"
))
.await
.map_err(|e| anyhow::anyhow!("获取 Fabric 版本元数据失败:{:?}", e))?;
let res = loader_meta_res
.body_bytes()
.await
.map_err(|e| anyhow::anyhow!(e))?;
inner_future::fs::write(
format!(
"{}/{}/{}-fabric-loader.tmp.json",
self.minecraft_version_path.as_str(),
version_name,
version_name
),
&res,
)
.await?;
let meta: VersionMeta = serde_json::from_slice(&res)?;
let mut libraries_threads = Vec::with_capacity(meta.libraries.len());
for lib in &meta.libraries {
if !lib.name.is_empty() {
libraries_threads.push(self.download_library(&lib.name));
}
}
futures::future::try_join_all(libraries_threads).await?;
Ok(())
}
async fn download_fabric_post(&self, version_name: &str) -> DynResult {
let vanilla_path = format!(
"{}/{}/{}.json",
self.minecraft_version_path.as_str(),
version_name,
version_name
);
let vanilla_meta = crate::prelude::inner_future::fs::read(&vanilla_path).await?;
let loader_path = format!(
"{}/{}/{}-fabric-loader.tmp.json",
self.minecraft_version_path.as_str(),
version_name,
version_name
);
let loader_meta = crate::prelude::inner_future::fs::read(&loader_path).await?;
inner_future::fs::remove_file(loader_path).await?;
let mut vanilla_meta: VersionMeta = serde_json::from_slice(&vanilla_meta)?;
let loader_meta: VersionMeta = serde_json::from_slice(&loader_meta)?;
vanilla_meta += loader_meta;
inner_future::fs::write(&vanilla_path, serde_json::to_vec(&vanilla_meta)?).await?;
Ok(())
}
}