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/core/java/providers/adoptium.rs | 306 ++++++++++++++++++++++++++ src-tauri/src/core/java/providers/mod.rs | 3 + 2 files changed, 309 insertions(+) create mode 100644 src-tauri/src/core/java/providers/adoptium.rs create mode 100644 src-tauri/src/core/java/providers/mod.rs (limited to 'src-tauri/src/core/java/providers') diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs new file mode 100644 index 0000000..dfd4c0e --- /dev/null +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -0,0 +1,306 @@ +use crate::core::java::provider::JavaProvider; +use crate::core::java::{ImageType, JavaCatalog, JavaDownloadInfo, JavaReleaseInfo}; +use serde::Deserialize; +use tauri::AppHandle; + +const ADOPTIUM_API_BASE: &str = "https://api.adoptium.net/v3"; + +#[derive(Debug, Clone, Deserialize)] +pub struct AdoptiumAsset { + pub binary: AdoptiumBinary, + pub release_name: String, + pub version: AdoptiumVersionData, +} + +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct AdoptiumBinary { + pub os: String, + pub architecture: String, + pub image_type: String, + pub package: AdoptiumPackage, + #[serde(default)] + pub updated_at: Option, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct AdoptiumPackage { + pub name: String, + pub link: String, + pub size: u64, + pub checksum: Option, +} + +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct AdoptiumVersionData { + pub major: u32, + pub minor: u32, + pub security: u32, + pub semver: String, + pub openjdk_version: String, +} + +#[derive(Debug, Clone, Deserialize)] +#[allow(dead_code)] +pub struct AvailableReleases { + pub available_releases: Vec, + pub available_lts_releases: Vec, + pub most_recent_lts: Option, + pub most_recent_feature_release: Option, +} + +pub struct AdoptiumProvider; + +impl AdoptiumProvider { + pub fn new() -> Self { + Self + } +} + +impl Default for AdoptiumProvider { + fn default() -> Self { + Self::new() + } +} + +impl JavaProvider for AdoptiumProvider { + async fn fetch_catalog( + &self, + app_handle: &AppHandle, + force_refresh: bool, + ) -> Result { + if !force_refresh { + if let Some(cached) = super::super::load_cached_catalog(app_handle) { + return Ok(cached); + } + } + + let os = self.os_name(); + let arch = self.arch_name(); + let client = reqwest::Client::new(); + + let releases_url = format!("{}/info/available_releases", ADOPTIUM_API_BASE); + let available: AvailableReleases = client + .get(&releases_url) + .header("Accept", "application/json") + .send() + .await + .map_err(|e| format!("Failed to fetch available releases: {}", e))? + .json() + .await + .map_err(|e| format!("Failed to parse available releases: {}", e))?; + + let mut releases = Vec::new(); + + for major_version in &available.available_releases { + for image_type in &["jre", "jdk"] { + let url = format!( + "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}", + ADOPTIUM_API_BASE, major_version, os, arch, image_type + ); + + match client + .get(&url) + .header("Accept", "application/json") + .send() + .await + { + Ok(response) => { + if response.status().is_success() { + if let Ok(assets) = response.json::>().await { + if let Some(asset) = assets.into_iter().next() { + let release_date = asset.binary.updated_at.clone(); + let release_info = JavaReleaseInfo { + major_version: *major_version, + image_type: image_type.to_string(), + version: asset.version.semver.clone(), + release_name: asset.release_name.clone(), + release_date, + file_size: asset.binary.package.size, + checksum: asset.binary.package.checksum, + download_url: asset.binary.package.link, + is_lts: available + .available_lts_releases + .contains(major_version), + is_available: true, + architecture: asset.binary.architecture.clone(), + }; + releases.push(release_info); + } + } + } else { + let release_info = JavaReleaseInfo { + major_version: *major_version, + image_type: image_type.to_string(), + version: format!("{}.x", major_version), + release_name: format!("jdk-{}", major_version), + release_date: None, + file_size: 0, + checksum: None, + download_url: String::new(), + is_lts: available.available_lts_releases.contains(major_version), + is_available: false, + architecture: arch.to_string(), + }; + releases.push(release_info); + } + } + Err(_) => { + releases.push(JavaReleaseInfo { + major_version: *major_version, + image_type: image_type.to_string(), + version: format!("{}.x", major_version), + release_name: format!("jdk-{}", major_version), + release_date: None, + file_size: 0, + checksum: None, + download_url: String::new(), + is_lts: available.available_lts_releases.contains(major_version), + is_available: false, + architecture: arch.to_string(), + }); + } + } + } + } + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + let catalog = JavaCatalog { + releases, + available_major_versions: available.available_releases, + lts_versions: available.available_lts_releases, + cached_at: now, + }; + + let _ = super::super::save_catalog_cache(app_handle, &catalog); + + Ok(catalog) + } + + async fn fetch_release( + &self, + major_version: u32, + image_type: ImageType, + ) -> Result { + let os = self.os_name(); + let arch = self.arch_name(); + + let url = format!( + "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}", + ADOPTIUM_API_BASE, major_version, os, arch, image_type + ); + + let client = reqwest::Client::new(); + let response = client + .get(&url) + .header("Accept", "application/json") + .send() + .await + .map_err(|e| format!("Network request failed: {}", e))?; + + if !response.status().is_success() { + return Err(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 asset = assets + .into_iter() + .next() + .ok_or_else(|| format!("Java {} {} download not found", major_version, image_type))?; + + Ok(JavaDownloadInfo { + version: asset.version.semver.clone(), + release_name: asset.release_name, + download_url: asset.binary.package.link, + file_name: asset.binary.package.name, + file_size: asset.binary.package.size, + checksum: asset.binary.package.checksum, + image_type: asset.binary.image_type, + }) + } + + async fn available_versions(&self) -> Result, String> { + let url = format!("{}/info/available_releases", ADOPTIUM_API_BASE); + + let response = reqwest::get(url) + .await + .map_err(|e| format!("Network request failed: {}", e))?; + + let releases: AvailableReleases = response + .json() + .await + .map_err(|e| format!("Failed to parse response: {}", e))?; + + Ok(releases.available_releases) + } + + fn provider_name(&self) -> &'static str { + "adoptium" + } + + fn os_name(&self) -> &'static str { + #[cfg(target_os = "linux")] + { + if std::path::Path::new("/etc/alpine-release").exists() { + return "alpine-linux"; + } + "linux" + } + #[cfg(target_os = "macos")] + { + "mac" + } + #[cfg(target_os = "windows")] + { + "windows" + } + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + { + "linux" + } + } + + fn arch_name(&self) -> &'static str { + #[cfg(target_arch = "x86_64")] + { + "x64" + } + #[cfg(target_arch = "aarch64")] + { + "aarch64" + } + #[cfg(target_arch = "x86")] + { + "x86" + } + #[cfg(target_arch = "arm")] + { + "arm" + } + #[cfg(not(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "x86", + target_arch = "arm" + )))] + { + "x64" + } + } + + fn install_prefix(&self) -> &'static str { + "temurin" + } +} diff --git a/src-tauri/src/core/java/providers/mod.rs b/src-tauri/src/core/java/providers/mod.rs new file mode 100644 index 0000000..16eb5c7 --- /dev/null +++ b/src-tauri/src/core/java/providers/mod.rs @@ -0,0 +1,3 @@ +pub mod adoptium; + +pub use adoptium::AdoptiumProvider; -- cgit v1.2.3-70-g09d2 From 83e9e3c6067c8a2016676d461c17835d4da8b7ab Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:28:46 +0100 Subject: refactor(java): address Sourcery AI code review feedback - Centralize strip_unc_prefix into java/mod.rs to eliminate duplication across detection.rs and validation.rs - Remove unused JAVA_CHECK_TIMEOUT constant from validation.rs - Implement actual timeout mechanism in run_which_command_with_timeout() using try_wait() loop - Parallelize Adoptium API requests for better catalog fetch performance Fixes: - Multiple strip_unc_prefix implementations consolidated - Timeout constant now properly enforced in which/where command execution - Catalog fetching now uses concurrent tokio::spawn tasks instead of sequential await Reviewed-by: Claude Sonnet 4.5 --- src-tauri/src/core/java/detection.rs | 59 +++++++++------ src-tauri/src/core/java/mod.rs | 18 ++++- src-tauri/src/core/java/providers/adoptium.rs | 103 ++++++++++++++------------ src-tauri/src/core/java/validation.rs | 14 ---- 4 files changed, 110 insertions(+), 84 deletions(-) (limited to 'src-tauri/src/core/java/providers') diff --git a/src-tauri/src/core/java/detection.rs b/src-tauri/src/core/java/detection.rs index 263580f..ee2111e 100644 --- a/src-tauri/src/core/java/detection.rs +++ b/src-tauri/src/core/java/detection.rs @@ -1,20 +1,15 @@ +use std::io::Read; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Stdio}; +use std::thread::sleep; +use std::time::{Duration, Instant}; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; -pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { - #[cfg(target_os = "windows")] - { - // Remove the UNC prefix (\\?\) from Windows paths - let s = path.to_string_lossy().to_string(); - if s.starts_with(r"\\?\") { - return PathBuf::from(&s[4..]); - } - } - path -} +use super::strip_unc_prefix; + +const WHICH_TIMEOUT: Duration = Duration::from_secs(2); pub fn find_sdkman_java() -> Option { let home = std::env::var("HOME").ok()?; @@ -30,18 +25,40 @@ fn run_which_command_with_timeout() -> Option { let mut cmd = Command::new(if cfg!(windows) { "where" } else { "which" }); cmd.arg("java"); #[cfg(target_os = "windows")] - // Hide the console window on Windows cmd.creation_flags(0x08000000); - - match cmd.output() { - Ok(output) => { - if output.status.success() { - Some(String::from_utf8_lossy(&output.stdout).to_string()) - } else { - None + cmd.stdout(Stdio::piped()); + + let start = Instant::now(); + let mut child = cmd.spawn().ok()?; + + loop { + match child.try_wait() { + Ok(Some(status)) => { + if status.success() { + let mut output = String::new(); + if let Some(mut stdout) = child.stdout.take() { + let _ = stdout.read_to_string(&mut output); + } + return Some(output); + } else { + let _ = child.wait(); + return None; + } + } + Ok(None) => { + if start.elapsed() >= WHICH_TIMEOUT { + let _ = child.kill(); + let _ = child.wait(); + return None; + } + sleep(Duration::from_millis(50)); + } + Err(_) => { + let _ = child.kill(); + let _ = child.wait(); + return None; } } - Err(_) => None, } } diff --git a/src-tauri/src/core/java/mod.rs b/src-tauri/src/core/java/mod.rs index fd82390..05bf734 100644 --- a/src-tauri/src/core/java/mod.rs +++ b/src-tauri/src/core/java/mod.rs @@ -9,6 +9,18 @@ pub mod validation; pub mod provider; pub mod providers; +/// Remove the UNC prefix (\\?\) from Windows paths +pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { + #[cfg(target_os = "windows")] + { + let s = path.to_string_lossy().to_string(); + if s.starts_with(r"\\?\\") { + return PathBuf::from(&s[4..]); + } + } + path +} + use crate::core::downloader::{DownloadQueue, JavaDownloadProgress, PendingJavaDownload}; use crate::utils::zip; use provider::JavaProvider; @@ -267,7 +279,7 @@ pub async fn download_and_install_java( } let java_bin = std::fs::canonicalize(&java_bin).map_err(|e| e.to_string())?; - let java_bin = validation::strip_unc_prefix(java_bin); + let java_bin = strip_unc_prefix(java_bin); let installation = validation::check_java_installation(&java_bin) .await @@ -431,7 +443,7 @@ fn find_java_executable(dir: &PathBuf) -> Option { let direct_bin = dir.join("bin").join(bin_name); if direct_bin.exists() { let resolved = std::fs::canonicalize(&direct_bin).unwrap_or(direct_bin); - return Some(validation::strip_unc_prefix(resolved)); + return Some(strip_unc_prefix(resolved)); } #[cfg(target_os = "macos")] @@ -449,7 +461,7 @@ fn find_java_executable(dir: &PathBuf) -> Option { let nested_bin = path.join("bin").join(bin_name); if nested_bin.exists() { let resolved = std::fs::canonicalize(&nested_bin).unwrap_or(nested_bin); - return Some(validation::strip_unc_prefix(resolved)); + return Some(strip_unc_prefix(resolved)); } #[cfg(target_os = "macos")] diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index dfd4c0e..53d1519 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -91,77 +91,88 @@ impl JavaProvider for AdoptiumProvider { .await .map_err(|e| format!("Failed to parse available releases: {}", e))?; - let mut releases = Vec::new(); + // Parallelize HTTP requests for better performance + let mut fetch_tasks = Vec::new(); for major_version in &available.available_releases { for image_type in &["jre", "jdk"] { + let major_version = *major_version; + let image_type = image_type.to_string(); let url = format!( "{}/assets/latest/{}/hotspot?os={}&architecture={}&image_type={}", ADOPTIUM_API_BASE, major_version, os, arch, image_type ); - - match client - .get(&url) - .header("Accept", "application/json") - .send() - .await - { - Ok(response) => { - if response.status().is_success() { - if let Ok(assets) = response.json::>().await { - if let Some(asset) = assets.into_iter().next() { - let release_date = asset.binary.updated_at.clone(); - let release_info = JavaReleaseInfo { - major_version: *major_version, - image_type: image_type.to_string(), - version: asset.version.semver.clone(), - release_name: asset.release_name.clone(), - release_date, - file_size: asset.binary.package.size, - checksum: asset.binary.package.checksum, - download_url: asset.binary.package.link, - is_lts: available - .available_lts_releases - .contains(major_version), - is_available: true, - architecture: asset.binary.architecture.clone(), - }; - releases.push(release_info); + let client = client.clone(); + let is_lts = available.available_lts_releases.contains(&major_version); + let arch = arch.to_string(); + + let task = tokio::spawn(async move { + match client + .get(&url) + .header("Accept", "application/json") + .send() + .await + { + Ok(response) => { + if response.status().is_success() { + if let Ok(assets) = response.json::>().await { + if let Some(asset) = assets.into_iter().next() { + let release_date = asset.binary.updated_at.clone(); + return Some(JavaReleaseInfo { + major_version, + image_type, + version: asset.version.semver.clone(), + release_name: asset.release_name.clone(), + release_date, + file_size: asset.binary.package.size, + checksum: asset.binary.package.checksum, + download_url: asset.binary.package.link, + is_lts, + is_available: true, + architecture: asset.binary.architecture.clone(), + }); + } } } - } else { - let release_info = JavaReleaseInfo { - major_version: *major_version, - image_type: image_type.to_string(), + // Fallback for unsuccessful response + Some(JavaReleaseInfo { + major_version, + image_type, version: format!("{}.x", major_version), release_name: format!("jdk-{}", major_version), release_date: None, file_size: 0, checksum: None, download_url: String::new(), - is_lts: available.available_lts_releases.contains(major_version), + is_lts, is_available: false, - architecture: arch.to_string(), - }; - releases.push(release_info); + architecture: arch, + }) } - } - Err(_) => { - releases.push(JavaReleaseInfo { - major_version: *major_version, - image_type: image_type.to_string(), + Err(_) => Some(JavaReleaseInfo { + major_version, + image_type, version: format!("{}.x", major_version), release_name: format!("jdk-{}", major_version), release_date: None, file_size: 0, checksum: None, download_url: String::new(), - is_lts: available.available_lts_releases.contains(major_version), + is_lts, is_available: false, - architecture: arch.to_string(), - }); + architecture: arch, + }), } - } + }); + fetch_tasks.push(task); + } + } + + // Collect all results concurrently + let mut releases = Vec::new(); + for task in fetch_tasks { + if let Ok(Some(release)) = task.await { + releases.push(release); } } diff --git a/src-tauri/src/core/java/validation.rs b/src-tauri/src/core/java/validation.rs index ec7745d..8eca58a 100644 --- a/src-tauri/src/core/java/validation.rs +++ b/src-tauri/src/core/java/validation.rs @@ -1,25 +1,11 @@ use std::path::PathBuf; use std::process::Command; -use std::time::Duration; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; use super::JavaInstallation; -const JAVA_CHECK_TIMEOUT: Duration = Duration::from_secs(5); - -pub fn strip_unc_prefix(path: PathBuf) -> PathBuf { - #[cfg(target_os = "windows")] - { - let s = path.to_string_lossy().to_string(); - if s.starts_with(r"\\?\") { - return PathBuf::from(&s[4..]); - } - } - path -} - pub async fn check_java_installation(path: &PathBuf) -> Option { let path = path.clone(); tokio::task::spawn_blocking(move || { -- 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/core/java/providers') 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/core/java/providers') 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 68a493ef22cb0558a05dd3881c7d8cb4999d9679 Mon Sep 17 00:00:00 2001 From: "Begonia, HE" <163421589+BegoniaHe@users.noreply.github.com> Date: Tue, 27 Jan 2026 05:56:59 +0100 Subject: feat(java): implement cache versioning, size limits, and automatic cleanup - Add CACHE_VERSION constant for cache format compatibility tracking - Add MAX_CACHE_SIZE_BYTES limit (10 MB) to prevent unbounded cache growth - Add cache_version field to JavaCatalog struct with default value - Implement cache version validation in load_cached_catalog() - Implement cache size enforcement in save_catalog_cache() - Add cleanup_expired_caches() for background cache cleanup - Add enforce_cache_size_limit() to validate cache file sizes - Add is_cache_version_compatible() helper function - Automatically clean up expired caches on load and clear operations - Validate cache version before using cached data Fixes: - Cache expiration without automatic cleanup (now cleaned on load) - Missing cache version control (now validates format compatibility) - Unbounded cache size growth (now limited to 10 MB) Reviewed-by: Claude 3.5 Sonnet --- src-tauri/src/core/java/providers/adoptium.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src-tauri/src/core/java/providers') diff --git a/src-tauri/src/core/java/providers/adoptium.rs b/src-tauri/src/core/java/providers/adoptium.rs index 13ef2a5..4b06721 100644 --- a/src-tauri/src/core/java/providers/adoptium.rs +++ b/src-tauri/src/core/java/providers/adoptium.rs @@ -198,6 +198,7 @@ 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); -- 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/core/java/providers') 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/core/java/providers') 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