summaryrefslogtreecommitdiffstats
path: root/src/version.rs
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2024-12-22 23:48:39 -0600
committerLibravatar bigfoot547 <[email protected]>2024-12-22 23:48:39 -0600
commit1f5693c5531fa7ddf7bfdb8e27dc48d9765b97ca (patch)
tree72bd95bb067306a9cf4d133a510fd10590f54d12 /src/version.rs
parentuse paths instead of dumb strings (diff)
when the
Diffstat (limited to 'src/version.rs')
-rw-r--r--src/version.rs172
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(""));
+ }
}