diff --git a/Cargo.lock b/Cargo.lock index af19ed27..2df62e49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,6 +369,15 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-tempfile" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8a57b75c36e16f4d015e60e6a177552976a99b6947724403c551bcfa7cb1207" +dependencies = [ + "tokio", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -3430,6 +3439,7 @@ dependencies = [ "anyhow", "async-compression", "async-stream", + "async-tempfile", "async-trait", "async_zip", "base64 0.22.1", @@ -3469,7 +3479,6 @@ dependencies = [ "sysinfo", "tauri", "taurpc", - "tempfile", "thiserror 2.0.12", "tokio", "tokio-stream", diff --git a/Cargo.toml b/Cargo.toml index fc08dc38..6369ebfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,7 +126,7 @@ dirs = { version = "=6.0.0" } dunce = { version = "=1.0.5" } flate2 = { version = "=1.1.1" } sanitize-filename = { version = "=0.6.0" } -tempfile = { version = "=3.19.1" } +async-tempfile = { version = "=0.7.0" } tokio = { version = "1", features = [ "full" ] } tokio-stream = { version = "0.1", features = [ "fs" ] } tokio-util = { version = "0.7", features = [ "compat" ] } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 50c3fbbc..9e914a1a 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -93,7 +93,7 @@ bytes = { workspace = true } base64 = { workspace = true } async-compression = { workspace = true } dunce = { workspace = true } -tempfile = { workspace = true } +async-tempfile = { workspace = true } sanitize-filename = { workspace = true } # features[tauri] diff --git a/packages/core/examples/download_java.rs b/packages/core/examples/download_java.rs new file mode 100644 index 00000000..6bdc1a9e --- /dev/null +++ b/packages/core/examples/download_java.rs @@ -0,0 +1,26 @@ +use onelauncher_core::api::java; +use onelauncher_core::api::proxy::ProxyDynamic; +use onelauncher_core::error::LauncherResult; +use onelauncher_core::initialize_core; +use onelauncher_core::store::{CoreOptions, State}; + +#[tokio::main] +async fn main() -> LauncherResult<()> { + initialize_core(CoreOptions::default(), ProxyDynamic::new()).await?; + + let _ = State::get().await?; + + let packages = java::get_zulu_packages().await?; + + let Some(pkg) = packages.first() else { + return Err(anyhow::anyhow!("no zulu package found").into()); + }; + + println!("found package: {pkg:#?}"); + + let path = java::install_java_package(pkg).await?; + + println!("java package installed at '{path:?}'"); + + Ok(()) +} diff --git a/packages/core/src/api/java/mod.rs b/packages/core/src/api/java/mod.rs index dfea3c2a..cd0c2bf5 100644 --- a/packages/core/src/api/java/mod.rs +++ b/packages/core/src/api/java/mod.rs @@ -1,16 +1,19 @@ use std::collections::HashMap; use std::path::PathBuf; -use async_zip::base::read::mem::ZipFileReader; +use futures::TryStreamExt; use interpulse::api::minecraft::VersionInfo; use onelauncher_entity::{java_versions, setting_profiles}; +use reqwest::Method; use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWriteExt; use crate::api::ingress::{init_ingress, init_ingress_opt, send_ingress}; +use crate::constants::JAVA_BIN; use crate::error::LauncherResult; use crate::store::Dirs; -use crate::store::ingress::{IngressType, SubIngress}; -use crate::utils::http::{fetch_advanced, fetch_json}; +use crate::store::ingress::IngressType; +use crate::utils::http; use crate::utils::io::{self, IOError}; pub mod dao; @@ -44,15 +47,11 @@ pub fn get_java_bin() -> PathBuf { .join("Contents") .join("Home") .join("bin") - .join("java") + .join(JAVA_BIN) } - #[cfg(target_os = "windows")] + #[cfg(not(target_os = "macos"))] { - PathBuf::new().join("bin").join("javaw.exe") - } - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - { - PathBuf::new().join("bin").join("java") + PathBuf::new().join("bin").join(JAVA_BIN) } } @@ -65,7 +64,7 @@ pub struct JavaInfo { } #[onelauncher_macro::specta] -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct JavaPackage { pub download_url: String, pub name: PathBuf, @@ -88,15 +87,15 @@ pub async fn check_java_runtime( .await?; let id = id.as_ref(); - let dir = io::tempdir()?; - let file = dir.path().join("JavaInfo.class"); + let dir = io::tempdir().await?; + let file = dir.dir_path().join("JavaInfo.class"); id.map(async |id| send_ingress(id, 25.0).await); io::write(&file, JAVA_INFO_CLASS).await?; id.map(async |id| send_ingress(id, 25.0).await); let java_info = tokio::process::Command::new(absolute_path) .arg("-cp") - .arg(dir.path()) + .arg(dir.dir_path()) .arg("JavaInfo") .env_remove("_JAVA_OPTIONS") .output() @@ -207,7 +206,7 @@ pub async fn prepare_java(major: u32) -> LauncherResult { fn find_java_in_path(mut found: Vec) -> Vec { if let Ok(path) = std::env::var("PATH") { for path_entry in path.split(':') { - let java = PathBuf::from(path_entry).join("java"); + let java = PathBuf::from(path_entry).join(JAVA_BIN); if java.exists() && java.is_file() && !found.contains(&java) { found.push(java); } @@ -264,7 +263,7 @@ fn internal_locate_java() -> Vec { if let Ok(entries) = std::fs::read_dir(base_path) { for entry in entries.flatten() { if entry.file_type().map_or(false, |ft| ft.is_dir()) { - let java_exe = entry.path().join("bin").join("java.exe"); + let java_exe = entry.path().join(get_java_bin()); if java_exe.exists() { java_homes.push(java_exe); } @@ -276,14 +275,14 @@ fn internal_locate_java() -> Vec { // env vars if let Ok(java_home) = std::env::var("JAVA_HOME") { let path_buf = PathBuf::from(java_home); - if path_buf.join("bin").join("java.exe").exists() { + if path_buf.join("bin").join(JAVA_BIN).exists() { java_homes.push(path_buf); } } if let Ok(path) = std::env::var("PATH") { for path_entry in path.split(';') { - let java_exe = PathBuf::from(path_entry).join("java.exe"); + let java_exe = PathBuf::from(path_entry).join(JAVA_BIN); if java_exe.exists() { if let Some(bin_dir) = java_exe.parent() { if let Some(java_home) = bin_dir.parent() { @@ -363,7 +362,7 @@ fn internal_locate_java() -> Vec { } pub async fn get_zulu_packages() -> LauncherResult> { - fetch_json::>( + http::fetch_json::>( reqwest::Method::GET, format!( "https://api.azul.com/metadata/v1/zulu/packages/?os={}&arch={}&archive_type=zip&java_package_type=jre&javafx_bundled=false&latest=true&release_status=ga&availability_types=CA&certifications=tck&page=1&page_size=100", @@ -384,16 +383,14 @@ pub async fn install_java_from_major(version: u32) -> LauncherResult { .find(|p| p.java_version.contains(&version)) .ok_or_else(|| anyhow::anyhow!("Could not find a java package for version {}", version))?; - install_java_package(package).await + install_java_package(&package).await } -pub async fn install_java_package(package: JavaPackage) -> LauncherResult { +pub async fn install_java_package(package: &JavaPackage) -> LauncherResult { const INGRESS_TOTAL: f64 = 100.0; const INGRESS_TASKS: f64 = 4.0; const INGRESS_STEP: f64 = INGRESS_TOTAL / INGRESS_TASKS; - let version = package.download_url; - let ingress_id = init_ingress( IngressType::JavaPrepare, &format!( @@ -404,37 +401,40 @@ pub async fn install_java_package(package: JavaPackage) -> LauncherResult 0 { + let absolute = (downloaded as f64 / size as f64) * INGRESS_STEP; + let delta = absolute - last_ingress_prog; + send_ingress(&ingress_id, delta).await?; + last_ingress_prog = absolute; } } - io::unzip_bytes(archive.data().to_vec(), &java_dir).await?; + tmp_archive.flush().await.map_err(IOError::from)?; + + // extract file + io::extract_zip(tmp_archive.file_path(), &java_dir).await?; + send_ingress(&ingress_id, INGRESS_STEP).await?; + + // drop (which in turn closes / deletes the tempfile) + drop(tmp_archive); let mut base_path = java_dir.join( package @@ -453,18 +453,10 @@ pub async fn install_java_package(package: JavaPackage) -> LauncherResult>() .join("."); - base_path = base_path - .join(format!("zulu-{java_version}.jre")) - .join("Contents") - .join("Home") - .join("bin") - .join("java"); + base_path = base_path.join(format!("zulu-{java_version}.jre")); } - #[cfg(not(target_os = "macos"))] - { - base_path = base_path.join("bin").join(crate::constants::JAVA_BIN); - } + base_path = base_path.join(get_java_bin()); Ok(base_path) } diff --git a/packages/core/src/store/settings.rs b/packages/core/src/store/settings.rs index f0ab4f82..4af1c7bd 100644 --- a/packages/core/src/store/settings.rs +++ b/packages/core/src/store/settings.rs @@ -18,6 +18,8 @@ pub struct Settings { pub max_concurrent_requests: usize, pub settings_version: u32, pub native_window_frame: bool, + + #[cfg(feature = "tauri")] pub show_tanstack_dev_tools: bool, } @@ -32,6 +34,8 @@ impl Default for Settings { max_concurrent_requests: 25, settings_version: 1, native_window_frame: false, + + #[cfg(feature = "tauri")] show_tanstack_dev_tools: tauri::is_dev(), } } diff --git a/packages/core/src/utils/io.rs b/packages/core/src/utils/io.rs index 64383c9a..b57ccdec 100644 --- a/packages/core/src/utils/io.rs +++ b/packages/core/src/utils/io.rs @@ -4,12 +4,12 @@ use std::sync::Arc; use async_compression::tokio::bufread::GzipDecoder; use async_stream::try_stream; +use async_tempfile::{TempDir, TempFile}; use async_zip::StoredZipEntry; use async_zip::base::read::WithoutEntry; use futures::{Stream, TryStreamExt, pin_mut}; use serde::Serialize; use serde::de::DeserializeOwned; -use tempfile::TempDir; use tokio_util::compat::TokioAsyncWriteCompatExt; /// A wrapper around generic and unhelpful [`std::io::Error`] messages. @@ -46,6 +46,12 @@ pub enum IOError { #[skip] async_zip::error::ZipError, ), + #[error("tempfile error: {0}")] + TempFileError( + #[from] + #[skip] + async_tempfile::Error, + ), } impl> From<(P, std::io::Error)> for IOError { @@ -256,13 +262,13 @@ pub async fn remove_file(path: impl AsRef) -> Result<(), IOErro } /// Creates a temporary directory. -pub fn tempdir() -> Result { - Ok(tempfile::tempdir()?) +pub async fn tempdir() -> Result { + Ok(TempDir::new().await?) } /// Creates a temporary file. -pub fn tempfile() -> Result { - Ok(tempfile::tempfile()?) +pub async fn tempfile() -> Result { + Ok(TempFile::new().await?) } /// Reads a zip archive from a byte array diff --git a/packages/utils/Cargo.toml b/packages/utils/Cargo.toml deleted file mode 100644 index e74022d0..00000000 --- a/packages/utils/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "onelauncher_utils" -description = "rust utils for onelauncher" -version = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -repository = { workspace = true } -documentation = { workspace = true } -readme = { workspace = true } -homepage = { workspace = true } -authors = { workspace = true } - -[lints] -workspace = true - -[dependencies] -serde = { workspace = true } -uuid = { workspace = true } -async-channel = { workspace = true } -futures = { workspace = true } -pin-project-lite = { workspace = true } -tokio = { workspace = true } -tempfile = { workspace = true } -sys-info = { workspace = true } -zip = { workspace = true } -dunce = { workspace = true } -async-compression = { workspace = true } -chrono = { workspace = true } -thiserror = { workspace = true } -tracing = { workspace = true } -nom = { workspace = true } -regex = { workspace = true } -interpulse = { workspace = true } diff --git a/packages/utils/package.json b/packages/utils/package.json deleted file mode 100644 index ee893346..00000000 --- a/packages/utils/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@onelauncher/utils", - "version": "0.1.0-alpha.2", - "description": "Next-generation open source Minecraft launcher utils", - "license": "GPL-3.0-only", - "repository": { - "type": "git", - "url": "git@github.com:polyfrost/onelauncher", - "directory": "packages/utils" - }, - "bugs": "https://github.com/polyfrost/onelauncher/issues", - "sideEffects": false, - "scripts": {} -} diff --git a/packages/utils/src/io.rs b/packages/utils/src/io.rs deleted file mode 100644 index 401cfcf0..00000000 --- a/packages/utils/src/io.rs +++ /dev/null @@ -1,245 +0,0 @@ -//! **IO Utilities** -//! -//! Async wrapper around [`tokio::fs`] and [`std::io::Error`] for our error system. - -use async_compression::tokio::bufread::GzipDecoder; -use std::io::Write; -use tempfile::NamedTempFile; -use tokio::io::AsyncReadExt; -use tokio::task::spawn_blocking; - -/// A wrapper around generic and unhelpful [`std::io::Error`] messages. -#[derive(Debug, thiserror::Error)] -#[serde(tag = "type", content = "data")] -pub enum IOError { - /// A wrapped [`std::io::Error`] along with the path involved in the error. - #[error("error acessing path: {source}, path: {path}")] - IOErrorWrapper { - #[source] - source: std::io::Error, - path: String, - }, - /// A wrapped [`zip::result::ZipError`]. - #[error(transparent)] - ZipError(#[from] zip::result::ZipError), - /// A wrapped [`std::io::Error`]. - #[error(transparent)] - IOError(#[from] std::io::Error), -} - -impl> From<(P, std::io::Error)> for IOError { - fn from((path, source): (P, std::io::Error)) -> Self { - Self::IOErrorWrapper { - source, - path: path.as_ref().to_string_lossy().to_string(), - } - } -} - -#[derive(Debug, thiserror::Error)] -#[serde(tag = "type", content = "data")] -#[error("received a non UTF-8 path: ", .0.to_string_lossy())] -pub struct NonUtf8PathError(pub Box); - -impl IOError { - /// Converts a [`std::io::Error`] into an [`IOError`]. - #[must_use] - pub const fn from(source: std::io::Error) -> Self { - Self::IOError(source) - } - - /// Converts a [`zip::result::ZipError`] into an [`IOError`]. - #[must_use] - pub const fn from_zip(source: zip::result::ZipError) -> Self { - Self::ZipError(source) - } - - /// Converts a [`std::io::Error`] and the path involved in the error into an [`IOErrror`]. - pub fn with_path(source: std::io::Error, path: impl AsRef) -> Self { - let path = path.as_ref().to_string_lossy().to_string(); - - Self::IOErrorWrapper { source, path } - } -} - -/// An OS specific wrapper of [`std::fs::canonicalize`], but on Windows it outputs the most compatible form of a path instead of UNC. -pub fn canonicalize(path: impl AsRef) -> Result { - let path = path.as_ref(); - dunce::canonicalize(path).map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Returns a stream over the entries within a directory. -pub async fn read_dir(path: impl AsRef) -> Result { - let path = path.as_ref(); - tokio::fs::read_dir(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Creates a directory if they are missing. -pub async fn create_dir(path: impl AsRef) -> Result<(), IOError> { - let path = path.as_ref(); - if path.exists() { - return Ok(()); - } - - tokio::fs::create_dir(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Recursively creates a directory and all of its parent components if they are missing. -pub async fn create_dir_all(path: impl AsRef) -> Result<(), IOError> { - let path = path.as_ref(); - tokio::fs::create_dir_all(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Removes a directory at this path, after removing all its contents. Use carefully! -pub async fn remove_dir_all(path: impl AsRef) -> Result<(), IOError> { - let path = path.as_ref(); - tokio::fs::remove_dir_all(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Creates a future which will open a gzip compressed file for reading and read the entire contents into a string and return said string. -pub async fn read_gz_to_string(path: impl AsRef) -> Result { - let mut f = tokio::fs::File::open(path).await?; - let mut buf = vec![]; - f.read_to_end(&mut buf).await?; - - let mut decoder = GzipDecoder::new(buf.as_slice()); - let mut dst = String::new(); - decoder.read_to_string(&mut dst).await?; - - Ok(dst) -} - -/// Creates a future which will open a file for reading and read the entire contents into a string and return said string. -pub async fn read_to_string(path: impl AsRef) -> Result { - let path = path.as_ref(); - tokio::fs::read_to_string(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Reads the entire contents of a file into a bytes vector. -pub async fn read(path: impl AsRef) -> Result, IOError> { - let path = path.as_ref(); - tokio::fs::read(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -/// Asynchrously write to a tempfile that is then transferred to an official [`AsRef`]. -pub async fn write( - path: impl AsRef, - data: impl AsRef<[u8]>, -) -> Result<(), IOError> { - let path = path.as_ref().to_owned(); - let data = data.as_ref().to_owned(); - spawn_blocking(move || { - let cloned_path = path.clone(); - sync_write(data, path).map_err(|e| IOError::IOErrorWrapper { - source: e, - path: cloned_path.to_string_lossy().to_string(), - }) - }) - .await - .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "tokio task failed"))??; - - Ok(()) -} - -/// Write to a tempfile that is then transferred to an official [`AsRef`]. -fn sync_write( - data: impl AsRef<[u8]>, - path: impl AsRef, -) -> Result<(), std::io::Error> { - let mut tempfile = NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::Other, - "failed to get parent directory of a tempfile", - ) - })?)?; - tempfile.write_all(data.as_ref())?; - let tmp_path = tempfile.into_temp_path(); - let path = path.as_ref(); - // this is a sorta dangerous call but shouldnt matter because of async - tmp_path.persist(path)?; - std::io::Result::Ok(()) -} - -/// Renames a file or directory to a new name, replacing the original file if `to` already exists. -pub async fn rename( - from: impl AsRef, - to: impl AsRef, -) -> Result<(), IOError> { - let from = from.as_ref(); - let to = to.as_ref(); - tokio::fs::rename(from, to) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: from.to_string_lossy().to_string(), - }) -} - -/// Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file. This function will overwrite the contents of to. -pub async fn copy( - from: impl AsRef, - to: impl AsRef, -) -> Result { - let from = from.as_ref(); - let to = to.as_ref(); - tokio::fs::copy(from, to) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: from.to_string_lossy().to_string(), - }) -} - -/// Removes a file from the filesystem. -pub async fn remove_file(path: impl AsRef) -> Result<(), IOError> { - let path = path.as_ref(); - tokio::fs::remove_file(path) - .await - .map_err(|e| IOError::IOErrorWrapper { - source: e, - path: path.to_string_lossy().to_string(), - }) -} - -///// Returns the SHA512 hash of a file. -// pub fn sha512(path: impl AsRef) -> Result { -// let path = path.as_ref(); -// let mut file = std::fs::File::open(path)?; -// let mut hasher = sha2::Sha512::new(); -// std::io::copy(&mut file, &mut hasher)?; - -// Ok(format!("{:x}", hasher.finalize())) -// } diff --git a/packages/utils/src/lib.rs b/packages/utils/src/lib.rs deleted file mode 100644 index d55c1dd7..00000000 --- a/packages/utils/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! **`OneLauncher` Utilities** -//! Standard utilities for use within all Rust subprojects. -//! -//! - [`logging`]: Async utilities for log4j parsing with [`nom`]. -//! - [`platform`]: Async utilities for managing OS-specific [`interpulse`] operations and rules. -//! - [`io`]: Async wrapper around [`tokio::fs`] and [`std::io::Error`] for our error system. - -pub mod io; -pub mod logging; -pub mod platform; - -/// Simple macro that takes a mutable reference and inserts it into a codeblock closure -/// as an owned reference. -/// -/// mutable reference gets epically owned by free thinking macro!!!!! (not clickbait) -/// im going insane insane insane insane insane insane insane insane insane -#[macro_export] -macro_rules! ref_owned { - ($id:ident = $init:expr => $transform:block) => {{ - let mut it = $init; - { - let $id = &mut it; - $transform; - } - it - }}; -} - -/// Combines an iterator of `T` and an iterator of [`Option`], -/// removing and ensuring the safety of any [`None`] values in the process. -pub fn chain_iterator( - required: impl IntoIterator, - optional: impl IntoIterator>, -) -> Vec { - required - .into_iter() - .map(Some) - .chain(optional) - .flatten() - .collect() -} - -#[inline] -#[must_use] -pub fn test_type_of(_: T) -> &'static str { - std::any::type_name::() -} diff --git a/packages/utils/src/logging.rs b/packages/utils/src/logging.rs deleted file mode 100644 index 5581758e..00000000 --- a/packages/utils/src/logging.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Async utilities for parsing Log4j entries with [`nom`]. -//! From - -use nom::branch::alt; -use nom::bytes::streaming::{tag, take_until}; -use nom::character::streaming::{char, multispace0, u64}; -use nom::combinator::{map, value}; -use nom::error::ParseError; -use nom::multi::count; -use nom::sequence::{delimited, preceded, separated_pair, tuple}; -use nom::IResult; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LogEntry<'a> { - pub logger: &'a str, - pub level: LogLevel, - pub timestamp: u64, - pub thread_name: &'a str, - pub message: &'a str, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum LogLevel { - Trace, - Debug, - Info, - Warn, - Error, -} - -fn whitespace<'a, O, E: ParseError<&'a str>>( - inner: impl FnMut(&'a str) -> IResult<&'a str, O, E>, -) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> { - delimited(multispace0, inner, multispace0) -} - -pub fn parse_logentry(input: &str) -> IResult<&str, LogEntry<'_>> { - let (o, (attributes, _, message)) = preceded( - multispace0, - alt(( - delimited( - tag(""), whitespace(message))), - tag(""), - ), - plain_text, - )), - )(input)?; - - let Attributes { - logger, - level, - timestamp, - thread_name, - } = attributes; - - Ok(( - o, - LogEntry { - logger, - level, - timestamp, - thread_name, - message, - }, - )) -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Attributes<'a> { - pub logger: &'a str, - pub level: LogLevel, - pub timestamp: u64, - pub thread_name: &'a str, -} - -fn attributes(input: &str) -> IResult<&str, Attributes<'_>> { - let (o, attributes) = count(whitespace(attribute), 4)(input)?; - macro_rules! extract_attribute { - ($field:ident) => {{ - let err = nom::Err::Error(nom::error::Error::from_error_kind( - o, - nom::error::ErrorKind::Alt, - )); - let Attribute::$field(field) = attributes - .iter() - .copied() - .find(|a| matches!(a, Attribute::$field(_))) - .ok_or(err)? - else { - unreachable!(); - }; - - field - }}; - } - - Ok(( - o, - Attributes { - logger: extract_attribute!(Logger), - level: extract_attribute!(Level), - timestamp: extract_attribute!(Timestamp), - thread_name: extract_attribute!(ThreadName), - }, - )) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Attribute<'a> { - Logger(&'a str), - Level(LogLevel), - Timestamp(u64), - ThreadName(&'a str), -} - -fn attribute(input: &str) -> IResult<&str, Attribute<'_>> { - alt((attr_logger, attr_timestamp, attr_level, attr_thread))(input) -} - -fn attr_logger(input: &str) -> IResult<&str, Attribute<'_>> { - map( - separated_pair( - tag("logger"), - whitespace(char('=')), - map(quoted_string, Attribute::Logger), - ), - |(_, attr)| attr, - )(input) -} - -fn attr_timestamp(input: &str) -> IResult<&str, Attribute<'_>> { - map( - separated_pair( - tag("timestamp"), - whitespace(char('=')), - delimited(char('"'), map(u64, Attribute::Timestamp), char('"')), - ), - |(_, attr)| attr, - )(input) -} - -fn attr_level(input: &str) -> IResult<&str, Attribute<'_>> { - map( - separated_pair( - tag("level"), - whitespace(char('=')), - delimited(char('"'), map(level, Attribute::Level), char('"')), - ), - |(_, attr)| attr, - )(input) -} - -fn attr_thread(input: &str) -> IResult<&str, Attribute<'_>> { - map( - separated_pair( - tag("thread"), - whitespace(char('=')), - map(quoted_string, Attribute::ThreadName), - ), - |(_, attr)| attr, - )(input) -} - -fn quoted_string(input: &str) -> IResult<&str, &str> { - delimited(char('"'), take_until("\""), char('"'))(input) -} - -fn level(input: &str) -> IResult<&str, LogLevel> { - alt(( - value(LogLevel::Trace, tag("TRACE")), - value(LogLevel::Debug, tag("DEBUG")), - value(LogLevel::Info, tag("INFO")), - value(LogLevel::Warn, tag("WARN")), - value(LogLevel::Error, tag("ERROR")), - ))(input) -} - -/// Parses the message of the event. -fn message(input: &str) -> IResult<&str, &str> { - delimited( - tag(""), - whitespace(delimited(tag(""), tag("]]>"))), - tag(""), - )(input) -} - -#[allow(clippy::cast_sign_loss)] -fn plain_text(input: &str) -> IResult<&str, (Attributes<'_>, &str, &str)> { - map(take_until("<"), |text| { - ( - Attributes { - logger: "OneLauncher", - level: LogLevel::Info, - timestamp: chrono::Local::now().timestamp_millis() as u64, - thread_name: "N/A", - }, - "", - text, - ) - })(input) -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - - #[test] - fn parse_message() { - message( - r" - - ", - ) - .unwrap(); - } - - #[test] - fn parse_attributes() { - let (_, attributes) = attributes( - r#" - logger="com.mojang.datafixers.DataFixerBuilder" - timestamp="3213819318239" - level="DEBUG" - thread="Datafixer" - > - "#, - ) - .unwrap(); - - assert_eq!( - attributes, - Attributes { - logger: "com.mojang.datafixers.DataFixerBuilder", - level: LogLevel::Debug, - timestamp: 3_213_819_318_239, - thread_name: "Datafixer" - } - ); - } - - #[test] - fn parse_logger_attribute() { - let (_, attr) = attr_logger(r#"logger="com.mojang.datafixers.DataFixerBuilder""#).unwrap(); - - assert_eq!( - attr, - Attribute::Logger("com.mojang.datafixers.DataFixerBuilder") - ); - } - - #[test] - fn parse_level_attribute() { - let (_, attr) = attr_level(r#"level="INFO""#).unwrap(); - assert_eq!(attr, Attribute::Level(LogLevel::Info)); - } - - #[test] - fn parse_timestamp_attribute() { - let (_, attr) = attr_timestamp(r#"timestamp="313213473127""#).unwrap(); - assert_eq!(attr, Attribute::Timestamp(313_213_473_127)); - } - - #[test] - fn parse_thread_attribute() { - let (_, attr) = attr_thread(r#"thread="Datafixer""#).unwrap(); - assert_eq!(attr, Attribute::ThreadName("Datafixer")); - } - - #[test] - fn parse_quoted_string() { - let (_, res) = quoted_string(r#""girlskissing""#).unwrap(); - - assert_eq!(res, "girlskissing"); - } - - #[test] - fn parse_single_entry() { - let (_, entry) = parse_logentry( - r#" - - - - - - "#, - ) - .unwrap(); - - assert_eq!( - entry, - LogEntry { - logger: "com.mojang.datafixers.DataFixerBuilder", - level: LogLevel::Debug, - timestamp: 3_213_712_731_731, - thread_name: "Datafixer", - message: "200 Datafixer optimizations took 2000 years", - } - ); - } - - // #[test] - // fn parse_sample_log_entries() { - // let mut input = include_str!("../../fixtures/test_log.xml"); - - // while let Ok((o, _)) = parse_logentry(input) { - // input = o; - // } - - // assert_eq!(input, "exit code: 0"); - // } -} diff --git a/packages/utils/src/platform.rs b/packages/utils/src/platform.rs deleted file mode 100644 index 4e078a44..00000000 --- a/packages/utils/src/platform.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! **Platform Utilities** -//! -//! Async utilities for managing OS-specific [`interpulse`] operations and rules. - -use interpulse::api::minecraft::{Os, OsRule}; -use regex::Regex; - -/// An extension of [`Os`] for utilities on converting the current system's operating system -/// into an [`Os`] structure for simple management. -pub trait OsExt { - /// Get the [`Os`] of the current system - fn native() -> Self; - - /// Get the [`Os`] of the current system along with it's arch from the `java_arch`. - fn native_arch(java_arch: &str) -> Self; -} - -impl OsExt for Os { - fn native_arch(java_arch: &str) -> Self { - if std::env::consts::OS == "windows" { - if java_arch == "aarch64" { - Self::WindowsArm64 - } else { - Self::Windows - } - } else if std::env::consts::OS == "linux" { - if java_arch == "aarch64" { - Self::LinuxArm64 - } else if java_arch == "arm" { - Self::LinuxArm32 - } else { - Self::Linux - } - } else if std::env::consts::OS == "macos" { - if java_arch == "aarch64" { - Self::OsxArm64 - } else { - Self::Osx - } - } else { - Self::Unknown - } - } - - fn native() -> Self { - match std::env::consts::OS { - "windows" => Self::Windows, - "macos" => Self::Osx, - "linux" => Self::Linux, - _ => Self::Unknown, - } - } -} - -/// Handles an os-specific [`OsRule`], returning if the [`Os`] matches the rule. -#[must_use] -pub fn os_rule(rule: &OsRule, java_arch: &str, updated: bool) -> bool { - let mut rule_match = true; - - if let Some(ref arch) = rule.arch { - rule_match &= !matches!(arch.as_str(), "x86" | "arm"); - } - - if let Some(name) = &rule.name { - if updated && (name != &Os::LinuxArm64 || name != &Os::LinuxArm32) { - rule_match &= &Os::native() == name || &Os::native_arch(java_arch) == name; - } else { - rule_match &= &Os::native_arch(java_arch) == name; - } - } - - if let Some(version) = &rule.version { - if let Ok(regex) = Regex::new(version.as_str()) { - rule_match &= regex.is_match(&sys_info::os_release().unwrap_or_default()); - } - } - - rule_match -} - -/// Gets the seperator between Java classpaths in metadata (`";"` on Windows, `";"` on others). -#[must_use] -pub fn classpath_separator(java_arch: &str) -> &'static str { - match Os::native_arch(java_arch) { - Os::Osx | Os::OsxArm64 | Os::Linux | Os::LinuxArm32 | Os::LinuxArm64 | Os::Unknown => ":", - Os::Windows | Os::WindowsArm64 => ";", - } -}