summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-01-13 20:58:36 -0600
committerLibravatar bigfoot547 <[email protected]>2025-01-13 20:58:36 -0600
commitff428f36935cefd2b6d8ea6ba4a0572d75d6512d (patch)
treecf8b71137247ba74c4a606146d45ec2791725877
parentmore stuff (diff)
library downloads complete
-rw-r--r--.gitignore1
-rw-r--r--ozone-cli/src/main.rs7
-rw-r--r--src/launcher.rs61
-rw-r--r--src/launcher/download.rs4
-rw-r--r--src/launcher/rules.rs114
-rw-r--r--src/launcher/version.rs13
-rw-r--r--src/version.rs46
7 files changed, 219 insertions, 27 deletions
diff --git a/.gitignore b/.gitignore
index 40eb61d..f8f6cd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
/test_stuff
+/work
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs
index dbe591a..67932da 100644
--- a/ozone-cli/src/main.rs
+++ b/ozone-cli/src/main.rs
@@ -2,6 +2,7 @@ use std::env::consts::{ARCH, OS};
use std::error::Error;
use std::path::PathBuf;
use sysinfo::System;
+use o3launcher::launcher::Profile;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
@@ -12,6 +13,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
println!("stuff: {:?} {:?} {:?} {}", System::distribution_id(), OS, ARCH, size_of::<*const i32>());
let launcher = o3launcher::launcher::Launcher::new(PathBuf::from("./work").as_path(), true).await?;
+ let profile = Profile {
+ version_id: "1.21.4".into(),
+ java_runtime: None,
+ instance: "".into()
+ };
+ launcher.prepare_launch(&profile).await?;
println!("ok");
Ok(())
diff --git a/src/launcher.rs b/src/launcher.rs
index a849cc5..83ec342 100644
--- a/src/launcher.rs
+++ b/src/launcher.rs
@@ -3,6 +3,7 @@ mod version;
mod profile;
mod strsub;
mod download;
+mod rules;
use std::borrow::Cow;
use std::collections::HashMap;
@@ -12,19 +13,18 @@ use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use const_format::formatcp;
-use futures::TryFutureExt;
-use log::{debug, warn};
+use futures::StreamExt;
+use log::{debug, info, warn};
use serde::{Deserialize, Serialize};
-use sha1_smol::Sha1;
use sysinfo::System;
-use tokio::fs::File;
use tokio::{fs, io};
-use tokio::io::AsyncReadExt;
use version::VersionList;
-use profile::{Instance, Profile};
-use crate::launcher::download::{Download, VerifiedDownload};
-use crate::launcher::version::{VersionResolveError, VersionResult};
-use crate::version::{DownloadInfo, Library, OSRestriction, OperatingSystem};
+use download::{MultiDownloader, PhaseDownloadError, VerifiedDownload};
+use rules::{CompatCheck, IncompatibleError};
+use version::{VersionResolveError, VersionResult};
+use crate::version::{Library, OSRestriction, OperatingSystem};
+
+pub use profile::{Instance, Profile};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Settings {
@@ -95,9 +95,14 @@ pub struct Launcher {
#[derive(Debug)]
pub enum LaunchError {
+ // version resolution errors
UnknownVersion(String),
LoadVersion(Box<dyn Error>),
- ResolveVersion(VersionResolveError)
+ ResolveVersion(VersionResolveError),
+ IncompatibleVersion(IncompatibleError),
+
+ // library errors
+ LibraryDirError(PathBuf, io::Error)
}
impl Display for LaunchError {
@@ -105,7 +110,9 @@ impl Display for LaunchError {
match &self {
LaunchError::UnknownVersion(id) => write!(f, "unknown version id: {id}"),
LaunchError::LoadVersion(e) => write!(f, "error loading remote version: {e}"),
- LaunchError::ResolveVersion(e) => write!(f, "error resolving remote version: {e}")
+ LaunchError::ResolveVersion(e) => write!(f, "error resolving remote version: {e}"),
+ LaunchError::IncompatibleVersion(e) => e.fmt(f),
+ LaunchError::LibraryDirError(path, e) => write!(f, "failed to create library directory {}: {}", path.display(), e)
}
}
}
@@ -115,6 +122,8 @@ impl Error for LaunchError {
match &self {
LaunchError::LoadVersion(e) => Some(e.as_ref()),
LaunchError::ResolveVersion(e) => Some(e),
+ LaunchError::IncompatibleVersion(e) => Some(e),
+ LaunchError::LibraryDirError(_, e) => Some(e),
_ => None
}
}
@@ -196,10 +205,34 @@ impl Launcher {
};
let ver = self.versions.resolve_version(ver.as_ref()).await.map_err(|e| LaunchError::ResolveVersion(e))?;
+ ver.rules_apply(&self.system_info, |_| false).map_err(|e| LaunchError::IncompatibleVersion(e))?;
+
+ let mut libs = Vec::new();
+ let mut downloads = Vec::new();
+
+ for lib in ver.libraries.values() {
+ if lib.rules_apply(&self.system_info, |_| false).is_err() {
+ continue;
+ }
+
+ libs.push(lib);
+ if let Some(dl) = self.libraries.create_download(lib, self.system_info.os) {
+ dl.make_dirs().await.map_err(|e| LaunchError::LibraryDirError(dl.get_path().to_path_buf(), e))?;
+ downloads.push(dl);
+ }
+ }
+
+ // TODO: offline
+ info!("Downloading {} libraries...", downloads.len());
+
+ let mut multi = MultiDownloader::new(downloads);
+ let dl: Vec<_> = multi.perform().await.collect().await;
+ info!("amogus: {dl:?}");
- // todo: make a list of all the required libraries
+ // todo: offline mode
- todo!()
+ //todo!()
+ Ok(())
}
}
@@ -264,7 +297,7 @@ impl LibraryRepository {
}
fn create_download(&self, lib: &Library, os: OperatingSystem) -> Option<VerifiedDownload> {
- let classifier = lib.natives.as_ref()?.get(&os).map(|s| s.as_str());
+ let classifier = lib.natives.as_ref().map_or(None, |n| n.get(&os)).map(|s| s.as_str());
if lib.url.is_some() || lib.downloads.is_none() {
// TODO: derive download URL in this situation?
diff --git a/src/launcher/download.rs b/src/launcher/download.rs
index ec89a15..813117c 100644
--- a/src/launcher/download.rs
+++ b/src/launcher/download.rs
@@ -206,6 +206,10 @@ impl VerifiedDownload {
self
}
+ pub fn get_path(&self) -> &Path {
+ &self.path
+ }
+
pub async fn make_dirs(&self) -> Result<(), io::Error> {
fs::create_dir_all(self.path.parent().expect("download created with no containing directory (?)")).await
}
diff --git a/src/launcher/rules.rs b/src/launcher/rules.rs
new file mode 100644
index 0000000..29da8a2
--- /dev/null
+++ b/src/launcher/rules.rs
@@ -0,0 +1,114 @@
+use std::error::Error;
+use std::fmt::Display;
+use crate::version::{Argument, CompatibilityRule, CompleteVersion, FeatureMatcher, Library, OSRestriction, RuleAction};
+use super::SystemInfo;
+
+#[derive(Debug)]
+pub struct IncompatibleError {
+ what: &'static str,
+ reason: Option<String>
+}
+
+impl Display for IncompatibleError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(reason) = self.reason.as_ref() {
+ write!(f, "{} incompatible: {}", self.what, reason)
+ } else {
+ write!(f, "{} incompatible", self.what)
+ }
+ }
+}
+
+impl Error for IncompatibleError {}
+
+mod seal {
+ pub trait CompatCheckInner {
+ const WHAT: &'static str;
+
+ fn get_rules(&self) -> Option<impl IntoIterator<Item = &super::CompatibilityRule>>;
+ fn get_incompatibility_reason(&self) -> Option<&str>;
+ }
+}
+
+pub trait CompatCheck: seal::CompatCheckInner {
+ fn rules_apply<'a>(&'a self, system: &SystemInfo, feature_matcher: impl FeatureMatcher<'a>) -> Result<(), IncompatibleError> {
+ let Some(rules) = self.get_rules() else { return Ok(()) };
+ let mut action = RuleAction::Disallow;
+
+ fn match_os(os: &OSRestriction, system: &SystemInfo) -> bool {
+ os.os.is_none_or(|o| system.is_our_os(o))
+ && os.version.as_ref().is_none_or(|v| v.is_match(system.os_version.as_str()))
+ && os.arch.as_ref().is_none_or(|a| a.is_match(system.arch.as_str()))
+ }
+
+ for rule in rules {
+ if rule.os.as_ref().is_none_or(|o| match_os(o, system))
+ && rule.features_match(feature_matcher) {
+ action = rule.action;
+ }
+ }
+
+ if action == RuleAction::Disallow {
+ Err(IncompatibleError {
+ what: Self::WHAT,
+ reason: self.get_incompatibility_reason().map(|s| s.to_owned())
+ })
+ } else {
+ Ok(())
+ }
+ }
+}
+
+// trivial
+impl seal::CompatCheckInner for CompatibilityRule {
+ const WHAT: &'static str = "rule";
+
+ fn get_rules(&self) -> Option<impl IntoIterator<Item = &CompatibilityRule>> {
+ Some(Some(self))
+ }
+
+ fn get_incompatibility_reason(&self) -> Option<&str> {
+ None
+ }
+}
+
+impl seal::CompatCheckInner for CompleteVersion {
+ const WHAT: &'static str = "version";
+
+ fn get_rules(&self) -> Option<impl IntoIterator<Item = &CompatibilityRule>> {
+ self.compatibility_rules.as_ref()
+ }
+
+ fn get_incompatibility_reason(&self) -> Option<&str> {
+ self.incompatibility_reason.as_ref().map(|s| s.as_str())
+ }
+}
+
+impl seal::CompatCheckInner for Library {
+ const WHAT: &'static str = "library";
+
+ fn get_rules(&self) -> Option<impl IntoIterator<Item = &CompatibilityRule>> {
+ self.rules.as_ref()
+ }
+
+ fn get_incompatibility_reason(&self) -> Option<&str> {
+ None
+ }
+}
+
+impl seal::CompatCheckInner for Argument {
+ const WHAT: &'static str = "argument";
+
+ fn get_rules(&self) -> Option<impl IntoIterator<Item = &CompatibilityRule>> {
+ self.rules.as_ref()
+ }
+
+ fn get_incompatibility_reason(&self) -> Option<&str> {
+ None
+ }
+}
+
+impl CompatCheck for CompatibilityRule {}
+impl CompatCheck for CompleteVersion {}
+impl CompatCheck for Library {}
+impl CompatCheck for Argument {} \ No newline at end of file
diff --git a/src/launcher/version.rs b/src/launcher/version.rs
index 411ac59..40bb953 100644
--- a/src/launcher/version.rs
+++ b/src/launcher/version.rs
@@ -4,7 +4,7 @@ use std::collections::HashSet;
use std::fmt::Display;
use std::path::{Path, PathBuf};
-use log::{debug, info, warn};
+use log::{debug, info, trace, warn};
use sha1_smol::Digest;
use tokio::{fs, io};
use crate::util;
@@ -51,19 +51,22 @@ impl RemoteVersionList {
// download it
let ver_text = reqwest::get(ver.url.as_str()).await?.error_for_status()?.text().await?;
-
+
debug!("Validating downloaded {}...", ver.id);
// make sure it's valid
util::verify_sha1(ver.sha1, ver_text.as_str())
.map_err::<Box<dyn Error>, _>(|e| format!("downloaded version {} has wrong hash! (expect {}, got {})", ver.id.as_str(), &ver.sha1, e).as_str().into())?;
-
+
// make sure it's well-formed
- let cver: CompleteVersion = serde_json::from_str(ver.url.as_str())?;
+ let cver: CompleteVersion = serde_json::from_str(ver_text.as_str())?;
debug!("Saving version {}...", ver.id);
// write it out
- tokio::fs::write(path, ver_text).await?;
+ tokio::fs::write(path, ver_text).await.map_err(|e| {
+ warn!("Failed to save version {}: {}", ver.id, e);
+ e
+ })?;
info!("Done downloading and verifying {}!", ver.id);
diff --git a/src/version.rs b/src/version.rs
index ae91149..af61481 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -10,7 +10,7 @@ use sha1_smol::Digest;
pub mod manifest;
use manifest::*;
-#[derive(Deserialize, Debug, Clone, Copy)]
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum RuleAction {
Allow,
@@ -81,21 +81,51 @@ pub struct CompatibilityRule {
pub os: Option<OSRestriction>
}
-pub trait FeatureMatcher {
- fn matches(&self, feature: &str) -> bool;
-}
-impl<F> FeatureMatcher for F
+/// ## Details
+/// ### Why is the lifetime `'f` specified instead of elided?
+/// This trait has a blanket implementation for all `Fn(&str) -> bool`, with the intent of allowing
+/// one to specify trivial feature matcher implementations using closures (maybe `|_| false` or
+/// `|f| f == "something"`). If the lifetime is left unbounded, the following error is produced by
+/// the compiler:
+///
+/// ```text
+/// Implementation of `Fn` is not general enough
+/// Note: closure with signature `fn(&'2 str) -> bool` must implement `Fn<(&'1 str,)>`, for any lifetime `'1`...
+/// Note: ...but it actually implements `Fn<(&'2 str,)>`, for some specific lifetime `'2`
+/// ```
+///
+/// ### How do I implement this type for non-`Copy` custom feature matchers?
+/// The short answer is: implement the trait for the reference type. Here's an example.
+/// ```rust
+/// use o3launcher::version::FeatureMatcher;
+///
+/// #[derive(Clone)] // can't derive Copy here
+/// struct FM {
+/// our_feature: String // heap-owned data!
+/// }
+///
+/// impl<'f> FeatureMatcher<'f> for &FM {
+/// fn matches(self, feature: &'f str) -> bool {
+/// self.our_feature == feature
+/// }
+/// }
+/// ```
+pub trait FeatureMatcher<'f>: Copy {
+ fn matches(self, feature: &'f str) -> bool;
+}
+
+impl<'f, F> FeatureMatcher<'f> for F
where
- F: Fn(&str) -> bool
+ F: Fn(&'f str) -> bool + Copy
{
- fn matches(&self, feature: &str) -> bool {
+ fn matches(self, feature: &'f str) -> bool {
self(feature)
}
}
impl CompatibilityRule {
- pub fn features_match(&self, checker: impl FeatureMatcher) -> bool {
+ pub fn features_match<'a>(&'a self, checker: impl FeatureMatcher<'a>) -> bool {
if let Some(m) = self.features.as_ref() {
for (feat, expect) in m {
if checker.matches(feat) != *expect {