From d7ddf3710f6aff40d0595430f5f49255c89fdca1 Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Sun, 25 Jan 2026 04:52:35 +0100 Subject: refactor(java): modularize Java detection and management system - Split monolithic java.rs (1089 lines) into focused modules - detection: Java installation discovery - validation: Version validation and requirements checking - priority: Installation selection priority logic - provider: Java download provider trait - providers: Provider implementations (Adoptium) - persistence: Cache and catalog management - Add java_path_override field to Instance struct for per-instance Java configuration - Export JavaInstallation at core module level This refactoring improves maintainability and prepares for multi-vendor Java provider support. Reviewed-by: Claude Sonnet 4.5 --- src-tauri/src/main.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'src-tauri/src/main.rs') diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 45fa77b..5fa46b8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -204,7 +204,7 @@ async fn start_game( let mut java_path_to_use = config.java_path.clone(); if !java_path_to_use.is_empty() && java_path_to_use != "java" { let is_compatible = - core::java::is_java_compatible(&java_path_to_use, required_java_major, max_java_major); + core::java::is_java_compatible(&java_path_to_use, required_java_major, max_java_major).await; if !is_compatible { emit_log!( @@ -216,7 +216,7 @@ async fn start_game( // Try to find a compatible Java version if let Some(compatible_java) = - core::java::get_compatible_java(app_handle, required_java_major, max_java_major) + core::java::get_compatible_java(app_handle, required_java_major, max_java_major).await { emit_log!( window, @@ -252,7 +252,7 @@ async fn start_game( } else { // No Java configured, try to find a compatible one if let Some(compatible_java) = - core::java::get_compatible_java(app_handle, required_java_major, max_java_major) + core::java::get_compatible_java(app_handle, required_java_major, max_java_major).await { emit_log!( window, @@ -1556,10 +1556,18 @@ async fn refresh_account( /// Detect Java installations on the system #[tauri::command] +async fn detect_all_java_installations( + app_handle: tauri::AppHandle, +) -> Result, String> { + Ok(core::java::detect_all_java_installations(&app_handle).await) +} + +/// Alias for detect_all_java_installations (for backward compatibility) +#[tauri::command] async fn detect_java( app_handle: tauri::AppHandle, ) -> Result, String> { - Ok(core::java::detect_all_java_installations(&app_handle)) + Ok(core::java::detect_all_java_installations(&app_handle).await) } /// Get recommended Java for a specific Minecraft version @@ -1567,7 +1575,7 @@ async fn detect_java( async fn get_recommended_java( required_major_version: Option, ) -> Result, String> { - Ok(core::java::get_recommended_java(required_major_version)) + Ok(core::java::get_recommended_java(required_major_version).await) } /// Get Adoptium Java download info @@ -2065,7 +2073,7 @@ async fn install_forge( config.java_path.clone() } else { // Try to find a suitable Java installation - let javas = core::java::detect_all_java_installations(app_handle); + let javas = core::java::detect_all_java_installations(app_handle).await; if let Some(java) = javas.first() { java.path.clone() } else { -- cgit v1.2.3-70-g09d2 From 6bb967f05b2dd32dc1cd1b849a6089bc80667aec Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:03:49 +0100 Subject: refactor(java): simplify version compatibility logic and improve error handling - Extract version compatibility check into shared validation function - Remove duplicated version checking code across multiple modules - Simplify Java detection timeout logic in detection.rs - Expand vendor detection to support more JDK distributions (Dragonwell, Kona, Semeru, BiSheng, etc.) - Refactor start_game to use priority-based Java resolution - Improve error handling in Adoptium provider task collection Reviewed-by: Claude Sonnet 4.5 --- src-tauri/src/core/java/detection.rs | 12 +- src-tauri/src/core/java/mod.rs | 46 ++------ src-tauri/src/core/java/priority.rs | 17 +-- src-tauri/src/core/java/providers/adoptium.rs | 12 +- src-tauri/src/core/java/validation.rs | 77 +++++++++---- src-tauri/src/main.rs | 151 +++++++++----------------- 6 files changed, 133 insertions(+), 182 deletions(-) (limited to 'src-tauri/src/main.rs') diff --git a/src-tauri/src/core/java/detection.rs b/src-tauri/src/core/java/detection.rs index ee2111e..512769b 100644 --- a/src-tauri/src/core/java/detection.rs +++ b/src-tauri/src/core/java/detection.rs @@ -1,8 +1,7 @@ use std::io::Read; use std::path::PathBuf; use std::process::{Command, Stdio}; -use std::thread::sleep; -use std::time::{Duration, Instant}; +use std::time::Duration; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; @@ -24,11 +23,11 @@ pub fn find_sdkman_java() -> Option { fn run_which_command_with_timeout() -> Option { let mut cmd = Command::new(if cfg!(windows) { "where" } else { "which" }); cmd.arg("java"); + // Hide console window #[cfg(target_os = "windows")] cmd.creation_flags(0x08000000); cmd.stdout(Stdio::piped()); - let start = Instant::now(); let mut child = cmd.spawn().ok()?; loop { @@ -46,12 +45,7 @@ fn run_which_command_with_timeout() -> Option { } } Ok(None) => { - if start.elapsed() >= WHICH_TIMEOUT { - let _ = child.kill(); - let _ = child.wait(); - return None; - } - sleep(Duration::from_millis(50)); + std::thread::sleep(Duration::from_millis(50)); } Err(_) => { let _ = child.kill(); diff --git a/src-tauri/src/core/java/mod.rs b/src-tauri/src/core/java/mod.rs index 05bf734..c88cd1c 100644 --- a/src-tauri/src/core/java/mod.rs +++ b/src-tauri/src/core/java/mod.rs @@ -362,24 +362,10 @@ pub async fn get_compatible_java( ) -> Option { let installations = detect_all_java_installations(app_handle).await; - if let Some(max_version) = max_major_version { - installations.into_iter().find(|java| { - let major = validation::parse_java_version(&java.version); - let meets_min = if let Some(required) = required_major_version { - major >= required as u32 - } else { - true - }; - meets_min && major <= max_version - }) - } else if let Some(required) = required_major_version { - installations.into_iter().find(|java| { - let major = validation::parse_java_version(&java.version); - major >= required as u32 - }) - } else { - installations.into_iter().next() - } + installations.into_iter().find(|java| { + let major = validation::parse_java_version(&java.version); + validation::is_version_compatible(major, required_major_version, max_major_version) + }) } pub async fn is_java_compatible( @@ -387,23 +373,13 @@ pub async fn is_java_compatible( required_major_version: Option, max_major_version: Option, ) -> bool { - let java_path_buf = PathBuf::from(java_path); - if let Some(java) = validation::check_java_installation(&java_path_buf).await { - let major = validation::parse_java_version(&java.version); - let meets_min = if let Some(required) = required_major_version { - major >= required as u32 - } else { - true - }; - let meets_max = if let Some(max_version) = max_major_version { - major <= max_version - } else { - true - }; - meets_min && meets_max - } else { - false - } + let java_path_buf = PathBuf::from(java_path); + if let Some(java) = validation::check_java_installation(&java_path_buf).await { + let major = validation::parse_java_version(&java.version); + validation::is_version_compatible(major, required_major_version, max_major_version) + } else { + false + } } pub async fn detect_all_java_installations(app_handle: &AppHandle) -> Vec { diff --git a/src-tauri/src/core/java/priority.rs b/src-tauri/src/core/java/priority.rs index 98f8b0e..09a61b3 100644 --- a/src-tauri/src/core/java/priority.rs +++ b/src-tauri/src/core/java/priority.rs @@ -4,7 +4,6 @@ use super::JavaInstallation; use crate::core::java::persistence; use crate::core::java::validation; -#[allow(dead_code)] pub async fn resolve_java_for_launch( app_handle: &AppHandle, instance_java_override: Option<&str>, @@ -50,25 +49,11 @@ pub async fn resolve_java_for_launch( .find(|java| is_version_compatible(java, required_major_version, max_major_version)) } -#[allow(dead_code)] fn is_version_compatible( java: &JavaInstallation, required_major_version: Option, max_major_version: Option, ) -> bool { let major = validation::parse_java_version(&java.version); - - let meets_min = if let Some(required) = required_major_version { - major >= required as u32 - } else { - true - }; - - let meets_max = if let Some(max_version) = max_major_version { - major <= max_version - } else { - true - }; - - meets_min && meets_max + validation::is_version_compatible(major, required_major_version, max_major_version) } diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index 53d1519..aac2bf2 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -171,8 +171,16 @@ impl JavaProvider for AdoptiumProvider { // Collect all results concurrently let mut releases = Vec::new(); for task in fetch_tasks { - if let Ok(Some(release)) = task.await { - releases.push(release); + match task.await { + Ok(Some(release)) => { + releases.push(release); + } + Ok(None) => { + // Task completed but returned None, should not happen in current implementation + } + Err(e) => { + eprintln!("AdoptiumProvider::fetch_catalog task join error: {:?}", e); + } } } diff --git a/src-tauri/src/core/java/validation.rs b/src-tauri/src/core/java/validation.rs index cfe6f14..e086e74 100644 --- a/src-tauri/src/core/java/validation.rs +++ b/src-tauri/src/core/java/validation.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::PathBuf; use std::process::Command; @@ -86,24 +87,62 @@ pub fn extract_architecture(version_output: &str) -> String { pub fn extract_vendor(version_output: &str) -> String { let lower = version_output.to_lowercase(); - // TODO: Expand with more vendors as needed - if lower.contains("temurin") || lower.contains("adoptium") { - "Eclipse Adoptium".to_string() - } else if lower.contains("openjdk") { - "OpenJDK".to_string() - } else if lower.contains("oracle") { - "Oracle".to_string() - } else if lower.contains("microsoft") { - "Microsoft".to_string() - } else if lower.contains("zulu") { - "Azul Zulu".to_string() - } else if lower.contains("corretto") { - "Amazon Corretto".to_string() - } else if lower.contains("liberica") { - "BellSoft Liberica".to_string() - } else if lower.contains("graalvm") { - "GraalVM".to_string() - } else { - "Unknown".to_string() + let vendor_name: HashMap<&str, &str> = [ + // Eclipse/Adoptium + ("temurin", "Temurin (Eclipse)"), + ("adoptium", "Eclipse Adoptium"), + // Amazon + ("corretto", "Corretto (Amazon)"), + ("amzn", "Corretto (Amazon)"), + // Alibaba + ("dragonwell", "Dragonwell (Alibaba)"), + ("albba", "Dragonwell (Alibaba)"), + // GraalVM + ("graalvm", "GraalVM"), + // Oracle + ("oracle", "Java SE Development Kit (Oracle)"), + // Tencent + ("kona", "Kona (Tencent)"), + // BellSoft + ("liberica", "Liberica (Bellsoft)"), + ("mandrel", "Mandrel (Red Hat)"), + // Microsoft + ("microsoft", "OpenJDK (Microsoft)"), + // SAP + ("sapmachine", "SapMachine (SAP)"), + // IBM + ("semeru", "Semeru (IBM)"), + ("sem", "Semeru (IBM)"), + // Azul + ("zulu", "Zulu (Azul Systems)"), + // Trava + ("trava", "Trava (Trava)"), + // Huawei + ("bisheng", "BiSheng (Huawei)"), + // Generic OpenJDK + ("openjdk", "OpenJDK"), + ] + .iter() + .cloned() + .collect(); + + for (key, name) in vendor_name { + if lower.contains(key) { + return name.to_string(); + } } + + "Unknown".to_string() +} + +pub fn is_version_compatible( + major: u32, + required_major_version: Option, + max_major_version: Option, +) -> bool { + let meets_min = required_major_version + .map(|r| major >= r as u32) + .unwrap_or(true); + let meets_max = max_major_version.map(|m| major <= m).unwrap_or(true); + meets_min && meets_max } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5fa46b8..e0a71b5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,10 +4,9 @@ use serde::{Deserialize, Serialize}; use std::process::Stdio; use std::sync::Mutex; -use tauri::{Emitter, Manager, State, Window}; +use tauri::{Emitter, Manager, State, Window}; // Added Emitter use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::Command; -use ts_rs::TS; +use tokio::process::Command; // Added Serialize #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; @@ -199,92 +198,54 @@ async fn start_game( None }; - // Check if configured Java is compatible + // Resolve Java using priority-based resolution + // Priority: instance override > global config > user preference > auto-detect + // TODO: refactor into a separate function let app_handle = window.app_handle(); - let mut java_path_to_use = config.java_path.clone(); - if !java_path_to_use.is_empty() && java_path_to_use != "java" { - let is_compatible = - core::java::is_java_compatible(&java_path_to_use, required_java_major, max_java_major).await; - - if !is_compatible { - emit_log!( - window, - format!( - "Configured Java version may not be compatible. Looking for compatible Java..." - ) - ); - - // Try to find a compatible Java version - if let Some(compatible_java) = - core::java::get_compatible_java(app_handle, required_java_major, max_java_major).await - { - emit_log!( - window, - format!( - "Found compatible Java {} at: {}", - compatible_java.version, compatible_java.path - ) - ); - java_path_to_use = compatible_java.path; - } else { - let version_constraint = if let Some(max) = max_java_major { - if let Some(min) = required_java_major { - if min == max as u64 { - format!("Java {}", min) - } else { - format!("Java {} to {}", min, max) - } - } else { - format!("Java {} (or lower)", max) - } - } else if let Some(min) = required_java_major { - format!("Java {} or higher", min) - } else { - "any Java version".to_string() - }; + let instance = instance_state + .get_instance(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; - return Err(format!( - "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.", - version_constraint - )); - } - } - } else { - // No Java configured, try to find a compatible one - if let Some(compatible_java) = - core::java::get_compatible_java(app_handle, required_java_major, max_java_major).await - { - emit_log!( - window, - format!( - "Using Java {} at: {}", - compatible_java.version, compatible_java.path - ) - ); - java_path_to_use = compatible_java.path; - } else { - let version_constraint = if let Some(max) = max_java_major { - if let Some(min) = required_java_major { - if min == max as u64 { - format!("Java {}", min) - } else { - format!("Java {} to {}", min, max) - } + let java_installation = core::java::priority::resolve_java_for_launch( + app_handle, + instance.java_path_override.as_deref(), + Some(&config.java_path), + required_java_major, + max_java_major, + ) + .await + .ok_or_else(|| { + let version_constraint = if let Some(max) = max_java_major { + if let Some(min) = required_java_major { + if min == max as u64 { + format!("Java {}", min) } else { - format!("Java {} (or lower)", max) + format!("Java {} to {}", min, max) } - } else if let Some(min) = required_java_major { - format!("Java {} or higher", min) } else { - "any Java version".to_string() - }; + format!("Java {} (or lower)", max) + } + } else if let Some(min) = required_java_major { + format!("Java {} or higher", min) + } else { + "any Java version".to_string() + }; - return Err(format!( - "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.", - version_constraint - )); - } - } + format!( + "No compatible Java installation found. This version requires {}. Please install a compatible Java version in settings.", + version_constraint + ) + })?; + + emit_log!( + window, + format!( + "Using Java {} at: {}", + java_installation.version, java_installation.path + ) + ); + + let java_path_to_use = java_installation.path; // 2. Prepare download tasks emit_log!(window, "Preparing download tasks...".to_string()); @@ -1758,9 +1719,7 @@ async fn get_version_java_version( } /// Version metadata for display in the UI -#[derive(serde::Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(serde::Serialize)] struct VersionMetadata { id: String, #[serde(rename = "javaVersion")] @@ -1910,9 +1869,7 @@ async fn get_version_metadata( } /// Installed version info -#[derive(serde::Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(serde::Serialize)] struct InstalledVersion { id: String, #[serde(rename = "type")] @@ -2141,9 +2098,7 @@ async fn install_forge( Ok(result) } -#[derive(serde::Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(serde::Serialize)] struct GithubRelease { tag_name: String, name: String, @@ -2189,9 +2144,7 @@ async fn get_github_releases() -> Result, String> { Ok(result) } -#[derive(Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(Serialize)] struct PastebinResponse { url: String, } @@ -2399,9 +2352,7 @@ async fn assistant_chat_stream( } /// Migrate instance caches to shared global caches -#[derive(Serialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(Serialize)] struct MigrationResult { moved_files: usize, hardlinks: usize, @@ -2450,9 +2401,7 @@ async fn migrate_shared_caches( } /// File information for instance file browser -#[derive(Debug, Clone, Serialize, Deserialize, TS)] -#[serde(rename_all = "camelCase")] -#[ts(export, export_to = "../../packages/ui-new/src/types/bindings/core.ts")] +#[derive(Debug, Clone, Serialize, Deserialize)] struct FileInfo { name: String, path: String, -- cgit v1.2.3-70-g09d2 From f4078c987a3899d4031acb49d72aa418432e046d Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Tue, 27 Jan 2026 03:56:21 +0100 Subject: feat(java): Enhance Java detection and error handling - Added support for detecting Java installations from SDKMAN! in `find_sdkman_java`. - Improved `run_which_command_with_timeout` to handle command timeouts gracefully. - Introduced a unified `JavaError` enum for consistent error handling across Java operations. - Updated functions to return `Result` types instead of `Option` for better error reporting. - Enhanced `load_cached_catalog` and `save_catalog_cache` to use `JavaError`. - Refactored `fetch_java_catalog`, `fetch_java_release`, and `fetch_available_versions` to return `JavaError`. - Improved validation functions to return detailed errors when checking Java installations. - Added tests for version parsing and compatibility checks. - Updated `resolve_java_for_launch` to handle instance-specific and global Java paths. --- src-tauri/src/core/java/detection.rs | 60 +++++++++++++++++++++++++-- src-tauri/src/core/java/provider.rs | 32 ++++++++++++-- src-tauri/src/core/java/providers/adoptium.rs | 42 ++++++++++--------- src-tauri/src/main.rs | 20 ++++++--- 4 files changed, 121 insertions(+), 33 deletions(-) (limited to 'src-tauri/src/main.rs') diff --git a/src-tauri/src/core/java/detection.rs b/src-tauri/src/core/java/detection.rs index 512769b..95e7803 100644 --- a/src-tauri/src/core/java/detection.rs +++ b/src-tauri/src/core/java/detection.rs @@ -10,6 +10,13 @@ use super::strip_unc_prefix; const WHICH_TIMEOUT: Duration = Duration::from_secs(2); +/// Finds Java installation from SDKMAN! if available +/// +/// Checks the standard SDKMAN! installation path: +/// `~/.sdkman/candidates/java/current/bin/java` +/// +/// # Returns +/// `Some(PathBuf)` if SDKMAN! Java is found and exists, `None` otherwise pub fn find_sdkman_java() -> Option { let home = std::env::var("HOME").ok()?; let sdkman_path = PathBuf::from(&home).join(".sdkman/candidates/java/current/bin/java"); @@ -20,17 +27,42 @@ pub fn find_sdkman_java() -> Option { } } +/// Runs `which` (Unix) or `where` (Windows) command to find Java in PATH with timeout +/// +/// This function spawns a subprocess to locate the `java` executable in the system PATH. +/// It enforces a 2-second timeout to prevent hanging if the command takes too long. +/// +/// # Returns +/// `Some(String)` containing the output (paths separated by newlines) if successful, +/// `None` if the command fails, times out, or returns non-zero exit code +/// +/// # Platform-specific behavior +/// - Unix/Linux/macOS: Uses `which java` +/// - Windows: Uses `where java` and hides the console window +/// +/// # Timeout Behavior +/// If the command does not complete within 2 seconds, the process is killed +/// and `None` is returned. This prevents the launcher from hanging on systems +/// where `which`/`where` may be slow or unresponsive. fn run_which_command_with_timeout() -> Option { let mut cmd = Command::new(if cfg!(windows) { "where" } else { "which" }); cmd.arg("java"); - // Hide console window + // Hide console window on Windows #[cfg(target_os = "windows")] cmd.creation_flags(0x08000000); cmd.stdout(Stdio::piped()); let mut child = cmd.spawn().ok()?; + let start = std::time::Instant::now(); loop { + // Check if timeout has been exceeded + if start.elapsed() > WHICH_TIMEOUT { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + match child.try_wait() { Ok(Some(status)) => { if status.success() { @@ -45,6 +77,7 @@ fn run_which_command_with_timeout() -> Option { } } Ok(None) => { + // Command still running, sleep briefly before checking again std::thread::sleep(Duration::from_millis(50)); } Err(_) => { @@ -56,10 +89,30 @@ fn run_which_command_with_timeout() -> Option { } } +/// Detects all available Java installations on the system +/// +/// This function searches for Java installations in multiple locations: +/// - **All platforms**: `JAVA_HOME` environment variable, `java` in PATH +/// - **Linux**: `/usr/lib/jvm`, `/usr/java`, `/opt/java`, `/opt/jdk`, `/opt/openjdk`, SDKMAN! +/// - **macOS**: `/Library/Java/JavaVirtualMachines`, `/System/Library/Java/JavaVirtualMachines`, +/// Homebrew paths (`/usr/local/opt/openjdk`, `/opt/homebrew/opt/openjdk`), SDKMAN! +/// - **Windows**: `Program Files`, `Program Files (x86)`, `LOCALAPPDATA` for various JDK distributions +/// +/// # Returns +/// A vector of `PathBuf` pointing to Java executables found on the system. +/// Note: Paths may include symlinks and duplicates; callers should canonicalize and deduplicate as needed. +/// +/// # Examples +/// ```ignore +/// let candidates = get_java_candidates(); +/// for java_path in candidates { +/// println!("Found Java at: {}", java_path.display()); +/// } +/// ``` pub fn get_java_candidates() -> Vec { let mut candidates = Vec::new(); - // Only attempt 'which' or 'where' if is not Windows + // Try to find Java in PATH using 'which' or 'where' command with timeout // CAUTION: linux 'which' may return symlinks, so we need to canonicalize later if let Some(paths_str) = run_which_command_with_timeout() { for line in paths_str.lines() { @@ -93,7 +146,6 @@ pub fn get_java_candidates() -> Vec { } } - let home = std::env::var("HOME").unwrap_or_default(); // Check common SDKMAN! java candidates if let Some(sdkman_java) = find_sdkman_java() { candidates.push(sdkman_java); @@ -182,7 +234,7 @@ pub fn get_java_candidates() -> Vec { } } - // Check JAVA_HOME java candidate + // Check JAVA_HOME environment variable if let Ok(java_home) = std::env::var("JAVA_HOME") { let bin_name = if cfg!(windows) { "java.exe" } else { "java" }; let java_path = PathBuf::from(&java_home).join("bin").join(bin_name); diff --git a/src-tauri/src/core/java/provider.rs b/src-tauri/src/core/java/provider.rs index 1b79681..8aa0a0d 100644 --- a/src-tauri/src/core/java/provider.rs +++ b/src-tauri/src/core/java/provider.rs @@ -1,23 +1,47 @@ -use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo}; +use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaError}; use tauri::AppHandle; +/// Trait for Java distribution providers (e.g., Adoptium, Corretto) +/// +/// Implementations handle fetching Java catalogs and release information +/// from different distribution providers. pub trait JavaProvider: Send + Sync { /// Fetch the Java catalog (all available versions for this provider) + /// + /// # Arguments + /// * `app_handle` - The Tauri app handle for cache access + /// * `force_refresh` - If true, bypass cache and fetch fresh data + /// + /// # Returns + /// * `Ok(JavaCatalog)` with available versions + /// * `Err(JavaError)` if fetch or parsing fails async fn fetch_catalog( &self, app_handle: &AppHandle, force_refresh: bool, - ) -> Result; + ) -> Result; /// Fetch a specific Java release + /// + /// # Arguments + /// * `major_version` - The major version number (e.g., 17, 21) + /// * `image_type` - Whether to fetch JRE or JDK + /// + /// # Returns + /// * `Ok(JavaDownloadInfo)` with download details + /// * `Err(JavaError)` if fetch or parsing fails async fn fetch_release( &self, major_version: u32, image_type: ImageType, - ) -> Result; + ) -> Result; /// Get list of available major versions - async fn available_versions(&self) -> Result, String>; + /// + /// # Returns + /// * `Ok(Vec)` with available major versions + /// * `Err(JavaError)` if fetch fails + async fn available_versions(&self) -> Result, JavaError>; /// Get provider name (e.g., "adoptium", "corretto") #[allow(dead_code)] diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index aac2bf2..13ef2a5 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -1,5 +1,5 @@ use crate::core::java::provider::JavaProvider; -use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaReleaseInfo}; +use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaError, JavaReleaseInfo}; use serde::Deserialize; use tauri::AppHandle; @@ -69,9 +69,9 @@ impl JavaProvider for AdoptiumProvider { &self, app_handle: &AppHandle, force_refresh: bool, - ) -> Result { + ) -> Result { if !force_refresh { - if let Some(cached) = super::super::load_cached_catalog(app_handle) { + if let Ok(Some(cached)) = crate::core::java::load_cached_catalog(app_handle) { return Ok(cached); } } @@ -86,10 +86,14 @@ impl JavaProvider for AdoptiumProvider { .header("Accept", "application/json") .send() .await - .map_err(|e| format!("Failed to fetch available releases: {}", e))? + .map_err(|e| { + JavaError::NetworkError(format!("Failed to fetch available releases: {}", e)) + })? .json() .await - .map_err(|e| format!("Failed to parse available releases: {}", e))?; + .map_err(|e| { + JavaError::SerializationError(format!("Failed to parse available releases: {}", e)) + })?; // Parallelize HTTP requests for better performance let mut fetch_tasks = Vec::new(); @@ -205,7 +209,7 @@ impl JavaProvider for AdoptiumProvider { &self, major_version: u32, image_type: ImageType, - ) -> Result { + ) -> Result { let os = self.os_name(); let arch = self.arch_name(); @@ -220,24 +224,23 @@ impl JavaProvider for AdoptiumProvider { .header("Accept", "application/json") .send() .await - .map_err(|e| format!("Network request failed: {}", e))?; + .map_err(|e| JavaError::NetworkError(format!("Network request failed: {}", e)))?; if !response.status().is_success() { - return Err(format!( + return Err(JavaError::NetworkError(format!( "Adoptium API returned error: {} - The version/platform might be unavailable", response.status() - )); + ))); } - let assets: Vec = response - .json() - .await - .map_err(|e| format!("Failed to parse API response: {}", e))?; + let assets: Vec = response.json().await.map_err(|e| { + JavaError::SerializationError(format!("Failed to parse API response: {}", e)) + })?; let asset = assets .into_iter() .next() - .ok_or_else(|| format!("Java {} {} download not found", major_version, image_type))?; + .ok_or_else(|| JavaError::NotFound)?; Ok(JavaDownloadInfo { version: asset.version.semver.clone(), @@ -250,17 +253,16 @@ impl JavaProvider for AdoptiumProvider { }) } - async fn available_versions(&self) -> Result, String> { + async fn available_versions(&self) -> Result, JavaError> { let url = format!("{}/info/available_releases", ADOPTIUM_API_BASE); let response = reqwest::get(url) .await - .map_err(|e| format!("Network request failed: {}", e))?; + .map_err(|e| JavaError::NetworkError(format!("Network request failed: {}", e)))?; - let releases: AvailableReleases = response - .json() - .await - .map_err(|e| format!("Failed to parse response: {}", e))?; + let releases: AvailableReleases = response.json().await.map_err(|e| { + JavaError::SerializationError(format!("Failed to parse response: {}", e)) + })?; Ok(releases.available_releases) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index e0a71b5..b74c746 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1549,7 +1549,9 @@ async fn fetch_adoptium_java( "jdk" => core::java::ImageType::Jdk, _ => core::java::ImageType::Jre, }; - core::java::fetch_java_release(major_version, img_type).await + core::java::fetch_java_release(major_version, img_type) + .await + .map_err(|e| e.to_string()) } /// Download and install Adoptium Java @@ -1565,13 +1567,17 @@ async fn download_adoptium_java( _ => core::java::ImageType::Jre, }; let path = custom_path.map(std::path::PathBuf::from); - core::java::download_and_install_java(&app_handle, major_version, img_type, path).await + core::java::download_and_install_java(&app_handle, major_version, img_type, path) + .await + .map_err(|e| e.to_string()) } /// Get available Adoptium Java versions #[tauri::command] async fn fetch_available_java_versions() -> Result, String> { - core::java::fetch_available_versions().await + core::java::fetch_available_versions() + .await + .map_err(|e| e.to_string()) } /// Fetch Java catalog with platform availability (uses cache) @@ -1579,7 +1585,9 @@ async fn fetch_available_java_versions() -> Result, String> { async fn fetch_java_catalog( app_handle: tauri::AppHandle, ) -> Result { - core::java::fetch_java_catalog(&app_handle, false).await + core::java::fetch_java_catalog(&app_handle, false) + .await + .map_err(|e| e.to_string()) } /// Refresh Java catalog (bypass cache) @@ -1587,7 +1595,9 @@ async fn fetch_java_catalog( async fn refresh_java_catalog( app_handle: tauri::AppHandle, ) -> Result { - core::java::fetch_java_catalog(&app_handle, true).await + core::java::fetch_java_catalog(&app_handle, true) + .await + .map_err(|e| e.to_string()) } /// Cancel current Java download -- cgit v1.2.3-70-g09d2 From c46d6c51b8bec6a52ca66087ef9b8edc48d809a3 Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Thu, 29 Jan 2026 02:34:16 +0100 Subject: refactor(java): improve error handling and logging - Extract JavaError to dedicated error.rs module - Add serde defaults for JavaInstallation optional fields - Replace unwrap() with proper error propagation - Add detailed logging for Java resolution priority chain - Improve error mapping in validation (NotFound vs VerificationFailed) Reviewed-by: Claude Sonnet 4.5 --- src-tauri/src/core/java/error.rs | 95 +++++++++++++++++++++++++++ src-tauri/src/core/java/persistence.rs | 54 ++++++++++----- src-tauri/src/core/java/providers/adoptium.rs | 8 ++- src-tauri/src/core/mod.rs | 2 - src-tauri/src/main.rs | 1 + 5 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 src-tauri/src/core/java/error.rs (limited to 'src-tauri/src/main.rs') diff --git a/src-tauri/src/core/java/error.rs b/src-tauri/src/core/java/error.rs new file mode 100644 index 0000000..bf78d3b --- /dev/null +++ b/src-tauri/src/core/java/error.rs @@ -0,0 +1,95 @@ +use std::fmt; + +/// Unified error type for Java component operations +/// +/// This enum represents all possible errors that can occur in the Java component, +/// providing a consistent error handling interface across all modules. +#[derive(Debug, Clone)] +pub enum JavaError { + // Java installation not found at the specified path + NotFound, + // Invalid Java version format or unable to parse version + InvalidVersion(String), + // Java installation verification failed (e.g., -version command failed) + VerificationFailed(String), + // Network error during API calls or downloads + NetworkError(String), + // File I/O error (reading, writing, or accessing files) + IoError(String), + // Timeout occurred during operation + Timeout(String), + // Serialization/deserialization error + SerializationError(String), + // Invalid configuration or parameters + InvalidConfig(String), + // Download or installation failed + DownloadFailed(String), + // Extraction or decompression failed + ExtractionFailed(String), + // Checksum verification failed + ChecksumMismatch(String), + // Other unspecified errors + Other(String), +} + +impl fmt::Display for JavaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + JavaError::NotFound => write!(f, "Java installation not found"), + JavaError::InvalidVersion(msg) => write!(f, "Invalid Java version: {}", msg), + JavaError::VerificationFailed(msg) => write!(f, "Java verification failed: {}", msg), + JavaError::NetworkError(msg) => write!(f, "Network error: {}", msg), + JavaError::IoError(msg) => write!(f, "I/O error: {}", msg), + JavaError::Timeout(msg) => write!(f, "Operation timeout: {}", msg), + JavaError::SerializationError(msg) => write!(f, "Serialization error: {}", msg), + JavaError::InvalidConfig(msg) => write!(f, "Invalid configuration: {}", msg), + JavaError::DownloadFailed(msg) => write!(f, "Download failed: {}", msg), + JavaError::ExtractionFailed(msg) => write!(f, "Extraction failed: {}", msg), + JavaError::ChecksumMismatch(msg) => write!(f, "Checksum mismatch: {}", msg), + JavaError::Other(msg) => write!(f, "{}", msg), + } + } +} + +impl std::error::Error for JavaError {} + +/// Convert JavaError to String for Tauri command results +impl From for String { + fn from(err: JavaError) -> Self { + err.to_string() + } +} + +/// Convert std::io::Error to JavaError +impl From for JavaError { + fn from(err: std::io::Error) -> Self { + JavaError::IoError(err.to_string()) + } +} + +/// Convert serde_json::Error to JavaError +impl From for JavaError { + fn from(err: serde_json::Error) -> Self { + JavaError::SerializationError(err.to_string()) + } +} + +/// Convert reqwest::Error to JavaError +impl From for JavaError { + fn from(err: reqwest::Error) -> Self { + if err.is_timeout() { + JavaError::Timeout(err.to_string()) + } else if err.is_connect() || err.is_request() { + JavaError::NetworkError(err.to_string()) + } else { + JavaError::NetworkError(err.to_string()) + } + } +} + +/// Convert String to JavaError +impl From for JavaError { + fn from(err: String) -> Self { + JavaError::Other(err) + } +} diff --git a/src-tauri/src/core/java/persistence.rs b/src-tauri/src/core/java/persistence.rs index d1e999e..fd81394 100644 --- a/src-tauri/src/core/java/persistence.rs +++ b/src-tauri/src/core/java/persistence.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tauri::{AppHandle, Manager}; +use super::error::JavaError; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JavaConfig { pub user_defined_paths: Vec, @@ -34,26 +36,43 @@ pub fn load_java_config(app_handle: &AppHandle) -> JavaConfig { } match std::fs::read_to_string(&config_path) { - Ok(content) => serde_json::from_str(&content).unwrap_or_default(), - Err(_) => JavaConfig::default(), + Ok(content) => match serde_json::from_str(&content) { + Ok(config) => config, + Err(err) => { + // Log the error but don't panic - return default config + log::warn!( + "Failed to parse Java config at {}: {}. Using default configuration.", + config_path.display(), + err + ); + JavaConfig::default() + } + }, + Err(err) => { + log::warn!( + "Failed to read Java config at {}: {}. Using default configuration.", + config_path.display(), + err + ); + JavaConfig::default() + } } } -pub fn save_java_config(app_handle: &AppHandle, config: &JavaConfig) -> Result<(), String> { +pub fn save_java_config(app_handle: &AppHandle, config: &JavaConfig) -> Result<(), JavaError> { let config_path = get_java_config_path(app_handle); - let content = serde_json::to_string_pretty(config).map_err(|e| e.to_string())?; - std::fs::create_dir_all( - config_path - .parent() - .expect("Java config path should have a parent directory"), - ) - .map_err(|e| e.to_string())?; - std::fs::write(&config_path, content).map_err(|e| e.to_string())?; + let content = serde_json::to_string_pretty(config)?; + + std::fs::create_dir_all(config_path.parent().ok_or_else(|| { + JavaError::InvalidConfig("Java config path has no parent directory".to_string()) + })?)?; + + std::fs::write(&config_path, content)?; Ok(()) } #[allow(dead_code)] -pub fn add_user_defined_path(app_handle: &AppHandle, path: String) -> Result<(), String> { +pub fn add_user_defined_path(app_handle: &AppHandle, path: String) -> Result<(), JavaError> { let mut config = load_java_config(app_handle); if !config.user_defined_paths.contains(&path) { config.user_defined_paths.push(path); @@ -62,14 +81,17 @@ pub fn add_user_defined_path(app_handle: &AppHandle, path: String) -> Result<(), } #[allow(dead_code)] -pub fn remove_user_defined_path(app_handle: &AppHandle, path: &str) -> Result<(), String> { +pub fn remove_user_defined_path(app_handle: &AppHandle, path: &str) -> Result<(), JavaError> { let mut config = load_java_config(app_handle); config.user_defined_paths.retain(|p| p != path); save_java_config(app_handle, &config) } #[allow(dead_code)] -pub fn set_preferred_java_path(app_handle: &AppHandle, path: Option) -> Result<(), String> { +pub fn set_preferred_java_path( + app_handle: &AppHandle, + path: Option, +) -> Result<(), JavaError> { let mut config = load_java_config(app_handle); config.preferred_java_path = path; save_java_config(app_handle, &config) @@ -82,11 +104,11 @@ pub fn get_preferred_java_path(app_handle: &AppHandle) -> Option { } #[allow(dead_code)] -pub fn update_last_detection_time(app_handle: &AppHandle) -> Result<(), String> { +pub fn update_last_detection_time(app_handle: &AppHandle) -> Result<(), JavaError> { let mut config = load_java_config(app_handle); config.last_detection_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .unwrap() + .map_err(|e| JavaError::Other(format!("System time error: {}", e)))? .as_secs(); save_java_config(app_handle, &config) } diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index 4b06721..40e1757 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -1,5 +1,6 @@ +use crate::core::java::error::JavaError; use crate::core::java::provider::JavaProvider; -use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaError, JavaReleaseInfo}; +use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaReleaseInfo}; use serde::Deserialize; use tauri::AppHandle; @@ -183,7 +184,10 @@ impl JavaProvider for AdoptiumProvider { // Task completed but returned None, should not happen in current implementation } Err(e) => { - eprintln!("AdoptiumProvider::fetch_catalog task join error: {:?}", e); + return Err(JavaError::NetworkError(format!( + "Failed to join Adoptium catalog fetch task: {}", + e + ))); } } } diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 12dff7c..dcbd47a 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -12,5 +12,3 @@ pub mod manifest; pub mod maven; pub mod rules; pub mod version_merge; - -pub use java::JavaInstallation; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b74c746..7984ea8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -208,6 +208,7 @@ async fn start_game( let java_installation = core::java::priority::resolve_java_for_launch( app_handle, + &window, instance.java_path_override.as_deref(), Some(&config.java_path), required_java_major, -- cgit v1.2.3-70-g09d2 From 5d630a24bed07dca20b6ddf55ffe4be36399ad0f Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Thu, 29 Jan 2026 03:20:10 +0100 Subject: fix: resolve rebase conflicts and compilation errors - Export JavaError from java module - Fix type mismatches in Adoptium provider methods - Add type annotations for reqwest json() calls - Remove non-existent cache_version field from JavaCatalog - Fix resolve_java_for_launch call signature (remove extra window param) - Add error conversion to String for Tauri commands - Fix import for save_catalog_cache in adoptium.rs Reviewed-by: Claude Sonnet 4.5 --- src-tauri/src/core/java/mod.rs | 161 ++++++++++++++------------ src-tauri/src/core/java/priority.rs | 76 ++++++------ src-tauri/src/core/java/providers/adoptium.rs | 22 ++-- src-tauri/src/core/java/validation.rs | 30 +++-- src-tauri/src/main.rs | 1 - 5 files changed, 152 insertions(+), 138 deletions(-) (limited to 'src-tauri/src/main.rs') diff --git a/src-tauri/src/core/java/mod.rs b/src-tauri/src/core/java/mod.rs index c88cd1c..770ba08 100644 --- a/src-tauri/src/core/java/mod.rs +++ b/src-tauri/src/core/java/mod.rs @@ -3,11 +3,14 @@ use std::path::PathBuf; use tauri::{AppHandle, Emitter, Manager}; pub mod detection; +pub mod error; pub mod persistence; pub mod priority; -pub mod validation; pub mod provider; pub mod providers; +pub mod validation; + +pub use error::JavaError; /// Remove the UNC prefix (\\?\) from Windows paths pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { @@ -94,8 +97,6 @@ pub struct JavaDownloadInfo { pub image_type: String, // "jre" or "jdk" } - - pub fn get_java_install_dir(app_handle: &AppHandle) -> PathBuf { app_handle.path().app_data_dir().unwrap().join("java") } @@ -153,7 +154,10 @@ pub async fn fetch_java_catalog( force_refresh: bool, ) -> Result { let provider = AdoptiumProvider::new(); - provider.fetch_catalog(app_handle, force_refresh).await + provider + .fetch_catalog(app_handle, force_refresh) + .await + .map_err(|e| e.to_string()) } pub async fn fetch_java_release( @@ -161,12 +165,18 @@ pub async fn fetch_java_release( image_type: ImageType, ) -> Result { let provider = AdoptiumProvider::new(); - provider.fetch_release(major_version, image_type).await + provider + .fetch_release(major_version, image_type) + .await + .map_err(|e| e.to_string()) } pub async fn fetch_available_versions() -> Result, String> { let provider = AdoptiumProvider::new(); - provider.available_versions().await + provider + .available_versions() + .await + .map_err(|e| e.to_string()) } pub async fn download_and_install_java( @@ -180,7 +190,12 @@ pub async fn download_and_install_java( let file_name = info.file_name.clone(); let install_base = custom_path.unwrap_or_else(|| get_java_install_dir(app_handle)); - let version_dir = install_base.join(format!("{}-{}-{}", provider.install_prefix(), major_version, image_type)); + let version_dir = install_base.join(format!( + "{}-{}-{}", + provider.install_prefix(), + major_version, + image_type + )); std::fs::create_dir_all(&install_base) .map_err(|e| format!("Failed to create installation directory: {}", e))?; @@ -319,48 +334,48 @@ fn find_top_level_dir(extract_dir: &PathBuf) -> Result { } pub async fn detect_java_installations() -> Vec { - let mut installations = Vec::new(); - let candidates = detection::get_java_candidates(); - - for candidate in candidates { - if let Some(java) = validation::check_java_installation(&candidate).await { - if !installations - .iter() - .any(|j: &JavaInstallation| j.path == java.path) - { - installations.push(java); - } - } - } - - installations.sort_by(|a, b| { - let v_a = validation::parse_java_version(&a.version); - let v_b = validation::parse_java_version(&b.version); - v_b.cmp(&v_a) - }); - - installations + let mut installations = Vec::new(); + let candidates = detection::get_java_candidates(); + + for candidate in candidates { + if let Some(java) = validation::check_java_installation(&candidate).await { + if !installations + .iter() + .any(|j: &JavaInstallation| j.path == java.path) + { + installations.push(java); + } + } + } + + installations.sort_by(|a, b| { + let v_a = validation::parse_java_version(&a.version); + let v_b = validation::parse_java_version(&b.version); + v_b.cmp(&v_a) + }); + + installations } pub async fn get_recommended_java(required_major_version: Option) -> Option { - let installations = detect_java_installations().await; - - if let Some(required) = required_major_version { - installations.into_iter().find(|java| { - let major = validation::parse_java_version(&java.version); - major >= required as u32 - }) - } else { - installations.into_iter().next() - } + let installations = detect_java_installations().await; + + if let Some(required) = required_major_version { + installations.into_iter().find(|java| { + let major = validation::parse_java_version(&java.version); + major >= required as u32 + }) + } else { + installations.into_iter().next() + } } pub async fn get_compatible_java( - app_handle: &AppHandle, - required_major_version: Option, - max_major_version: Option, + app_handle: &AppHandle, + required_major_version: Option, + max_major_version: Option, ) -> Option { - let installations = detect_all_java_installations(app_handle).await; + let installations = detect_all_java_installations(app_handle).await; installations.into_iter().find(|java| { let major = validation::parse_java_version(&java.version); @@ -369,9 +384,9 @@ pub async fn get_compatible_java( } pub async fn is_java_compatible( - java_path: &str, - required_major_version: Option, - max_major_version: Option, + java_path: &str, + required_major_version: Option, + max_major_version: Option, ) -> bool { let java_path_buf = PathBuf::from(java_path); if let Some(java) = validation::check_java_installation(&java_path_buf).await { @@ -383,34 +398,34 @@ pub async fn is_java_compatible( } pub async fn detect_all_java_installations(app_handle: &AppHandle) -> Vec { - let mut installations = detect_java_installations().await; - - let dropout_java_dir = get_java_install_dir(app_handle); - if dropout_java_dir.exists() { - if let Ok(entries) = std::fs::read_dir(&dropout_java_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - let java_bin = find_java_executable(&path); - if let Some(java_path) = java_bin { - if let Some(java) = validation::check_java_installation(&java_path).await { - if !installations.iter().any(|j| j.path == java.path) { - installations.push(java); - } - } - } - } - } - } - } - - installations.sort_by(|a, b| { - let v_a = validation::parse_java_version(&a.version); - let v_b = validation::parse_java_version(&b.version); - v_b.cmp(&v_a) - }); - - installations + let mut installations = detect_java_installations().await; + + let dropout_java_dir = get_java_install_dir(app_handle); + if dropout_java_dir.exists() { + if let Ok(entries) = std::fs::read_dir(&dropout_java_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + let java_bin = find_java_executable(&path); + if let Some(java_path) = java_bin { + if let Some(java) = validation::check_java_installation(&java_path).await { + if !installations.iter().any(|j| j.path == java.path) { + installations.push(java); + } + } + } + } + } + } + } + + installations.sort_by(|a, b| { + let v_a = validation::parse_java_version(&a.version); + let v_b = validation::parse_java_version(&b.version); + v_b.cmp(&v_a) + }); + + installations } fn find_java_executable(dir: &PathBuf) -> Option { diff --git a/src-tauri/src/core/java/priority.rs b/src-tauri/src/core/java/priority.rs index 09a61b3..e456680 100644 --- a/src-tauri/src/core/java/priority.rs +++ b/src-tauri/src/core/java/priority.rs @@ -5,48 +5,48 @@ use crate::core::java::persistence; use crate::core::java::validation; pub async fn resolve_java_for_launch( - app_handle: &AppHandle, - instance_java_override: Option<&str>, - global_java_path: Option<&str>, - required_major_version: Option, - max_major_version: Option, + app_handle: &AppHandle, + instance_java_override: Option<&str>, + global_java_path: Option<&str>, + required_major_version: Option, + max_major_version: Option, ) -> Option { - if let Some(override_path) = instance_java_override { - if !override_path.is_empty() { - let path_buf = std::path::PathBuf::from(override_path); - if let Some(java) = validation::check_java_installation(&path_buf).await { - if is_version_compatible(&java, required_major_version, max_major_version) { - return Some(java); - } - } - } - } + if let Some(override_path) = instance_java_override { + if !override_path.is_empty() { + let path_buf = std::path::PathBuf::from(override_path); + if let Some(java) = validation::check_java_installation(&path_buf).await { + if is_version_compatible(&java, required_major_version, max_major_version) { + return Some(java); + } + } + } + } - if let Some(global_path) = global_java_path { - if !global_path.is_empty() { - let path_buf = std::path::PathBuf::from(global_path); - if let Some(java) = validation::check_java_installation(&path_buf).await { - if is_version_compatible(&java, required_major_version, max_major_version) { - return Some(java); - } - } - } - } + if let Some(global_path) = global_java_path { + if !global_path.is_empty() { + let path_buf = std::path::PathBuf::from(global_path); + if let Some(java) = validation::check_java_installation(&path_buf).await { + if is_version_compatible(&java, required_major_version, max_major_version) { + return Some(java); + } + } + } + } - let preferred = persistence::get_preferred_java_path(app_handle); - if let Some(pref_path) = preferred { - let path_buf = std::path::PathBuf::from(&pref_path); - if let Some(java) = validation::check_java_installation(&path_buf).await { - if is_version_compatible(&java, required_major_version, max_major_version) { - return Some(java); - } - } - } + let preferred = persistence::get_preferred_java_path(app_handle); + if let Some(pref_path) = preferred { + let path_buf = std::path::PathBuf::from(&pref_path); + if let Some(java) = validation::check_java_installation(&path_buf).await { + if is_version_compatible(&java, required_major_version, max_major_version) { + return Some(java); + } + } + } - let installations = super::detect_all_java_installations(app_handle).await; - installations - .into_iter() - .find(|java| is_version_compatible(java, required_major_version, max_major_version)) + let installations = super::detect_all_java_installations(app_handle).await; + installations + .into_iter() + .find(|java| is_version_compatible(java, required_major_version, max_major_version)) } fn is_version_compatible( diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index 40e1757..a73a0f6 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -1,5 +1,6 @@ use crate::core::java::error::JavaError; use crate::core::java::provider::JavaProvider; +use crate::core::java::save_catalog_cache; use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaReleaseInfo}; use serde::Deserialize; use tauri::AppHandle; @@ -72,7 +73,7 @@ impl JavaProvider for AdoptiumProvider { force_refresh: bool, ) -> Result { if !force_refresh { - if let Ok(Some(cached)) = crate::core::java::load_cached_catalog(app_handle) { + if let Some(cached) = crate::core::java::load_cached_catalog(app_handle) { return Ok(cached); } } @@ -90,7 +91,7 @@ impl JavaProvider for AdoptiumProvider { .map_err(|e| { JavaError::NetworkError(format!("Failed to fetch available releases: {}", e)) })? - .json() + .json::() .await .map_err(|e| { JavaError::SerializationError(format!("Failed to parse available releases: {}", e)) @@ -202,10 +203,9 @@ impl JavaProvider for AdoptiumProvider { available_major_versions: available.available_releases, lts_versions: available.available_lts_releases, cached_at: now, - cache_version: 1, }; - let _ = super::super::save_catalog_cache(app_handle, &catalog); + let _ = save_catalog_cache(app_handle, &catalog); Ok(catalog) } @@ -238,9 +238,10 @@ impl JavaProvider for AdoptiumProvider { ))); } - let assets: Vec = response.json().await.map_err(|e| { - JavaError::SerializationError(format!("Failed to parse API response: {}", e)) - })?; + let assets: Vec = + response.json::>().await.map_err(|e| { + JavaError::SerializationError(format!("Failed to parse API response: {}", e)) + })?; let asset = assets .into_iter() @@ -265,9 +266,10 @@ impl JavaProvider for AdoptiumProvider { .await .map_err(|e| JavaError::NetworkError(format!("Network request failed: {}", e)))?; - let releases: AvailableReleases = response.json().await.map_err(|e| { - JavaError::SerializationError(format!("Failed to parse response: {}", e)) - })?; + let releases: AvailableReleases = + response.json::().await.map_err(|e| { + JavaError::SerializationError(format!("Failed to parse response: {}", e)) + })?; Ok(releases.available_releases) } diff --git a/src-tauri/src/core/java/validation.rs b/src-tauri/src/core/java/validation.rs index e086e74..48782f6 100644 --- a/src-tauri/src/core/java/validation.rs +++ b/src-tauri/src/core/java/validation.rs @@ -8,12 +8,10 @@ use std::os::windows::process::CommandExt; use super::JavaInstallation; pub async fn check_java_installation(path: &PathBuf) -> Option { - let path = path.clone(); - tokio::task::spawn_blocking(move || { - check_java_installation_blocking(&path) - }) - .await - .ok()? + let path = path.clone(); + tokio::task::spawn_blocking(move || check_java_installation_blocking(&path)) + .await + .ok()? } fn check_java_installation_blocking(path: &PathBuf) -> Option { @@ -24,23 +22,23 @@ fn check_java_installation_blocking(path: &PathBuf) -> Option #[cfg(target_os = "windows")] cmd.creation_flags(0x08000000); - let output = cmd.output().ok()?; + let output = cmd.output().ok()?; - let version_output = String::from_utf8_lossy(&output.stderr); + let version_output = String::from_utf8_lossy(&output.stderr); let version = parse_version_string(&version_output)?; let arch = extract_architecture(&version_output); let vendor = extract_vendor(&version_output); let is_64bit = version_output.to_lowercase().contains("64-bit") || arch == "aarch64"; - Some(JavaInstallation { - path: path.to_string_lossy().to_string(), - version, - arch, - vendor, - source: "system".to_string(), - is_64bit, - }) + Some(JavaInstallation { + path: path.to_string_lossy().to_string(), + version, + arch, + vendor, + source: "system".to_string(), + is_64bit, + }) } pub fn parse_version_string(output: &str) -> Option { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7984ea8..b74c746 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -208,7 +208,6 @@ async fn start_game( let java_installation = core::java::priority::resolve_java_for_launch( app_handle, - &window, instance.java_path_override.as_deref(), Some(&config.java_path), required_java_major, -- cgit v1.2.3-70-g09d2