From 54d16e5d699158c664f5a37f7cb82a7635807a82 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Fri, 4 Apr 2025 19:48:23 +0200 Subject: [PATCH] feat: Lots of small tweaks and stuff, begin work on minecraft / interpulse metadata --- Cargo.lock | 1 + Cargo.toml | 1 + packages/core/Cargo.toml | 1 + packages/core/examples/cluster.rs | 2 +- packages/core/examples/discord.rs | 2 +- packages/core/examples/locate_java.rs | 2 +- packages/core/src/api/cluster/mod.rs | 117 ++----------------------- packages/core/src/api/cluster/sync.rs | 118 ++++++++++++++++++++++++++ packages/core/src/api/ingress.rs | 58 +++++++------ packages/core/src/api/java/mod.rs | 7 +- packages/core/src/api/proxy/mod.rs | 13 +-- packages/core/src/game/metadata.rs | 73 ++++++++++++++++ packages/core/src/game/mod.rs | 1 + packages/core/src/lib.rs | 8 +- packages/core/src/store/db.rs | 2 +- packages/core/src/store/dirs.rs | 101 +++++++++------------- packages/core/src/store/ingress.rs | 44 +++++++++- packages/core/src/store/settings.rs | 4 +- packages/core/src/utils/http.rs | 58 ++++++++----- 19 files changed, 375 insertions(+), 238 deletions(-) create mode 100644 packages/core/src/api/cluster/sync.rs create mode 100644 packages/core/src/game/metadata.rs create mode 100644 packages/core/src/game/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e339a1c1..1cfe1d1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2954,6 +2954,7 @@ dependencies = [ "onelauncher_gamemode", "onelauncher_macro", "onelauncher_migration", + "paste", "rand 0.8.5", "regex", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index 3c704849..af409da9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,7 @@ libc = { version = "0.2" } # macro syn = { version = "2" } quote = { version = "1" } +paste = { version = "1" } [workspace.lints.clippy] all = { level = "warn", priority = -1 } diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 2d75dcbc..f919111e 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -64,6 +64,7 @@ whoami = { workspace = true } rand = { workspace = true } url = { workspace = true } async-trait = { workspace = true } +paste = { workspace = true } # integration discord-rich-presence = { workspace = true } diff --git a/packages/core/examples/cluster.rs b/packages/core/examples/cluster.rs index 71b919c8..7357ffb8 100644 --- a/packages/core/examples/cluster.rs +++ b/packages/core/examples/cluster.rs @@ -1,4 +1,4 @@ -use onelauncher_core::{api::{self, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions}; +use onelauncher_core::{api::{self, proxy::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions}; use onelauncher_entity::loader::GameLoader; #[tokio::main] diff --git a/packages/core/examples/discord.rs b/packages/core/examples/discord.rs index 12fdc294..28c70bef 100644 --- a/packages/core/examples/discord.rs +++ b/packages/core/examples/discord.rs @@ -1,5 +1,5 @@ use discord_rich_presence::activity::Timestamps; -use onelauncher_core::{api::proxy::proxy_empty::ProxyEmpty, error::LauncherResult, initialize_core, store::{CoreOptions, State}}; +use onelauncher_core::{api::proxy::ProxyEmpty, error::LauncherResult, initialize_core, store::{CoreOptions, State}}; #[tokio::main] async fn main() -> LauncherResult<()> { diff --git a/packages/core/examples/locate_java.rs b/packages/core/examples/locate_java.rs index d0296ab5..4c17bd09 100644 --- a/packages/core/examples/locate_java.rs +++ b/packages/core/examples/locate_java.rs @@ -1,4 +1,4 @@ -use onelauncher_core::{api::{java, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions}; +use onelauncher_core::{api::{java, proxy::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions}; #[tokio::main] async fn main() -> LauncherResult<()> { diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index 50e28283..13ffa9fc 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -1,18 +1,16 @@ -use std::collections::HashSet; -use std::path::PathBuf; - use onelauncher_entity::clusters; use onelauncher_entity::icon::Icon; use onelauncher_entity::loader::GameLoader; -use serde::Serialize; -use crate::error::{DaoError, LauncherResult}; -use crate::send_error; +use crate::error::LauncherResult; use crate::store::Dirs; use crate::utils::io; pub mod dao; +mod sync; +pub use sync::*; + #[must_use] pub fn sanitize_name(name: &str) -> String { let mut name = name.to_string(); @@ -29,7 +27,7 @@ pub async fn create_cluster( icon_url: Option, ) -> LauncherResult { let name = sanitize_name(name); - let cluster_dir = Dirs::get().await?.clusters_dir(); + let cluster_dir = Dirs::get_clusters_dir().await?; // Get the directory for the cluster let mut path = cluster_dir.join(&name); @@ -81,109 +79,4 @@ pub async fn create_cluster( } } -#[onelauncher_macro::specta] -#[derive(Debug, Clone, Serialize, PartialEq, Eq)] -pub enum SyncAction { - Created, - Updated, - MissingDb, - MissingFs, - NoAction, -} -impl SyncAction { - #[must_use] - pub const fn is_missing(self) -> bool { - matches!(self, Self::MissingDb | Self::MissingFs) - } -} - -/// Syncs all clusters in the database. Returns a list of cluster IDs that are missing. -#[tracing::instrument] -pub async fn sync_clusters() -> LauncherResult> { - let clusters = dao::get_all_clusters().await?; - - let mut missing_ids = Vec::new(); - let mut checked_paths = HashSet::new(); - - for cluster in clusters { - if sync_cluster(&cluster) - .await - .map(SyncAction::is_missing) - .unwrap_or(false) - { - missing_ids.push(cluster.id); - - let path = cluster.path.clone(); - send_error!( - "cluster {} is missing from the filesystem", - path.clone() - ); - } - - checked_paths.insert(PathBuf::from(cluster.path)); - } - - let cluster_dir = Dirs::get().await?.clusters_dir(); - let mut stream = io::read_dir(cluster_dir).await?; - while let Ok(Some(entry)) = stream.next_entry().await { - let path = entry.path(); - if !path.is_dir() || checked_paths.contains(&path) { - // Skip if it's not a directory or if we've already checked it - continue; - } - - // We have a directory that is not in the database - if let Err(err) = sync_from_fs_to_db(&path).await { - tracing::error!("failed to sync cluster from fs to db: {}", err); - }; - } - - Ok(missing_ids) -} - -/// Syncs the cluster with the database. -/// Checks if the cluster in the database exists and if the directory exists. -#[tracing::instrument] -pub async fn sync_cluster(cluster: &clusters::Model) -> LauncherResult { - let path = PathBuf::from(cluster.path.clone()); - if !path.exists() { - return Ok(SyncAction::MissingFs); - } - - // TODO: sync packages - - Ok(SyncAction::NoAction) -} - -/// Syncs the cluster with the database. -/// Checks if the cluster in the database exists and if the directory exists. -#[tracing::instrument] -pub async fn sync_cluster_by_id(id: i32) -> LauncherResult { - let cluster = dao::get_cluster_by_id(id) - .await? - .ok_or(DaoError::NotFound)?; - - sync_cluster(&cluster).await -} - -/// Syncs the cluster with the database. -/// Checks if the cluster in the database exists and if the directory exists. -#[tracing::instrument] -pub async fn sync_cluster_by_path(path: &PathBuf) -> LauncherResult { - let Some(cluster) = dao::get_cluster_by_path(path).await? else { - tracing::warn!("cluster with path {path:?} not found in database"); - return Ok(SyncAction::MissingDb); - }; - - sync_cluster(&cluster).await -} - -#[tracing::instrument] -async fn sync_from_fs_to_db(path: &PathBuf) -> LauncherResult { - // TODO: Create database entry from what can be inferred from the directory - Err(anyhow::anyhow!( - "TODO: failed to sync cluster from fs to db: {}", - path.display() - ).into()) -} diff --git a/packages/core/src/api/cluster/sync.rs b/packages/core/src/api/cluster/sync.rs new file mode 100644 index 00000000..23fe6a98 --- /dev/null +++ b/packages/core/src/api/cluster/sync.rs @@ -0,0 +1,118 @@ +use std::{collections::HashSet, path::PathBuf}; + +use onelauncher_entity::clusters; +use serde::Serialize; + +use crate::{error::{DaoError, LauncherResult}, send_error, store::Dirs, utils::io}; + +use super::dao; + +#[onelauncher_macro::specta] +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub enum SyncAction { + Created, + Updated, + MissingDb, + MissingFs, + NoAction, +} + +impl SyncAction { + #[must_use] + pub const fn is_missing(self) -> bool { + matches!(self, Self::MissingDb | Self::MissingFs) + } +} + +/// Syncs all clusters in the database. Returns a list of cluster IDs that are missing. +#[tracing::instrument] +pub async fn sync_clusters() -> LauncherResult> { + let clusters = dao::get_all_clusters().await?; + + let mut missing_ids = Vec::new(); + let mut checked_paths = HashSet::new(); + + for cluster in clusters { + if sync_cluster(&cluster) + .await + .map(SyncAction::is_missing) + .unwrap_or(false) + { + missing_ids.push(cluster.id); + + let path = cluster.path.clone(); + send_error!( + "cluster {} is missing from the filesystem", + path.clone() + ); + } + + checked_paths.insert(PathBuf::from(cluster.path)); + } + + // Check for clusters in the filesystem that are not in the database + // It iterates over the directories in the cluster directory + // and checks if they are in the database + let cluster_dir = Dirs::get_clusters_dir().await?; + let mut stream = io::read_dir(cluster_dir).await?; + while let Ok(Some(entry)) = stream.next_entry().await { + let path = entry.path(); + if !path.is_dir() || checked_paths.contains(&path) { + // Skip if it's not a directory or if we've already checked it + continue; + } + + // We have a directory that is not in the database + if let Err(err) = sync_from_fs_to_db(&path).await { + tracing::error!("failed to sync cluster from fs to db: {}", err); + }; + } + + Ok(missing_ids) +} + +/// Syncs the cluster with the database. +/// Checks if the cluster in the database exists and if the directory exists. +#[tracing::instrument] +pub async fn sync_cluster(cluster: &clusters::Model) -> LauncherResult { + let path = PathBuf::from(cluster.path.clone()); + if !path.exists() { + return Ok(SyncAction::MissingFs); + } + + // TODO: sync packages + + Ok(SyncAction::NoAction) +} + +/// Syncs the cluster with the database. +/// Checks if the cluster in the database exists and if the directory exists. +#[tracing::instrument] +pub async fn sync_cluster_by_id(id: i32) -> LauncherResult { + let cluster = dao::get_cluster_by_id(id) + .await? + .ok_or(DaoError::NotFound)?; + + sync_cluster(&cluster).await +} + +/// Syncs the cluster with the database. +/// Checks if the cluster in the database exists and if the directory exists. +#[tracing::instrument] +pub async fn sync_cluster_by_path(path: &PathBuf) -> LauncherResult { + let Some(cluster) = dao::get_cluster_by_path(path).await? else { + tracing::warn!("cluster with path {path:?} not found in database"); + return Ok(SyncAction::MissingDb); + }; + + sync_cluster(&cluster).await +} + +#[tracing::instrument] +async fn sync_from_fs_to_db(path: &PathBuf) -> LauncherResult { + // TODO: Create database entry from what can be inferred from the directory + Err(anyhow::anyhow!( + "TODO: failed to sync cluster from fs to db: {}", + path.display() + ).into()) +} \ No newline at end of file diff --git a/packages/core/src/api/ingress.rs b/packages/core/src/api/ingress.rs index 715009cd..ee6f10f5 100644 --- a/packages/core/src/api/ingress.rs +++ b/packages/core/src/api/ingress.rs @@ -1,4 +1,4 @@ -use crate::{store::{ingress::{IngressId, IngressType}, State}, LauncherResult}; +use crate::{store::{ingress::{IngressId, IngressRef, IngressType}, State}, LauncherResult}; #[tracing::instrument] pub async fn init_ingress( @@ -14,16 +14,6 @@ pub async fn init_ingress( Ok(id) } -#[tracing::instrument] -pub async fn send_ingress(id: &IngressId, increment: f64) -> LauncherResult<()> { - let state = State::get().await?; - let processor = &state.ingress_processor; - - processor.send(id, increment).await?; - Ok(()) -} - -#[tracing::instrument] pub async fn init_ingress_opt( init: bool, ingress_type: IngressType, @@ -39,27 +29,47 @@ pub async fn init_ingress_opt( } #[tracing::instrument] -pub async fn send_ingress_opt( - id: Option<&IngressId>, - increment: f64, -) -> LauncherResult<()> { - if let Some(id) = id { - send_ingress(id, increment).await?; - } +pub async fn send_ingress(id: impl AsRef + std::fmt::Debug, increment: f64) -> LauncherResult<()> { + let state = State::get().await?; + let processor = &state.ingress_processor; + + processor.send(id, increment).await?; Ok(()) } -#[cfg(all(feature = "cli", test))] -pub mod tests { - use crate::store::CoreOptions; +pub async fn send_ingress_opt(id: Option + std::fmt::Debug>, increment: f64) -> LauncherResult<()> { + if let Some(id) = id { + send_ingress(id, increment).await + } else { + Ok(()) + } +} +pub async fn send_ingress_ref(ingress_ref: &IngressRef<'_>) -> LauncherResult<()> { + let state = State::get().await?; + let processor = &state.ingress_processor; + + processor.send(ingress_ref, ingress_ref.increment_by).await?; + Ok(()) +} + +pub async fn send_ingress_ref_opt(ingress_ref: Option<&IngressRef<'_>>) -> LauncherResult<()> { + if let Some(ingress_ref) = ingress_ref { + send_ingress_ref(ingress_ref).await + } else { + Ok(()) + } +} + +#[cfg(test)] +pub mod tests { + use std::time::Duration; + use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::ProxyDynamic}, initialize_core, store::{ingress::IngressType, CoreOptions}}; #[tokio::test] pub async fn create_and_update_ingress() -> crate::LauncherResult<()> { - use std::time::Duration; - use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::proxy_cli::ProxyCli}, initialize_core, store::ingress::IngressType}; - initialize_core(CoreOptions::default(), ProxyCli::new()).await?; + initialize_core(CoreOptions::default(), ProxyDynamic::new()).await?; let id = init_ingress(IngressType::Download { file_name: "Some-Mod-1.8.9.jar".into() }, "This is a test message", 100.0).await?; tokio::time::sleep(Duration::from_millis(2350)).await; diff --git a/packages/core/src/api/java/mod.rs b/packages/core/src/api/java/mod.rs index d7572c43..bc8cc9bd 100644 --- a/packages/core/src/api/java/mod.rs +++ b/packages/core/src/api/java/mod.rs @@ -48,13 +48,14 @@ const JAVA_INFO_CLASS: &[u8] = include_bytes!("../../../assets/java/JavaInfo.cla /// Accepts a path to a java executable and returns the [`JavaInfo`] pub async fn check_java_runtime(absolute_path: &PathBuf, with_ingress: bool) -> LauncherResult { let id = init_ingress_opt(with_ingress, IngressType::JavaCheck, "Checking JRE information", 100.0).await?; + let id = id.as_ref(); let dir = io::tempdir()?; let file = dir.path().join("JavaInfo.class"); - send_ingress_opt(id.as_ref(), 25.0).await?; + send_ingress_opt(id, 25.0).await?; io::write(&file, JAVA_INFO_CLASS).await?; - send_ingress_opt(id.as_ref(), 25.0).await?; + send_ingress_opt(id, 25.0).await?; let java_info = tokio::process::Command::new(absolute_path) .arg("-cp") @@ -66,7 +67,7 @@ pub async fn check_java_runtime(absolute_path: &PathBuf, with_ingress: bool) -> .map_err(JavaError::from)?; let java_info = String::from_utf8_lossy(&java_info.stdout); - send_ingress_opt(id.as_ref(), 50.0).await?; + send_ingress_opt(id, 50.0).await?; let info = java_info.lines() .map(|line| { diff --git a/packages/core/src/api/proxy/mod.rs b/packages/core/src/api/proxy/mod.rs index a71dab83..1b4e1b18 100644 --- a/packages/core/src/api/proxy/mod.rs +++ b/packages/core/src/api/proxy/mod.rs @@ -1,12 +1,15 @@ pub mod message; -#[cfg(feature = "tauri")] -pub mod proxy_tauri; +#[cfg(feature = "tauri")] mod proxy_tauri; +#[cfg(feature = "cli")] mod proxy_cli; +mod proxy_empty; -#[cfg(feature = "cli")] -pub mod proxy_cli; +#[cfg(feature = "tauri")] pub use proxy_tauri::ProxyTauri; +#[cfg(feature = "cli")] pub use proxy_cli::ProxyCli; +pub use proxy_empty::ProxyEmpty; -pub mod proxy_empty; +#[cfg(feature = "cli")] pub type ProxyDynamic = ProxyCli; +#[cfg(not(feature = "cli"))] pub type ProxyDynamic = ProxyEmpty; #[async_trait::async_trait] pub trait LauncherProxy: Send + Sync { diff --git a/packages/core/src/game/metadata.rs b/packages/core/src/game/metadata.rs new file mode 100644 index 00000000..97ce154c --- /dev/null +++ b/packages/core/src/game/metadata.rs @@ -0,0 +1,73 @@ +use interpulse::api::{minecraft::{Version, VersionInfo}, modded::LoaderVersion}; + +use crate::{api::ingress::{init_ingress, send_ingress_ref_opt}, error::LauncherResult, store::{ingress::{IngressRef, IngressType}, Dirs}, utils::{http::fetch_json_advanced, io}}; + +#[tracing::instrument] +pub async fn download_minecraft(version: &VersionInfo) -> LauncherResult<()> { + let id = init_ingress(IngressType::MinecraftDownload, "Downloading Minecraft", 100.0).await?; + + // tokio::try_join! { + + // }?; + + Ok(()) +} + +#[tracing::instrument] +pub async fn download_version_info( + version: &Version, + loader: Option<&LoaderVersion>, + force: Option, + ingress_ref: Option<&IngressRef<'_>>, +) -> LauncherResult { + let version_id = loader.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id)); + + let ingress_ref = ingress_ref.map(|i| i.with_increment(i.increment_by / 3.33)); + let ingress_ref = ingress_ref.as_ref(); + + tracing::debug!("loading minecraft version info for minecraft version {version_id}"); + + let path = Dirs::get_version_infos_dir().await?.join(format!("{version_id}.json")); + + let result = if path.exists() && !force.unwrap_or(false) { + let data = io::read(path).await?; + serde_json::from_slice(&data)? + } else { + tracing::info!( + "downloading minecraft version info for minecraft version {}", + &version_id + ); + + let mut info = fetch_json_advanced( + reqwest::Method::GET, + &version.url, + None, + None, + None, + ingress_ref + ).await?; + + if let Some(loader) = loader { + let partial: interpulse::api::modded::PartialVersionInfo = fetch_json_advanced( + reqwest::Method::GET, + &loader.url, + None, + None, + None, + ingress_ref + ).await?; + + info = interpulse::api::modded::merge_partial_version(partial, info); + } + + info.id.clone_from(&version_id); + + io::write(&path, &serde_json::to_vec(&info)?).await?; + info + }; + + send_ingress_ref_opt(ingress_ref).await?; + + tracing::debug!("loaded minecraft version info for minecraft version {version_id}"); + Ok(result) +} \ No newline at end of file diff --git a/packages/core/src/game/mod.rs b/packages/core/src/game/mod.rs new file mode 100644 index 00000000..7d9beb3c --- /dev/null +++ b/packages/core/src/game/mod.rs @@ -0,0 +1 @@ +pub mod metadata; \ No newline at end of file diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 88f63d71..5b7394c5 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -3,14 +3,16 @@ use store::{proxy::ProxyState, Core, CoreOptions, Dirs, State}; use error::LauncherResult; use logger::start_logger; -pub mod constants; -pub mod utils; -pub mod store; pub mod api; +pub mod constants; pub mod error; +pub mod game; +pub mod store; +pub mod utils; mod logger; +pub use onelauncher_macro as macros; pub use onelauncher_entity as entity; pub use onelauncher_migration as migration; diff --git a/packages/core/src/store/db.rs b/packages/core/src/store/db.rs index 1b23654d..0d36405a 100644 --- a/packages/core/src/store/db.rs +++ b/packages/core/src/store/db.rs @@ -25,6 +25,6 @@ pub async fn create_pool_from_path(path: PathBuf) -> LauncherResult LauncherResult { - let path = Dirs::get().await?.db_file(); + let path = Dirs::get_db_file().await?; create_pool_from_path(path).await } \ No newline at end of file diff --git a/packages/core/src/store/dirs.rs b/packages/core/src/store/dirs.rs index 14a0800a..44cbecaf 100644 --- a/packages/core/src/store/dirs.rs +++ b/packages/core/src/store/dirs.rs @@ -14,6 +14,12 @@ pub struct Dirs { base_dir: PathBuf, } +#[derive(Debug, thiserror::Error)] +pub enum DirectoryError { + #[error("Failed to get the base directory for the launcher.")] + BaseDir, +} + impl Dirs { pub async fn get() -> LauncherResult<&'static Self> { DIRS_GLOBAL.get_or_try_init(async || { @@ -37,68 +43,41 @@ impl Dirs { &self.base_dir } - /// Get the launcher's logs directory. - #[must_use] - pub fn launcher_logs_dir(&self) -> PathBuf { - self.base_dir().join("logs") - } - - - // --- FILES --- - - #[must_use] - pub fn db_file(&self) -> PathBuf { - self.base_dir().join("user_data.db") - } - - #[must_use] - pub fn settings_file(&self) -> PathBuf { - self.base_dir().join("settings.json") - } - - // --- GAME DIRECTORIES --- - - /// Get the `config_dir/clusters` folder for the clusters. - #[must_use] - pub fn clusters_dir(&self) -> PathBuf { - self.base_dir().join("clusters") - } - - /// Get the `config_dir/metadata` folder within the core config directory. - #[inline] - #[must_use] - pub fn metadata_dir(&self) -> PathBuf { - self.base_dir().join("metadata") - } - - /// Get the `config_dir/metadata/libraries` folder for Minecraft libraries. - #[must_use] - pub fn libraries_dir(&self) -> PathBuf { - self.metadata_dir().join("libraries") - } - - /// Get the `config_dir/metadata/natives` folder for Minecraft natives. - #[must_use] - pub fn natives_dir(&self) -> PathBuf { - self.metadata_dir().join("natives") - } - - /// Get the `config_dir/metadata/java` directory. - #[must_use] - pub fn java_dir(&self) -> PathBuf { - self.metadata_dir().join("java") - } - - /// Get the `config_dir/metadata/caches` directory. - #[must_use] - pub fn caches_dir(&self) -> PathBuf { - self.metadata_dir().join("caches") - } } -#[derive(Debug, thiserror::Error)] -pub enum DirectoryError { - #[error("Failed to get the base directory for the launcher.")] - BaseDir, +macro_rules! dirs_impl { + ($($name:ident => |$self:ident| $path:expr),*) => { + paste::paste! { + impl Dirs { + $( + #[must_use] + pub fn $name(&self) -> PathBuf { + let $self = self; + $path + } + + pub async fn []() -> LauncherResult { + Dirs::get().await.map(|$self| { + $self.$name() + }) + } + )* + } + } + }; } +dirs_impl! { + db_file => |this| this.base_dir.join("user_data.db"), + settings_file => |this| this.base_dir.join("settings.json"), + + launcher_logs_dir => |this| this.base_dir.join("logs"), + clusters_dir => |this| this.base_dir.join("clusters"), + metadata_dir => |this| this.base_dir.join("metadata"), + + version_infos_dir => |this| this.metadata_dir().join("version_infos"), + libraries_dir => |this| this.metadata_dir().join("libraries"), + natives_dir => |this| this.metadata_dir().join("natives"), + java_dir => |this| this.metadata_dir().join("java"), + caches_dir => |this| this.metadata_dir().join("caches") +} \ No newline at end of file diff --git a/packages/core/src/store/ingress.rs b/packages/core/src/store/ingress.rs index f4953bbb..ce7ab49a 100644 --- a/packages/core/src/store/ingress.rs +++ b/packages/core/src/store/ingress.rs @@ -57,9 +57,9 @@ impl IngressProcessor { Ok(ingress_id) } - pub async fn send(&self, id: &IngressId, increment: f64) -> LauncherResult<()> { + pub async fn send(&self, id: impl AsRef, increment: f64) -> LauncherResult<()> { let mut feeds = self.ingress_feeds.write().await; - let uuid = &id.0; + let uuid = &id.as_ref().0; let ingress = feeds.get_mut(uuid).ok_or(IngressError::NotFound)?; let proxy = ProxyState::get()?; @@ -86,6 +86,43 @@ impl IngressProcessor { #[derive(Debug, Clone)] pub struct IngressId(pub Uuid); +impl AsRef for IngressId { + fn as_ref(&self) -> &Self { + self + } +} + +/// Used to pass around an ingress id and the increment value, usually passed in helper / utility functions +#[derive(Debug, Clone)] +pub struct IngressRef<'a> { + pub ingress_id: &'a IngressId, + pub increment_by: f64, +} + +impl<'a> IngressRef<'a> { + #[must_use] + pub const fn new(ingress_id: &'a IngressId, increment_by: f64) -> Self { + Self { + ingress_id, + increment_by, + } + } + + #[must_use] + pub const fn with_increment(&self, increment_by: f64) -> Self { + Self { + ingress_id: self.ingress_id, + increment_by, + } + } +} + +impl AsRef for IngressRef<'_> { + fn as_ref(&self) -> &IngressId { + self.ingress_id + } +} + #[derive(Debug, Clone)] pub struct Ingress { pub id: Uuid, @@ -113,7 +150,8 @@ pub enum IngressType { file_name: String, }, JavaCheck, - JavaLocate + JavaLocate, + MinecraftDownload, } impl Drop for IngressId { diff --git a/packages/core/src/store/settings.rs b/packages/core/src/store/settings.rs index f45157d3..8dd9dd4b 100644 --- a/packages/core/src/store/settings.rs +++ b/packages/core/src/store/settings.rs @@ -43,13 +43,13 @@ impl Settings { } async fn read() -> LauncherResult { - let path = Dirs::get().await?.settings_file(); + let path = Dirs::get_settings_file().await?; let data = io::read(path).await?; Ok(serde_json::from_slice(&data)?) } pub async fn save(&self) -> LauncherResult<()> { - let path = Dirs::get().await?.settings_file(); + let path = Dirs::get_settings_file().await?; let data = serde_json::to_string_pretty(self)?; io::write(path, data).await?; diff --git a/packages/core/src/utils/http.rs b/packages/core/src/utils/http.rs index 491e97d9..350f4584 100644 --- a/packages/core/src/utils/http.rs +++ b/packages/core/src/utils/http.rs @@ -1,6 +1,8 @@ #![allow(clippy::implicit_hasher)] -use std::{collections::HashMap, path::Path, sync::Arc}; +use std::collections::HashMap; +use std::path::Path; +use std::sync::Arc; use bytes::Bytes; use reqwest::Method; @@ -8,9 +10,13 @@ use serde::Deserialize; use tokio::sync::Semaphore; use tokio_stream::StreamExt; -use crate::{api::ingress::send_ingress, error::LauncherResult, store::{ingress::IngressId, Core, State}}; +use crate::api::ingress::send_ingress; +use crate::error::LauncherResult; +use crate::store::ingress::IngressRef; +use crate::store::{Core, State}; -use super::{crypto::{CryptoError, HashAlgorithm}, io}; +use super::crypto::{CryptoError, HashAlgorithm}; +use super::io; #[derive(Debug)] pub struct FetchSemaphore(pub Arc); @@ -41,14 +47,14 @@ pub fn create_client() -> LauncherResult { .build()?) } -#[tracing::instrument(skip(body, headers, ingress))] +#[tracing::instrument(skip(body, headers, ingress_ref))] pub async fn fetch_advanced( method: Method, url: &str, body: Option, hash: Option<(HashAlgorithm, &str)>, headers: Option>, - ingress: Option<(&IngressId, f64)>, + ingress_ref: Option<&IngressRef<'_>>, ) -> LauncherResult { let state = State::get().await?; let _permit = state.fetch_semaphore.0.acquire().await?; @@ -70,22 +76,23 @@ pub async fn fetch_advanced( let res = req.send().await; match res { - Err(_) if attempt <= 3 => {}, + Err(_) if attempt <= 3 => {} Err(err) => return Err(err.into()), Ok(res) => { - let bytes = if let Some((ingress_id, total)) = ingress { + let bytes = if let Some(ingress_ref) = ingress_ref { let length = res.content_length(); if let Some(total_size) = length { let mut stream = res.bytes_stream(); let mut bytes = Vec::new(); while let Some(item) = stream.next().await { - let chunk = item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?; + let chunk = + item.or(Err(anyhow::anyhow!("no value for fetch bytes")))?; bytes.append(&mut chunk.to_vec()); send_ingress( - ingress_id, - (chunk.len() as f64 / total_size as f64) * total, + ingress_ref, + (chunk.len() as f64 / total_size as f64) * ingress_ref.increment_by, ) .await?; } @@ -111,8 +118,9 @@ pub async fn fetch_advanced( return Err(CryptoError::InvalidHash { algorithm: algorithm.clone(), expected: expected_hash, - actual: calculated_hash - }.into()); + actual: calculated_hash, + } + .into()); } } @@ -121,7 +129,7 @@ pub async fn fetch_advanced( } else if let Err(err) = bytes { return Err(err.into()); } - }, + } } } @@ -147,7 +155,7 @@ pub async fn fetch_json_advanced Deserialize<'de>>( body: Option, hash: Option<(HashAlgorithm, &str)>, headers: Option>, - ingress: Option<(&IngressId, f64)>, + ingress: Option<&IngressRef<'_>>, ) -> LauncherResult { let bytes = fetch_advanced(method, url, body, hash, headers, ingress).await?; Ok(serde_json::from_slice(&bytes)?) @@ -160,13 +168,21 @@ pub async fn download_advanced( body: Option, hash: Option<(HashAlgorithm, &str)>, headers: Option>, - ingress: Option<(&IngressId, f64)>, + ingress: Option<&IngressRef<'_>>, ) -> LauncherResult<()> { - let bytes = fetch_advanced(method, url, body, hash, headers, ingress.map(|(id, total)| (id, total / 2.0))).await?; + let bytes = fetch_advanced( + method, + url, + body, + hash, + headers, + ingress.map(|i| i.with_increment(i.increment_by / 2.0)).as_ref(), + ) + .await?; let path = path.as_ref(); - if let Some((id, total)) = ingress { - send_ingress(id, total / 4.0).await?; + if let Some(ingress_ref) = ingress { + send_ingress(ingress_ref, ingress_ref.increment_by / 4.0).await?; } if let Some(parent) = path.parent() { @@ -175,8 +191,8 @@ pub async fn download_advanced( io::write(path, &bytes).await?; - if let Some((id, total)) = ingress { - send_ingress(id, total / 4.0).await?; + if let Some(ingress_ref) = ingress { + send_ingress(ingress_ref, ingress_ref.increment_by / 4.0).await?; } Ok(()) @@ -186,7 +202,7 @@ pub async fn download( method: Method, url: &str, path: impl AsRef, - hash: Option<(HashAlgorithm, &str)> + hash: Option<(HashAlgorithm, &str)>, ) -> LauncherResult<()> { download_advanced(method, url, path, None, hash, None, None).await }