You've already forked AstralRinth
forked from didirus/AstralRinth
Daedalus Rewrite + Code Cleanup (#16)
* [wip] rewrite daedalus, vanilla, fabric, and quilt * finish forge + neo * fix docker * fix neoforge 1.21+ * update concurrency limit * finish * remove mac garb
This commit is contained in:
@@ -12,30 +12,6 @@ pub mod modded;
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// An error type representing possible errors when fetching metadata
|
||||
pub enum Error {
|
||||
#[error("Failed to validate file checksum at url {url} with hash {hash} after {tries} tries")]
|
||||
/// A checksum was failed to validate for a file
|
||||
ChecksumFailure {
|
||||
/// The checksum's hash
|
||||
hash: String,
|
||||
/// The URL of the file attempted to be downloaded
|
||||
url: String,
|
||||
/// The amount of tries that the file was downloaded until failure
|
||||
tries: u32,
|
||||
},
|
||||
/// There was an error while deserializing metadata
|
||||
#[error("Error while deserializing JSON")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
/// There was a network error when fetching an object
|
||||
#[error("Unable to fetch {item}")]
|
||||
FetchError {
|
||||
/// The internal reqwest error
|
||||
inner: reqwest::Error,
|
||||
/// The item that was failed to be fetched
|
||||
item: String,
|
||||
},
|
||||
/// There was an error when managing async tasks
|
||||
#[error("Error while managing asynchronous tasks")]
|
||||
TaskError(#[from] tokio::task::JoinError),
|
||||
/// Error while parsing input
|
||||
#[error("{0}")]
|
||||
ParseError(String),
|
||||
@@ -124,100 +100,3 @@ pub fn get_path_from_artifact(artifact: &str) -> Result<String, Error> {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads a file from specified mirrors
|
||||
pub async fn download_file_mirrors(
|
||||
base: &str,
|
||||
mirrors: &[&str],
|
||||
sha1: Option<&str>,
|
||||
) -> Result<bytes::Bytes, Error> {
|
||||
if mirrors.is_empty() {
|
||||
return Err(Error::ParseError("No mirrors provided!".to_string()));
|
||||
}
|
||||
|
||||
for (index, mirror) in mirrors.iter().enumerate() {
|
||||
let result = download_file(&format!("{}{}", mirror, base), sha1).await;
|
||||
|
||||
if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Downloads a file with retry and checksum functionality
|
||||
pub async fn download_file(
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
) -> Result<bytes::Bytes, Error> {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
|
||||
"modrinth/daedalus/{} (support@modrinth.com)",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
)) {
|
||||
headers.insert(reqwest::header::USER_AGENT, header);
|
||||
}
|
||||
let client = reqwest::Client::builder()
|
||||
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.map_err(|err| Error::FetchError {
|
||||
inner: err,
|
||||
item: url.to_string(),
|
||||
})?;
|
||||
|
||||
for attempt in 1..=4 {
|
||||
let result = client.get(url).send().await;
|
||||
|
||||
match result {
|
||||
Ok(x) => {
|
||||
let bytes = x.bytes().await;
|
||||
|
||||
if let Ok(bytes) = bytes {
|
||||
if let Some(sha1) = sha1 {
|
||||
if &*get_hash(bytes.clone()).await? != sha1 {
|
||||
if attempt <= 3 {
|
||||
continue;
|
||||
} else {
|
||||
return Err(Error::ChecksumFailure {
|
||||
hash: sha1.to_string(),
|
||||
url: url.to_string(),
|
||||
tries: attempt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(bytes);
|
||||
} else if attempt <= 3 {
|
||||
continue;
|
||||
} else if let Err(err) = bytes {
|
||||
return Err(Error::FetchError {
|
||||
inner: err,
|
||||
item: url.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(_) if attempt <= 3 => continue,
|
||||
Err(err) => {
|
||||
return Err(Error::FetchError {
|
||||
inner: err,
|
||||
item: url.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Computes a checksum of the input bytes
|
||||
pub async fn get_hash(bytes: bytes::Bytes) -> Result<String, Error> {
|
||||
let hash =
|
||||
tokio::task::spawn_blocking(|| sha1::Sha1::from(bytes).hexdigest())
|
||||
.await?;
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
use crate::modded::{Processor, SidedDataEntry};
|
||||
use crate::{download_file, Error};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
/// The latest version of the format the model structs deserialize to
|
||||
pub const CURRENT_FORMAT_VERSION: usize = 0;
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// The version type
|
||||
@@ -37,7 +32,6 @@ impl VersionType {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A game version of Minecraft
|
||||
@@ -50,26 +44,18 @@ pub struct Version {
|
||||
/// A link to additional information about the version
|
||||
pub url: String,
|
||||
/// The latest time a file in this version was updated
|
||||
#[cfg_attr(feature = "bincode", bincode(with_serde))]
|
||||
pub time: DateTime<Utc>,
|
||||
/// The time this version was released
|
||||
#[cfg_attr(feature = "bincode", bincode(with_serde))]
|
||||
pub release_time: DateTime<Utc>,
|
||||
/// The SHA1 hash of the additional information about the version
|
||||
pub sha1: String,
|
||||
/// Whether the version supports the latest player safety features
|
||||
pub compliance_level: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// (Modrinth Provided) The link to the assets index for this version
|
||||
/// This is only available when using the Modrinth mirror
|
||||
pub assets_index_url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// (Modrinth Provided) The SHA1 hash of the assets index for this version
|
||||
/// This is only available when using the Modrinth mirror
|
||||
pub assets_index_sha1: Option<String>,
|
||||
/// (Modrinth Provided) The SHA1 hash of the original unmodified Minecraft versions JSON
|
||||
pub original_sha1: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// The latest snapshot and release of the game
|
||||
pub struct LatestVersion {
|
||||
@@ -79,7 +65,6 @@ pub struct LatestVersion {
|
||||
pub snapshot: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// Data of all game versions of Minecraft
|
||||
pub struct VersionManifest {
|
||||
@@ -93,16 +78,6 @@ pub struct VersionManifest {
|
||||
pub const VERSION_MANIFEST_URL: &str =
|
||||
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
|
||||
|
||||
/// Fetches a version manifest from the specified URL. If no URL is specified, the default is used.
|
||||
pub async fn fetch_version_manifest(
|
||||
url: Option<&str>,
|
||||
) -> Result<VersionManifest, Error> {
|
||||
Ok(serde_json::from_slice(
|
||||
&download_file(url.unwrap_or(VERSION_MANIFEST_URL), None).await?,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Information about the assets of the game
|
||||
@@ -119,7 +94,6 @@ pub struct AssetIndex {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// The type of download
|
||||
@@ -136,7 +110,6 @@ pub enum DownloadType {
|
||||
WindowsServer,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
/// Download information of a file
|
||||
pub struct Download {
|
||||
@@ -148,7 +121,6 @@ pub struct Download {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// Download information of a library
|
||||
pub struct LibraryDownload {
|
||||
@@ -163,7 +135,6 @@ pub struct LibraryDownload {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A list of files that should be downloaded for libraries
|
||||
pub struct LibraryDownloads {
|
||||
@@ -176,7 +147,6 @@ pub struct LibraryDownloads {
|
||||
pub classifiers: Option<HashMap<String, LibraryDownload>>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// The action a rule can follow
|
||||
@@ -187,7 +157,6 @@ pub enum RuleAction {
|
||||
Disallow,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
/// An enum representing the different types of operating systems
|
||||
@@ -210,7 +179,6 @@ pub enum Os {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A rule which depends on what OS the user is on
|
||||
pub struct OsRule {
|
||||
@@ -225,7 +193,6 @@ pub struct OsRule {
|
||||
pub arch: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A rule which depends on the toggled features of the launcher
|
||||
pub struct FeatureRule {
|
||||
@@ -248,7 +215,6 @@ pub struct FeatureRule {
|
||||
pub is_quick_play_realms: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A rule deciding whether a file is downloaded, an argument is used, etc.
|
||||
pub struct Rule {
|
||||
@@ -262,7 +228,6 @@ pub struct Rule {
|
||||
pub features: Option<FeatureRule>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// Information delegating the extraction of the library
|
||||
pub struct LibraryExtract {
|
||||
@@ -271,7 +236,6 @@ pub struct LibraryExtract {
|
||||
pub exclude: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Information about the java version the game needs
|
||||
@@ -282,7 +246,6 @@ pub struct JavaVersion {
|
||||
pub major_version: u32,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A library which the game relies on to run
|
||||
pub struct Library {
|
||||
@@ -309,6 +272,9 @@ pub struct Library {
|
||||
#[serde(default = "default_include_in_classpath")]
|
||||
/// Whether the library should be included in the classpath at the game's launch
|
||||
pub include_in_classpath: bool,
|
||||
#[serde(default = "default_downloadable")]
|
||||
/// Whether the library should be downloaded
|
||||
pub downloadable: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
@@ -397,8 +363,10 @@ pub fn merge_partial_library(
|
||||
fn default_include_in_classpath() -> bool {
|
||||
true
|
||||
}
|
||||
fn default_downloadable() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
/// A container for an argument or multiple arguments
|
||||
@@ -409,7 +377,6 @@ pub enum ArgumentValue {
|
||||
Many(Vec<String>),
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
/// A command line argument passed to a program
|
||||
@@ -425,7 +392,6 @@ pub enum Argument {
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// The type of argument
|
||||
@@ -436,7 +402,6 @@ pub enum ArgumentType {
|
||||
Jvm,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// Information about a version
|
||||
@@ -481,16 +446,6 @@ pub struct VersionInfo {
|
||||
pub processors: Option<Vec<Processor>>,
|
||||
}
|
||||
|
||||
/// Fetches detailed information about a version from the manifest
|
||||
pub async fn fetch_version_info(
|
||||
version: &Version,
|
||||
) -> Result<VersionInfo, Error> {
|
||||
Ok(serde_json::from_slice(
|
||||
&download_file(&version.url, Some(&version.sha1)).await?,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
/// An asset of the game
|
||||
pub struct Asset {
|
||||
@@ -500,23 +455,9 @@ pub struct Asset {
|
||||
pub size: u32,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
/// An index containing all assets the game needs
|
||||
pub struct AssetsIndex {
|
||||
/// A hashmap containing the filename (key) and asset (value)
|
||||
pub objects: HashMap<String, Asset>,
|
||||
}
|
||||
|
||||
/// Fetches the assets index from the version info
|
||||
pub async fn fetch_assets_index(
|
||||
version: &VersionInfo,
|
||||
) -> Result<AssetsIndex, Error> {
|
||||
Ok(serde_json::from_slice(
|
||||
&download_file(
|
||||
&version.asset_index.url,
|
||||
Some(&version.asset_index.sha1),
|
||||
)
|
||||
.await?,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use crate::{download_file, Error};
|
||||
|
||||
use crate::minecraft::{
|
||||
Argument, ArgumentType, Library, VersionInfo, VersionType,
|
||||
};
|
||||
@@ -7,9 +5,6 @@ use chrono::{DateTime, TimeZone, Utc};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "bincode")]
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
/// The latest version of the format the fabric model structs deserialize to
|
||||
pub const CURRENT_FABRIC_FORMAT_VERSION: usize = 0;
|
||||
/// The latest version of the format the fabric model structs deserialize to
|
||||
@@ -23,7 +18,6 @@ pub const CURRENT_NEOFORGE_FORMAT_VERSION: usize = 0;
|
||||
pub const DUMMY_REPLACE_STRING: &str = "${modrinth.gameVersion}";
|
||||
|
||||
/// A data variable entry that depends on the side of the installation
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SidedDataEntry {
|
||||
/// The value on the client
|
||||
@@ -43,7 +37,6 @@ where
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A partial version returned by fabric meta
|
||||
@@ -53,11 +46,9 @@ pub struct PartialVersionInfo {
|
||||
/// The version ID this partial version inherits from
|
||||
pub inherits_from: String,
|
||||
/// The time that the version was released
|
||||
#[cfg_attr(feature = "bincode", bincode(with_serde))]
|
||||
#[serde(deserialize_with = "deserialize_date")]
|
||||
pub release_time: DateTime<Utc>,
|
||||
/// The latest time a file in this version was updated
|
||||
#[cfg_attr(feature = "bincode", bincode(with_serde))]
|
||||
#[serde(deserialize_with = "deserialize_date")]
|
||||
pub time: DateTime<Utc>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -83,7 +74,6 @@ pub struct PartialVersionInfo {
|
||||
}
|
||||
|
||||
/// A processor to be ran after downloading the files
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Processor {
|
||||
/// Maven coordinates for the JAR library of this processor.
|
||||
@@ -101,13 +91,6 @@ pub struct Processor {
|
||||
pub sides: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Fetches the version manifest of a game version's URL
|
||||
pub async fn fetch_partial_version(
|
||||
url: &str,
|
||||
) -> Result<PartialVersionInfo, Error> {
|
||||
Ok(serde_json::from_slice(&download_file(url, None).await?)?)
|
||||
}
|
||||
|
||||
/// Merges a partial version into a complete one
|
||||
pub fn merge_partial_version(
|
||||
partial: PartialVersionInfo,
|
||||
@@ -154,15 +137,10 @@ pub fn merge_partial_version(
|
||||
.libraries
|
||||
.into_iter()
|
||||
.chain(merge.libraries)
|
||||
.map(|x| Library {
|
||||
downloads: x.downloads,
|
||||
extract: x.extract,
|
||||
name: x.name.replace(DUMMY_REPLACE_STRING, &merge_id),
|
||||
url: x.url,
|
||||
natives: x.natives,
|
||||
rules: x.rules,
|
||||
checksums: x.checksums,
|
||||
include_in_classpath: x.include_in_classpath,
|
||||
.map(|mut x| {
|
||||
x.name = x.name.replace(DUMMY_REPLACE_STRING, &merge_id);
|
||||
|
||||
x
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
main_class: if let Some(main_class) = partial.main_class {
|
||||
@@ -180,7 +158,6 @@ pub fn merge_partial_version(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
/// A manifest containing information about a mod loader's versions
|
||||
@@ -189,7 +166,6 @@ pub struct Manifest {
|
||||
pub game_versions: Vec<Version>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A game version of Minecraft
|
||||
pub struct Version {
|
||||
@@ -201,7 +177,6 @@ pub struct Version {
|
||||
pub loaders: Vec<LoaderVersion>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
/// A version of a Minecraft mod loader
|
||||
pub struct LoaderVersion {
|
||||
@@ -212,8 +187,3 @@ pub struct LoaderVersion {
|
||||
/// Whether the loader is stable or not
|
||||
pub stable: bool,
|
||||
}
|
||||
|
||||
/// Fetches the manifest of a mod loader
|
||||
pub async fn fetch_manifest(url: &str) -> Result<Manifest, Error> {
|
||||
Ok(serde_json::from_slice(&download_file(url, None).await?)?)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user