diff options
| author | 2025-01-16 02:30:15 -0600 | |
|---|---|---|
| committer | 2025-01-16 02:30:15 -0600 | |
| commit | 38ab8b70c132150cd974a33d11bdf8048b99fb23 (patch) | |
| tree | 62f9141c9db23dcb9eb79901a48fe17ed291be87 | |
| parent | assets done (diff) | |
download client jar
| -rw-r--r-- | ozone-ui/src/main.rs | 6 | ||||
| -rw-r--r-- | src/launcher.rs | 186 | ||||
| -rw-r--r-- | src/launcher/assets.rs | 8 | ||||
| -rw-r--r-- | src/version.rs | 4 | ||||
| -rw-r--r-- | src/version/manifest.rs | 2 |
5 files changed, 117 insertions, 89 deletions
diff --git a/ozone-ui/src/main.rs b/ozone-ui/src/main.rs index 8fbec8e..728953d 100644 --- a/ozone-ui/src/main.rs +++ b/ozone-ui/src/main.rs @@ -1,8 +1,6 @@ -use iced::{Background, Border, Element, Fill, FillPortion, Theme}; -use iced::border::Radius; -use iced::widget::{container, column, row, button, text, horizontal_rule, vertical_rule, Themer}; -use iced::widget::button::Status; +use iced::widget::{button, column, container, horizontal_rule, row, text, vertical_rule}; use iced::window::Settings; +use iced::{Element, Fill, FillPortion, Theme}; fn main() -> iced::Result { let mut settings = Settings::default(); diff --git a/src/launcher.rs b/src/launcher.rs index ccb84cb..25cdcc4 100644 --- a/src/launcher.rs +++ b/src/launcher.rs @@ -13,9 +13,10 @@ use std::error::Error; use std::ffi::OsStr; use std::fmt::{Display, Formatter}; use std::io::ErrorKind; +use std::io::ErrorKind::AlreadyExists; use std::path::{Component, Path, PathBuf}; use const_format::formatcp; -use futures::{stream, StreamExt, TryStreamExt}; +use futures::TryStreamExt; use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use sha1_smol::Sha1; @@ -26,7 +27,7 @@ use tokio::io::AsyncWriteExt; use download::{MultiDownloader, VerifiedDownload}; use rules::{CompatCheck, IncompatibleError}; use version::{VersionList, VersionResolveError, VersionResult}; -use crate::version::{Logging, Library, OSRestriction, OperatingSystem}; +use crate::version::{Logging, Library, OSRestriction, OperatingSystem, DownloadType, DownloadInfo}; pub use profile::{Instance, Profile}; use crate::launcher::assets::{AssetError, AssetRepository}; @@ -154,8 +155,16 @@ pub enum LaunchError { LibraryVerifyError(FileVerifyError), LibraryDownloadError, + // ensure file errors + MissingURL, + IO { what: &'static str, error: io::Error }, + Offline, + Download { url: String, error: reqwest::Error }, + Integrity(IntegrityError), + // log errors - LogConfig(LogConfigError), + UnknownLogType(String), + InvalidLogId(Option<String>), // asset errors Assets(AssetError) @@ -171,7 +180,14 @@ impl Display for LaunchError { LaunchError::LibraryDirError(path, e) => write!(f, "failed to create library directory {}: {}", path.display(), e), LaunchError::LibraryVerifyError(e) => write!(f, "failed to verify library: {}", e), LaunchError::LibraryDownloadError => f.write_str("library download failed (see above logs for details)"), // TODO: booo this sucks - LaunchError::LogConfig(e) => write!(f, "failed to configure logger: {}", e), + LaunchError::MissingURL => f.write_str("cannot download required file, URL is missing"), + LaunchError::IO { what, error } => write!(f, "i/o error ({}): {}", what, error), + LaunchError::Offline => f.write_str("cannot download file in offline mode"), + LaunchError::Download { url, error } => write!(f, "failed to download file ({}): {}", url, error), + LaunchError::Integrity(e) => write!(f, "file verify error: {}", e), + LaunchError::UnknownLogType(t) => write!(f, "unknown log type: {}", t), + LaunchError::InvalidLogId(Some(id)) => write!(f, "invalid log id: {}", id), + LaunchError::InvalidLogId(None) => write!(f, "missing log id"), LaunchError::Assets(e) => write!(f, "failed to fetch assets: {}", e) } } @@ -185,7 +201,9 @@ impl Error for LaunchError { LaunchError::IncompatibleVersion(e) => Some(e), LaunchError::LibraryDirError(_, e) => Some(e), LaunchError::LibraryVerifyError(e) => Some(e), - LaunchError::LogConfig(e) => Some(e), + LaunchError::IO { error: e, .. } => Some(e), + LaunchError::Download { error: e, .. } => Some(e), + LaunchError::Integrity(e) => Some(e), LaunchError::Assets(e) => Some(e), _ => None } @@ -200,14 +218,11 @@ impl Launcher { let versions; match tokio::fs::create_dir_all(&home).await { - Ok(_) => (), - Err(e) => match e.kind() { - ErrorKind::AlreadyExists => (), - _ => { - warn!("Failed to create launcher home directory: {}", e); - return Err(e.into()); - } - } + Err(e) if e.kind() != AlreadyExists => { + warn!("Failed to create launcher home directory: {}", e); + return Err(e.into()); + }, + _ => () } debug!("Version list online?: {online}"); @@ -224,7 +239,6 @@ impl Launcher { Ok(Launcher { online, - home: home.to_owned(), versions, settings_path, settings, @@ -232,87 +246,56 @@ impl Launcher { libraries: LibraryRepository { home: home.join("libraries"), }, - assets: AssetRepository::new(online, &assets_path).await? + assets: AssetRepository::new(online, &assets_path).await?, + home }) } - async fn log_config_ensure(&self, config: &Logging) -> Result<Option<String>, LogConfigError> { - info!("Ensuring log configuration exists and is valid."); - - if config.client.log_type != "log4j2-xml" { - return Err(LogConfigError::UnknownType(config.client.log_type.clone())); - } - - let dlinfo = &config.client.file; - let Some(id) = dlinfo.id.as_ref() else { - return Err(LogConfigError::InvalidId(None)); - }; - - let mut path = self.home.join("logging"); - fs::create_dir_all(path.as_path()).await - .map_err(|e| LogConfigError::IO{ what: "creating log directory", error: e })?; - - let Some(Component::Normal(filename)) = Path::new(id).components().last() else { - return Err(LogConfigError::InvalidId(Some(id.clone()))); - }; - - path.push(filename); - - debug!("Logger config {} is at {}", id, path.display()); - - // creates the JVM argument for this logger config - fn get_arg(arg: &str, path: &Path) -> String { - strsub::replace_str(arg, |key| match key { - "path" => Some(path.to_string_lossy().clone()), - _ => None - }) - } - + async fn ensure_file(&self, path: &Path, dlinfo: &DownloadInfo) -> Result<(), LaunchError> { // verify the file - match util::verify_file(path.as_path(), dlinfo.size, dlinfo.sha1).await { + match util::verify_file(path, dlinfo.size, dlinfo.sha1).await { // integrity passed. return Ok(_) => { - info!("Log configuration {} exists and integrity matches. Skipping.", id); - return Ok(Some(get_arg(config.client.argument.as_str(), path.as_path()))); + info!("File {} exists and integrity matches. Skipping.", path.display()); + return Ok(()); }, // ruh roh Err(e) => match e { - FileVerifyError::Open(_, ioe) => match ioe.kind() { - ErrorKind::NotFound => (), - _ => return Err(LogConfigError::IO{ what: "verify file (open)", error: ioe }) - }, - FileVerifyError::Read(_, ioe) => return Err(LogConfigError::IO{ what: "verify file (read)", error: ioe }), - FileVerifyError::Integrity(_, ie) => info!("log config failed integrity check: {}", ie) + FileVerifyError::Open(_, ioe) if ioe.kind() != ErrorKind::NotFound => + return Err(LaunchError::IO{ what: "verify file (open)", error: ioe }), + FileVerifyError::Read(_, ioe) => return Err(LaunchError::IO{ what: "verify file (read)", error: ioe }), + FileVerifyError::Integrity(_, ie) => info!("file {} failed integrity check: {}", path.display(), ie), + _ => () } } if !self.online { - warn!("Cannot download log config! We are offline. Rerun the launcher in online mode to launch this version."); - return Err(LogConfigError::Offline); + warn!("Cannot download file {}! We are offline. Rerun the launcher in online mode to launch this version.", path.display()); + return Err(LaunchError::Offline); } // download it let Some(url) = dlinfo.url.as_ref() else { - return Err(LogConfigError::MissingURL); + return Err(LaunchError::MissingURL); }; - let mut file = File::create(path.as_path()).await.map_err(|e| LogConfigError::IO { - what: "save log config (open)", + let mut file = File::create(path).await.map_err(|e| LaunchError::IO { + what: "save downloaded file (open)", error: e })?; - debug!("Logger configuration {} must be downloaded ({}).", id, url); + debug!("File {} must be downloaded ({}).", path.display(), url); - let mut response = reqwest::get(url).await.map_err(|e| LogConfigError::Download{ url: url.to_owned(), error: e })?; + let mut response = reqwest::get(url).await.map_err(|e| LaunchError::Download{ url: url.to_owned(), error: e })?; let mut tally = 0usize; let mut sha1 = Sha1::new(); - while let Some(chunk) = response.chunk().await.map_err(|e| LogConfigError::Download{ url: url.to_owned(), error: e })? { + while let Some(chunk) = response.chunk().await.map_err(|e| LaunchError::Download{ url: url.to_owned(), error: e })? { let slice = chunk.as_ref(); - file.write_all(slice).await.map_err(|e| LogConfigError::IO { - what: "save log config (write)", + file.write_all(slice).await.map_err(|e| LaunchError::IO { + what: "save downloaded file (write)", error: e })?; @@ -323,15 +306,15 @@ impl Launcher { drop(file); // manually close file let del_file_silent = || async { - debug!("Deleting downloaded log config {} since its integrity doesn't match :( {}", id, path.display()); - let _ = fs::remove_file(path.as_path()).await.map_err(|e| warn!("failed to delete invalid log config: {}", e)); + debug!("Deleting downloaded file {} since its integrity doesn't match :(", path.display()); + let _ = fs::remove_file(path).await.map_err(|e| warn!("failed to delete invalid downloaded file: {}", e)); () }; if dlinfo.size.is_some_and(|s| s != tally) { del_file_silent().await; - return Err(LogConfigError::Integrity(IntegrityError::SizeMismatch { + return Err(LaunchError::Integrity(IntegrityError::SizeMismatch { expect: dlinfo.size.unwrap(), actual: tally })); @@ -342,15 +325,47 @@ impl Launcher { if dlinfo.sha1.is_some_and(|exp_dig| exp_dig != digest) { del_file_silent().await; - return Err(LogConfigError::Integrity(IntegrityError::Sha1Mismatch { + return Err(LaunchError::Integrity(IntegrityError::Sha1Mismatch { expect: dlinfo.sha1.unwrap(), actual: digest })); } - info!("Log configuration {} downloaded successfully.", id); + info!("File {} downloaded successfully.", path.display()); + + Ok(()) + } + + async fn log_config_ensure(&self, config: &Logging) -> Result<String, LaunchError> { + info!("Ensuring log configuration exists and is valid."); - Ok(Some(get_arg(config.client.argument.as_str(), path.as_path()))) + if config.client.log_type != "log4j2-xml" { + return Err(LaunchError::UnknownLogType(config.client.log_type.clone())); + } + + let dlinfo = &config.client.file; + let Some(id) = dlinfo.id.as_ref() else { + return Err(LaunchError::InvalidLogId(None)); + }; + + let mut path = self.home.join("logging"); + fs::create_dir_all(path.as_path()).await + .map_err(|e| LaunchError::IO{ what: "creating log directory", error: e })?; + + let Some(Component::Normal(filename)) = Path::new(id).components().last() else { + return Err(LaunchError::InvalidLogId(Some(id.clone()))); + }; + + path.push(filename); + + debug!("Logger config {} is at {}", id, path.display()); + + self.ensure_file(&path, dlinfo).await?; + + Ok(strsub::replace_str(config.client.argument.as_str(), |key| match key { + "path" => Some(path.to_string_lossy().clone()), + _ => None + })) } pub async fn prepare_launch(&self, profile: &Profile) -> Result<(), LaunchError> { @@ -373,8 +388,8 @@ impl Launcher { * - (done) (if offline mode, explode) * - if virtual or resource-mapped, copy (or perhaps hardlink? that would be cool) * - the actual client jar - * - check integriddy and download if needed - * - (explode if offline mode) + * - (done) check integriddy and download if needed + * - (done) (explode if offline mode) * - launch the game * - build argument list and whatnot also */ @@ -428,14 +443,15 @@ impl Launcher { let log_arg; if let Some(logging) = ver.logging.as_ref() { - log_arg = self.log_config_ensure(logging).await - .map_err(|e| LaunchError::LogConfig(e))?; + log_arg = Some(self.log_config_ensure(logging).await?); } else { log_arg = None; } dbg!(log_arg); + // download assets + if let Some(idx_download) = ver.asset_index.as_ref() { let asset_idx = self.assets.load_index(idx_download, ver.assets.as_ref().map(|s| s.as_ref())).await .map_err(|e| LaunchError::Assets(e))?; @@ -443,6 +459,24 @@ impl Launcher { self.assets.ensure_assets(&asset_idx).await.map_err(|e| LaunchError::Assets(e))?; } + // download client jar + + let client_jar_path; + if let Some(client) = ver.downloads.get(&DownloadType::Client) { + let mut client_path: PathBuf = [self.home.as_ref(), OsStr::new("versions"), OsStr::new(&ver.id)].iter().collect(); + fs::create_dir_all(&client_path).await.map_err(|e| LaunchError::IO{ what: "creating client download directory", error: e })?; + + client_path.push(format!("{}.jar", ver.id)); + + info!("Downloading client jar {}", client_path.display()); + + self.ensure_file(client_path.as_path(), client).await?; + + client_jar_path = Some(client_path); + } else { + client_jar_path = None; + } + //todo!() Ok(()) } diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs index 7d883d8..7ad368e 100644 --- a/src/launcher/assets.rs +++ b/src/launcher/assets.rs @@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter}; use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::path::Component::Normal;
-use futures::{stream, TryFutureExt, TryStreamExt};
+use futures::TryStreamExt;
use log::{debug, info, warn};
use sha1_smol::Sha1;
use tokio::{fs, io};
@@ -184,10 +184,6 @@ impl AssetRepository { format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash)
}
- async fn download_assets(&self, index: &AssetIndex) -> Result<(), AssetError> {
- todo!()
- }
-
async fn ensure_dir(path: impl AsRef<Path>) -> Result<(), io::Error> {
match fs::create_dir(path).await {
Ok(_) => Ok(()),
@@ -198,7 +194,7 @@ impl AssetRepository { pub async fn ensure_assets(&self, index: &AssetIndex) -> Result<(), AssetError> {
let mut downloads = Vec::new();
- let objects_path = [self.home.as_ref(), OsStr::new("objects")].iter().collect::<PathBuf>();
+ let objects_path = [self.home.as_ref(), OsStr::new(OBJECT_PATH)].iter().collect::<PathBuf>();
Self::ensure_dir(&objects_path).await.map_err(|e| AssetError::IO {
what: "creating objects directory",
diff --git a/src/version.rs b/src/version.rs index ab78bef..545a487 100644 --- a/src/version.rs +++ b/src/version.rs @@ -46,7 +46,7 @@ struct RegexVisitor; impl<'de> Visitor<'de> for RegexVisitor { type Value = WrappedRegex; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a valid regular expression") } @@ -462,7 +462,7 @@ where { type Value = Vec<T>; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("string or array") } diff --git a/src/version/manifest.rs b/src/version/manifest.rs index 598cc91..c8c49b8 100644 --- a/src/version/manifest.rs +++ b/src/version/manifest.rs @@ -24,7 +24,7 @@ struct VersionTypeVisitor; impl<'de> Visitor<'de> for VersionTypeVisitor { type Value = VersionType; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a Minecraft release type") } |
