From f7b4b643ebc52a4d72d90d9adbdddc9aa0721e4a Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Wed, 25 Feb 2026 16:10:23 +0100 Subject: Feat: Refactor core download logic with concurrency and async features Implement basic unit testing Implement automatic java executable switching based on game version Split loader module into smaller modules Implement basic documentation --- src/config/file.rs | 26 ++++++++++ src/config/loader.rs | 129 ++++++++++++++++++++++++++++++-------------------- src/config/mod.rs | 17 ++++--- src/config/runtime.rs | 95 +++++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+), 61 deletions(-) create mode 100644 src/config/file.rs create mode 100644 src/config/runtime.rs (limited to 'src/config') diff --git a/src/config/file.rs b/src/config/file.rs new file mode 100644 index 0000000..17f2cb2 --- /dev/null +++ b/src/config/file.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; + +use serde::Deserialize; + +use crate::minecraft::launcher::JavaRuntime; + +#[derive(Debug, Deserialize)] +pub struct FileConfig { + pub username: String, + pub uuid: String, + pub version: String, + pub max_memory_mb: u32, + + #[serde(default)] + pub jvm_args: Vec, + + #[serde(default)] + pub runtimes: Vec, + + #[serde(default)] + pub java_path: String, + + pub data_dir: PathBuf, + // pub cache_dir: PathBuf, + // pub config_dir: PathBuf, +} diff --git a/src/config/loader.rs b/src/config/loader.rs index d4b142e..47514e5 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -1,65 +1,90 @@ -use std::{env::var, fs::read_to_string, path::PathBuf}; - +use std::{env, fs::read_to_string, path::PathBuf}; use directories::ProjectDirs; -use serde::Deserialize; +use uuid::Uuid; +use super::{file::FileConfig, runtime::RuntimeConfig}; use crate::{constants::*, errors::McError}; -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub struct Config { - pub username: String, - pub uuid: String, - pub version: String, - pub java_path: String, - pub max_memory_mb: u32, - pub data_dir: PathBuf, - pub cache_dir: PathBuf, - pub config_dir: PathBuf, - #[serde(default)] - pub jvm_args: Vec, -} -impl Config { - pub fn load() -> Result { - let cfg_path = default_config_path()?; - let mut cfg: Config = if cfg_path.exists() { - let txt = read_to_string(&cfg_path)?; - toml::from_str(&txt).map_err(|e| McError::Config(e.to_string()))? + +pub struct ConfigLoader; + +impl ConfigLoader { + pub fn load(cfg_file: Option<&PathBuf>) -> Result { + let path = match cfg_file { + | Some(p) => p.clone(), + | None => Self::default_config_path()?, + }; + + let mut file_cfg = if path.exists() { + Self::read_file(&path)? } else { - Self::default() + Self::default_file_config()? }; - if let Ok(v) = var("MC_USERNAME") { - cfg.username = v; - } - if let Ok(v) = var("MC_VERSION") { - cfg.version = v; - } - if let Ok(v) = var("MC_JAVA_PATH") { - cfg.java_path = v; - } - if let Ok(v) = var("MC_MAX_MEMORY_MB") { - cfg.max_memory_mb = v.parse().unwrap_or(cfg.max_memory_mb); - } - Ok(cfg) + + Self::apply_env_overrides(&mut file_cfg); + + Ok(RuntimeConfig::from_file(file_cfg)) + } + + fn read_file(path: &PathBuf) -> Result { + let content = read_to_string(path).map_err(|e| { + McError::Config(format!( + "Failed to read config file {}: {}", + path.display(), + e + )) + })?; + + toml::from_str(&content).map_err(|e| { + McError::Config(format!("Failed to parse config file: {}", e)) + }) } - fn default() -> Self { - let base = - ProjectDirs::from("com", "example", "dml").expect("platform dirs"); - Self { - username: "Player".into(), - uuid: uuid::Uuid::new_v4().to_string(), + fn apply_env_overrides(cfg: &mut FileConfig) { + cfg.username = env::var(ENV_USERNAME) + .unwrap_or_else(|_| DEFAULT_USERNAME.to_string()); + + cfg.version = env::var(ENV_VERSION) + .unwrap_or_else(|_| DEFAULT_VERSION.to_string()); + + cfg.java_path = env::var(ENV_JAVA_PATH) + .unwrap_or_else(|_| DEFAULT_JAVA_PATH.to_string()); + + cfg.max_memory_mb = env::var(ENV_MAX_MEMORY_MB) + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(DEFAULT_MAX_MEMORY_MB); + } + + fn default_config_path() -> Result { + let base = ProjectDirs::from( + DEFAULT_COMPANY, + DEFAULT_PROJECT_GROUP, + DEFAULT_PROJECT_NAME, + ) + .ok_or_else(|| McError::Config(DEFAULT_ERR_PLATFORM_DIR.into()))?; + + Ok(base.config_dir().join(DEFAULT_CONFIG_FILENAME)) + } + + fn default_file_config() -> Result { + let base = ProjectDirs::from( + DEFAULT_COMPANY, + DEFAULT_PROJECT_GROUP, + DEFAULT_PROJECT_NAME, + ) + .ok_or_else(|| McError::Config(DEFAULT_ERR_PLATFORM_DIR.into()))?; + + Ok(FileConfig { + username: DEFAULT_USERNAME.into(), + uuid: Uuid::new_v4().to_string(), version: DEFAULT_VERSION.into(), - java_path: DEFAULT_JAVA_PATH.into(), max_memory_mb: DEFAULT_MAX_MEMORY_MB, + java_path: DEFAULT_JAVA_PATH.into(), data_dir: base.data_dir().into(), - cache_dir: base.cache_dir().into(), - config_dir: base.config_dir().into(), + // cache_dir: base.cache_dir().into(), + // config_dir: base.config_dir().into(), jvm_args: vec![], - } + runtimes: vec![], + }) } } -fn default_config_path() -> Result { - let base = ProjectDirs::from("com", "example", "dml") - .ok_or_else(|| McError::Config("cannot determine config dir".into()))?; - Ok(base.config_dir().join("config.toml")) -} diff --git a/src/config/mod.rs b/src/config/mod.rs index 066154a..a375dda 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,12 +1,11 @@ //! Configuration module for the DML launcher. -//! -//! This module contains submodules and helpers for loading, parsing, and -//! managing configuration files and settings. It abstracts the details -//! of where configuration is stored and how it is represented in memory. -//! -//! # Submodules -//! - `loader`: Functions to load and parse configuration from disk or -//! environment variables. +pub mod file; pub mod loader; -pub use loader::Config; +pub mod runtime; + +pub use loader::ConfigLoader; +pub use runtime::RuntimeConfig; + +/// Backwards-compatibility alias so existing code can keep using `Config` +pub type Config = RuntimeConfig; diff --git a/src/config/runtime.rs b/src/config/runtime.rs new file mode 100644 index 0000000..46857f0 --- /dev/null +++ b/src/config/runtime.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use super::file::FileConfig; +use crate::{constants::*, minecraft::launcher::JavaRuntime}; + +/// Configuration for the runtime environment used to launch Minecraft. +/// +/// The `RuntimeConfig` struct holds the configuration settings required to +/// launch Minecraft with a specific Java runtime. This includes details about +/// the user, the Minecraft version, the available Java runtimes, and JVM options. +/// +/// The `RuntimeConfig` struct is typically used by the launcher to store and +/// manage the necessary settings for running the game. It also supports +/// dynamic configuration by allowing the addition of Java runtimes and the +/// specification of system paths and arguments. +#[derive(Debug)] +#[derive(Default)] +pub struct RuntimeConfig { + /// The username of the player running Minecraft. + pub username: String, + + /// The UUID (unique identifier) of the user. + pub uuid: String, + + /// The version of Minecraft that the user is running. + pub version: String, + + /// The maximum amount of memory (in megabytes) allocated to the Java process. + /// This setting determines how much memory the Minecraft instance can use. + pub max_memory_mb: u32, + + /// A list of arguments to pass to the JVM when launching Minecraft. + /// These arguments can be used to customize the JVM's behavior, such as + /// memory settings, garbage collection options, etc. + pub jvm_args: Vec, + + /// A list of Java runtime environments available for Minecraft. + /// If no specific Java runtime is provided, the default one is used. + pub runtimes: Vec, + + /// The directory where Minecraft data (e.g., worlds, logs, configs) is stored. + /// This path is critical for loading and saving game data. + pub data_dir: PathBuf, +} + +impl RuntimeConfig { + /// Creates a new `RuntimeConfig` instance from a `FileConfig`. + /// + /// This method takes a `FileConfig` object (typically loaded from a configuration file) + /// and constructs a `RuntimeConfig` instance. It ensures that if no runtimes are defined + /// but a Java path is provided, a default Java runtime is created based on the specified path. + /// + /// # Parameters + /// - `file`: A `FileConfig` instance containing initial configuration values for the + /// Minecraft runtime. This includes user details, Minecraft version, JVM arguments, + /// and the path to Java (if specified). + /// + /// # Returns + /// - A `RuntimeConfig` instance initialized with the values from `file`. If no runtimes + /// were provided in `file` but a Java path was given, the method will add a default Java runtime. + /// + /// # Example + /// ```rust + /// let file_config = FileConfig { + /// username: "Player1".into(), + /// uuid: "1234-5678-9101".into(), + /// version: "1.18.2".into(), + /// max_memory_mb: 2048, + /// jvm_args: vec!["-Xmx2G".into()], + /// runtimes: Vec::new(), + /// java_path: "/path/to/java".into(), + /// data_dir: "/path/to/minecraft/data".into(), + /// }; + /// + /// let runtime_config = RuntimeConfig::from_file(file_config); + /// ``` + pub fn from_file(mut file: FileConfig) -> Self { + if file.runtimes.is_empty() && !file.java_path.is_empty() { + file.runtimes.push(JavaRuntime { + major: DEFAULT_JAVA_MAJOR, + path: PathBuf::from(&file.java_path), + }); + } + + Self { + username: file.username, + uuid: file.uuid, + version: file.version, + max_memory_mb: file.max_memory_mb, + jvm_args: file.jvm_args, + runtimes: file.runtimes, + data_dir: file.data_dir, + } + } +} -- cgit v1.2.3