diff options
| author | 2024-12-22 23:48:39 -0600 | |
|---|---|---|
| committer | 2024-12-22 23:48:39 -0600 | |
| commit | 1f5693c5531fa7ddf7bfdb8e27dc48d9765b97ca (patch) | |
| tree | 72bd95bb067306a9cf4d133a510fd10590f54d12 /src/version.rs | |
| parent | use paths instead of dumb strings (diff) | |
when the
Diffstat (limited to 'src/version.rs')
| -rw-r--r-- | src/version.rs | 172 |
1 files changed, 149 insertions, 23 deletions
diff --git a/src/version.rs b/src/version.rs index 0c1e65f..190dd72 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,12 +1,15 @@ use core::fmt; use std::{collections::BTreeMap, convert::Infallible, marker::PhantomData, ops::Deref, str::FromStr}; - +use std::ascii::AsciiExt; +use std::collections::HashMap; use chrono::{DateTime, Utc}; use regex::Regex; use serde::{de::{self, Visitor}, Deserialize, Deserializer}; +use serde::de::SeqAccess; pub mod manifest; use manifest::*; +use crate::util::Sha1Digest; #[derive(Deserialize, Debug, Clone, Copy)] #[serde(rename_all = "lowercase")] @@ -29,7 +32,7 @@ pub enum OperatingSystem { Unknown // (not used in official jsons) } -#[derive(Debug)] +#[derive(Debug, Clone)] struct WrappedRegex(Regex); impl Deref for WrappedRegex { @@ -63,7 +66,7 @@ impl<'de> Deserialize<'de> for WrappedRegex { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct OSRestriction { pub name: Option<OperatingSystem>, @@ -71,7 +74,7 @@ pub struct OSRestriction { pub arch: Option<WrappedRegex> } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct CompatibilityRule { pub action: RuleAction, pub features: Option<BTreeMap<String, bool>>, @@ -92,7 +95,7 @@ impl CompatibilityRule { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Argument { #[serde(default)] pub rules: Option<Vec<CompatibilityRule>>, @@ -102,7 +105,7 @@ pub struct Argument { pub value: Vec<String> } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WrappedArgument(Argument); impl FromStr for Argument { @@ -132,13 +135,29 @@ impl<'de> Deserialize<'de> for WrappedArgument { } } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Arguments { pub game: Option<Vec<WrappedArgument>>, pub jvm: Option<Vec<WrappedArgument>> } -#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +impl Arguments { + fn apply_child(&mut self, other: &Arguments) { + if self.game.is_none() { + if let Some(game) = other.game.as_ref() { + self.game.replace(game.to_owned()); + } + } + + if self.jvm.is_none() { + if let Some(jvm) = other.jvm.as_ref() { + self.jvm.replace(jvm.to_owned()); + } + } + } +} + +#[derive(Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum DownloadType { Client, @@ -148,9 +167,9 @@ pub enum DownloadType { WindowsServer } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct DownloadInfo { - pub sha1: Option<String>, + pub sha1: Option<Sha1Digest>, pub size: Option<usize>, pub total_size: Option<usize>, // available for asset index pub url: Option<String>, // may not be present for libraries @@ -158,26 +177,26 @@ pub struct DownloadInfo { pub path: Option<String> } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct JavaVersionInfo { pub component: String, pub major_version: u32 } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct LibraryDownloads { pub artifact: Option<DownloadInfo>, pub classifiers: Option<BTreeMap<String, DownloadInfo>> } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct LibraryExtractRule { #[serde(default)] pub exclude: Vec<String> } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Library { pub downloads: Option<LibraryDownloads>, pub name: String, @@ -187,7 +206,7 @@ pub struct Library { pub url: Option<String> // old format } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct ClientLogging { pub argument: String, @@ -196,12 +215,12 @@ pub struct ClientLogging { pub file: DownloadInfo } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct Logging { pub client: ClientLogging // other fields unknown } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct CompleteVersion { pub arguments: Option<Arguments>, @@ -217,8 +236,8 @@ pub struct CompleteVersion { #[serde(default)] pub downloads: BTreeMap<DownloadType, DownloadInfo>, - #[serde(default)] - pub libraries: Vec<Library>, + #[serde(default, deserialize_with = "deserialize_libraries")] + pub libraries: HashMap<String, Library>, pub id: String, pub jar: Option<String>, // used as the jar filename if specified? (no longer used officially) @@ -243,6 +262,104 @@ pub struct CompleteVersion { */ } +impl CompleteVersion { + pub fn get_jar(&self) -> &String { + &self.jar.as_ref().unwrap_or(&self.id) + } + + pub fn apply_child(&mut self, other: &CompleteVersion) { + macro_rules! replace_missing { + ($name:ident) => { + if self.$name.is_none() { + if let Some($name) = other.$name.as_ref() { + self.$name.replace($name.to_owned()); + } + } + }; + } + + if let Some(arguments) = other.arguments.as_ref() { + if let Some(my_args) = self.arguments.as_mut() { + my_args.apply_child(arguments); + } else { + self.arguments.replace(arguments.to_owned()); + } + } + + replace_missing!(minecraft_arguments); + replace_missing!(asset_index); + replace_missing!(assets); + replace_missing!(compliance_level); + replace_missing!(java_version); + + for (dltype, dl) in other.downloads.iter().by_ref() { + self.downloads.entry(*dltype).or_insert_with(|| dl.clone()); + } + + for (name, lib) in other.libraries.iter().by_ref() { + self.libraries.entry(name.to_owned()).or_insert_with(|| lib.clone()); + } + + replace_missing!(logging); + replace_missing!(main_class); + replace_missing!(minimum_launcher_version); + replace_missing!(release_time); + replace_missing!(time); + replace_missing!(version_type); + + if let Some(rules) = other.compatibility_rules.as_ref() { + if let Some(my_rules) = self.compatibility_rules.as_mut() { + for rule in rules { + my_rules.push(rule.to_owned()); + } + } else { + self.compatibility_rules.replace(rules.to_owned()); + } + } + + replace_missing!(incompatibility_reason); + } +} + +fn canonicalize_library_name(name: &str) -> String { + name.split(':') + .enumerate() + .filter(|(i, _)| *i != 2) + .map(|(_, s)| s.to_ascii_lowercase()) + .collect::<Vec<_>>() + .join(":") +} + +fn deserialize_libraries<'de, D>(deserializer: D) -> Result<HashMap<String, Library>, D::Error> +where + D: Deserializer<'de> +{ + struct LibrariesVisitor; + + impl<'de> Visitor<'de> for LibrariesVisitor { + type Value = HashMap<String, Library>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an array of libraries") + } + + fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> + where + A: SeqAccess<'de>, + { + let mut map = HashMap::new(); + + while let Some(lib) = seq.next_element::<Library>()? { + map.insert(canonicalize_library_name(lib.name.as_str()), lib); + } + + Ok(map) + } + } + + deserializer.deserialize_seq(LibrariesVisitor) +} + // https://serde.rs/string-or-struct.html fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error> where @@ -257,19 +374,19 @@ where { type Value = T; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string or map") } fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where - E: serde::de::Error, { + E: de::Error, { Ok(FromStr::from_str(v).unwrap()) } fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error> where - A: serde::de::MapAccess<'de>, { + A: de::MapAccess<'de>, { // wizardry (check comment in link) Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) } @@ -304,7 +421,7 @@ where fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error> where - A: de::SeqAccess<'de>, { + A: SeqAccess<'de>, { Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) } } @@ -333,4 +450,13 @@ mod tests { let arg: VersionManifest = serde_json::from_str(s.unwrap().as_str()).unwrap(); dbg!(arg); } + + #[test] + fn test_it3() { + assert_eq!(canonicalize_library_name("group:artifact:version"), String::from("group:artifact")); + assert_eq!(canonicalize_library_name("group:artifact:version:specifier"), String::from("group:artifact:specifier")); + assert_eq!(canonicalize_library_name("not_enough:fields"), String::from("not_enough:fields")); + assert_eq!(canonicalize_library_name("word"), String::from("word")); + assert_eq!(canonicalize_library_name(""), String::from("")); + } } |
