diff options
| -rw-r--r-- | src-tauri/Cargo.toml | 1 | ||||
| -rw-r--r-- | src-tauri/src/core/instance.rs | 325 | ||||
| -rw-r--r-- | src-tauri/src/core/mod.rs | 1 | ||||
| -rw-r--r-- | src-tauri/src/main.rs | 260 | ||||
| -rw-r--r-- | src-tauri/src/utils/mod.rs | 2 | ||||
| -rw-r--r-- | src-tauri/src/utils/path.rs | 22 |
6 files changed, 527 insertions, 84 deletions
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 407da5a..663a5b1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ serde_urlencoded = "0.7.1" tauri-plugin-dialog = "2.6.0" tauri-plugin-fs = "2.4.5" bytes = "1.11.0" +chrono = "0.4" [build-dependencies] tauri-build = { version = "2.0", features = [] } diff --git a/src-tauri/src/core/instance.rs b/src-tauri/src/core/instance.rs new file mode 100644 index 0000000..90ec34e --- /dev/null +++ b/src-tauri/src/core/instance.rs @@ -0,0 +1,325 @@ +//! Instance/Profile management module. +//! +//! This module provides functionality to: +//! - Create and manage multiple isolated game instances +//! - Each instance has its own versions, libraries, assets, mods, and saves +//! - Support for instance switching and isolation + +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; +use tauri::{AppHandle, Manager}; + +/// Represents a game instance/profile +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Instance { + pub id: String, // 唯一标识符(UUID) + pub name: String, // 显示名称 + pub game_dir: PathBuf, // 游戏目录路径 + pub version_id: Option<String>, // 当前选择的版本ID + pub created_at: i64, // 创建时间戳 + pub last_played: Option<i64>, // 最后游玩时间 + pub icon_path: Option<String>, // 图标路径(可选) + pub notes: Option<String>, // 备注(可选) + pub mod_loader: Option<String>, // 模组加载器类型:"fabric", "forge", "vanilla" + pub mod_loader_version: Option<String>, // 模组加载器版本 +} + +/// Configuration for all instances +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct InstanceConfig { + pub instances: Vec<Instance>, + pub active_instance_id: Option<String>, // 当前活动的实例ID +} + +/// State management for instances +pub struct InstanceState { + pub instances: Mutex<InstanceConfig>, + pub file_path: PathBuf, +} + +impl InstanceState { + /// Create a new InstanceState + pub fn new(app_handle: &AppHandle) -> Self { + let app_dir = app_handle.path().app_data_dir().unwrap(); + let file_path = app_dir.join("instances.json"); + + let config = if file_path.exists() { + let content = fs::read_to_string(&file_path).unwrap_or_default(); + serde_json::from_str(&content).unwrap_or_else(|_| InstanceConfig::default()) + } else { + InstanceConfig::default() + }; + + Self { + instances: Mutex::new(config), + file_path, + } + } + + /// Save the instance configuration to disk + pub fn save(&self) -> Result<(), String> { + let config = self.instances.lock().unwrap(); + let content = serde_json::to_string_pretty(&*config).map_err(|e| e.to_string())?; + fs::create_dir_all(self.file_path.parent().unwrap()).map_err(|e| e.to_string())?; + fs::write(&self.file_path, content).map_err(|e| e.to_string())?; + Ok(()) + } + + /// Create a new instance + pub fn create_instance( + &self, + name: String, + app_handle: &AppHandle, + ) -> Result<Instance, String> { + let app_dir = app_handle.path().app_data_dir().unwrap(); + let instance_id = uuid::Uuid::new_v4().to_string(); + let instance_dir = app_dir.join("instances").join(&instance_id); + let game_dir = instance_dir.clone(); + + // Create instance directory structure + fs::create_dir_all(&instance_dir).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("versions")).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("libraries")).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("assets")).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("mods")).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("config")).map_err(|e| e.to_string())?; + fs::create_dir_all(instance_dir.join("saves")).map_err(|e| e.to_string())?; + + let instance = Instance { + id: instance_id.clone(), + name, + game_dir, + version_id: None, + created_at: chrono::Utc::now().timestamp(), + last_played: None, + icon_path: None, + notes: None, + mod_loader: Some("vanilla".to_string()), + mod_loader_version: None, + }; + + let mut config = self.instances.lock().unwrap(); + config.instances.push(instance.clone()); + + // If this is the first instance, set it as active + if config.active_instance_id.is_none() { + config.active_instance_id = Some(instance_id); + } + + drop(config); + self.save()?; + + Ok(instance) + } + + /// Delete an instance + pub fn delete_instance(&self, id: &str) -> Result<(), String> { + let mut config = self.instances.lock().unwrap(); + + // Find the instance + let instance_index = config + .instances + .iter() + .position(|i| i.id == id) + .ok_or_else(|| format!("Instance {} not found", id))?; + + let instance = config.instances[instance_index].clone(); + + // Remove from list + config.instances.remove(instance_index); + + // If this was the active instance, clear or set another as active + if config.active_instance_id.as_ref() == Some(&id.to_string()) { + config.active_instance_id = config.instances.first().map(|i| i.id.clone()); + } + + drop(config); + self.save()?; + + // Delete the instance directory + if instance.game_dir.exists() { + fs::remove_dir_all(&instance.game_dir) + .map_err(|e| format!("Failed to delete instance directory: {}", e))?; + } + + Ok(()) + } + + /// Update an instance + pub fn update_instance(&self, instance: Instance) -> Result<(), String> { + let mut config = self.instances.lock().unwrap(); + + let index = config + .instances + .iter() + .position(|i| i.id == instance.id) + .ok_or_else(|| format!("Instance {} not found", instance.id))?; + + config.instances[index] = instance; + drop(config); + self.save()?; + + Ok(()) + } + + /// Get an instance by ID + pub fn get_instance(&self, id: &str) -> Option<Instance> { + let config = self.instances.lock().unwrap(); + config.instances.iter().find(|i| i.id == id).cloned() + } + + /// List all instances + pub fn list_instances(&self) -> Vec<Instance> { + let config = self.instances.lock().unwrap(); + config.instances.clone() + } + + /// Set the active instance + pub fn set_active_instance(&self, id: &str) -> Result<(), String> { + let mut config = self.instances.lock().unwrap(); + + // Verify the instance exists + if !config.instances.iter().any(|i| i.id == id) { + return Err(format!("Instance {} not found", id)); + } + + config.active_instance_id = Some(id.to_string()); + drop(config); + self.save()?; + + Ok(()) + } + + /// Get the active instance + pub fn get_active_instance(&self) -> Option<Instance> { + let config = self.instances.lock().unwrap(); + config + .active_instance_id + .as_ref() + .and_then(|id| config.instances.iter().find(|i| i.id == *id)) + .cloned() + } + + /// Get the game directory for an instance + pub fn get_instance_game_dir(&self, id: &str) -> Option<PathBuf> { + self.get_instance(id).map(|i| i.game_dir) + } + + /// Duplicate an instance + pub fn duplicate_instance( + &self, + id: &str, + new_name: String, + app_handle: &AppHandle, + ) -> Result<Instance, String> { + let source_instance = self + .get_instance(id) + .ok_or_else(|| format!("Instance {} not found", id))?; + + // Create new instance + let mut new_instance = self.create_instance(new_name, app_handle)?; + + // Copy instance properties + new_instance.version_id = source_instance.version_id.clone(); + new_instance.mod_loader = source_instance.mod_loader.clone(); + new_instance.mod_loader_version = source_instance.mod_loader_version.clone(); + new_instance.notes = source_instance.notes.clone(); + + // Copy directory contents + if source_instance.game_dir.exists() { + copy_dir_all(&source_instance.game_dir, &new_instance.game_dir) + .map_err(|e| format!("Failed to copy instance directory: {}", e))?; + } + + self.update_instance(new_instance.clone())?; + + Ok(new_instance) + } +} + +/// Copy a directory recursively +fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), std::io::Error> { + fs::create_dir_all(dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(&entry.path(), &dst.join(entry.file_name()))?; + } else { + fs::copy(entry.path(), dst.join(entry.file_name()))?; + } + } + Ok(()) +} + +/// Migrate legacy data to instance system +pub fn migrate_legacy_data( + app_handle: &AppHandle, + instance_state: &InstanceState, +) -> Result<(), String> { + let app_dir = app_handle.path().app_data_dir().unwrap(); + let old_versions_dir = app_dir.join("versions"); + let old_libraries_dir = app_dir.join("libraries"); + let old_assets_dir = app_dir.join("assets"); + + // Check if legacy data exists + let has_legacy_data = + old_versions_dir.exists() || old_libraries_dir.exists() || old_assets_dir.exists(); + + if !has_legacy_data { + return Ok(()); // No legacy data to migrate + } + + // Check if instances already exist + let config = instance_state.instances.lock().unwrap(); + if !config.instances.is_empty() { + drop(config); + return Ok(()); // Already have instances, skip migration + } + drop(config); + + // Create default instance + let default_instance = instance_state + .create_instance("Default".to_string(), app_handle) + .map_err(|e| format!("Failed to create default instance: {}", e))?; + + let new_versions_dir = default_instance.game_dir.join("versions"); + let new_libraries_dir = default_instance.game_dir.join("libraries"); + let new_assets_dir = default_instance.game_dir.join("assets"); + + // Move legacy data + if old_versions_dir.exists() { + if new_versions_dir.exists() { + // Merge directories + copy_dir_all(&old_versions_dir, &new_versions_dir) + .map_err(|e| format!("Failed to migrate versions: {}", e))?; + } else { + fs::rename(&old_versions_dir, &new_versions_dir) + .map_err(|e| format!("Failed to migrate versions: {}", e))?; + } + } + + if old_libraries_dir.exists() { + if new_libraries_dir.exists() { + copy_dir_all(&old_libraries_dir, &new_libraries_dir) + .map_err(|e| format!("Failed to migrate libraries: {}", e))?; + } else { + fs::rename(&old_libraries_dir, &new_libraries_dir) + .map_err(|e| format!("Failed to migrate libraries: {}", e))?; + } + } + + if old_assets_dir.exists() { + if new_assets_dir.exists() { + copy_dir_all(&old_assets_dir, &new_assets_dir) + .map_err(|e| format!("Failed to migrate assets: {}", e))?; + } else { + fs::rename(&old_assets_dir, &new_assets_dir) + .map_err(|e| format!("Failed to migrate assets: {}", e))?; + } + } + + Ok(()) +} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 7ad6ef9..dcbd47a 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -6,6 +6,7 @@ pub mod downloader; pub mod fabric; pub mod forge; pub mod game_version; +pub mod instance; pub mod java; pub mod manifest; pub mod maven; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 661309a..2871b03 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -68,11 +68,16 @@ async fn start_game( auth_state: State<'_, core::auth::AccountState>, config_state: State<'_, core::config::ConfigState>, assistant_state: State<'_, core::assistant::AssistantState>, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, version_id: String, ) -> Result<String, String> { emit_log!( window, - format!("Starting game launch for version: {}", version_id) + format!( + "Starting game launch for version: {} in instance: {}", + version_id, instance_id + ) ); // Check for active account @@ -93,14 +98,10 @@ async fn start_game( format!("Memory: {}MB - {}MB", config.min_memory, config.max_memory) ); - // Get App Data Directory (e.g., ~/.local/share/com.dropout.launcher or similar) - // The identifier is set in tauri.conf.json. - // If not accessible, use a specific logic. - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + // Get game directory from instance + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // Ensure game directory exists tokio::fs::create_dir_all(&game_dir) @@ -169,6 +170,7 @@ async fn start_game( }; // Check if configured Java is compatible + 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 = @@ -890,12 +892,15 @@ async fn get_versions(window: Window) -> Result<Vec<core::manifest::Version>, St /// Check if a version is installed (has client.jar) #[tauri::command] -async fn check_version_installed(window: Window, version_id: String) -> Result<bool, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; +async fn check_version_installed( + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, + version_id: String, +) -> Result<bool, String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // For modded versions, check the parent vanilla version let minecraft_version = if version_id.starts_with("fabric-loader-") { @@ -929,19 +934,24 @@ async fn check_version_installed(window: Window, version_id: String) -> Result<b async fn install_version( window: Window, config_state: State<'_, core::config::ConfigState>, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, version_id: String, ) -> Result<(), String> { emit_log!( window, - format!("Starting installation for version: {}", version_id) + format!( + "Starting installation for version: {} in instance: {}", + version_id, instance_id + ) ); let config = config_state.config.lock().unwrap().clone(); - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + + // Get game directory from instance + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // Ensure game directory exists tokio::fs::create_dir_all(&game_dir) @@ -1530,22 +1540,22 @@ async fn get_fabric_loaders_for_version( #[tauri::command] async fn install_fabric( window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, game_version: String, loader_version: String, ) -> Result<core::fabric::InstalledFabricVersion, String> { emit_log!( window, format!( - "Installing Fabric {} for Minecraft {}...", - loader_version, game_version + "Installing Fabric {} for Minecraft {} in instance {}...", + loader_version, game_version, instance_id ) ); - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; let result = core::fabric::install_fabric(&game_dir, &game_version, &loader_version) .await @@ -1564,12 +1574,14 @@ async fn install_fabric( /// List installed Fabric versions #[tauri::command] -async fn list_installed_fabric_versions(window: Window) -> Result<Vec<String>, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; +async fn list_installed_fabric_versions( + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, +) -> Result<Vec<String>, String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; core::fabric::list_installed_fabric_versions(&game_dir) .await @@ -1579,14 +1591,14 @@ async fn list_installed_fabric_versions(window: Window) -> Result<Vec<String>, S /// Get Java version requirement for a specific version #[tauri::command] async fn get_version_java_version( - window: Window, + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, version_id: String, ) -> Result<Option<u64>, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // Try to load the version JSON to get javaVersion match core::manifest::load_version(&game_dir, &version_id).await { @@ -1607,12 +1619,15 @@ struct VersionMetadata { /// Delete a version (remove version directory) #[tauri::command] -async fn delete_version(window: Window, version_id: String) -> Result<(), String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; +async fn delete_version( + window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, + version_id: String, +) -> Result<(), String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; let version_dir = game_dir.join("versions").join(&version_id); @@ -1634,14 +1649,14 @@ async fn delete_version(window: Window, version_id: String) -> Result<(), String /// Get detailed metadata for a specific version #[tauri::command] async fn get_version_metadata( - window: Window, + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, version_id: String, ) -> Result<VersionMetadata, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // Initialize metadata let mut metadata = VersionMetadata { @@ -1728,12 +1743,14 @@ struct InstalledVersion { /// List all installed versions from the data directory /// Simply lists all folders in the versions directory without validation #[tauri::command] -async fn list_installed_versions(window: Window) -> Result<Vec<InstalledVersion>, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; +async fn list_installed_versions( + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, +) -> Result<Vec<InstalledVersion>, String> { + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; let versions_dir = game_dir.join("versions"); let mut installed = Vec::new(); @@ -1813,15 +1830,15 @@ async fn list_installed_versions(window: Window) -> Result<Vec<InstalledVersion> /// Check if Fabric is installed for a specific version #[tauri::command] async fn is_fabric_installed( - window: Window, + _window: Window, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, game_version: String, loader_version: String, ) -> Result<bool, String> { - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; Ok(core::fabric::is_fabric_installed( &game_dir, @@ -1853,25 +1870,26 @@ async fn get_forge_versions_for_game( async fn install_forge( window: Window, config_state: State<'_, core::config::ConfigState>, + instance_state: State<'_, core::instance::InstanceState>, + instance_id: String, game_version: String, forge_version: String, ) -> Result<core::forge::InstalledForgeVersion, String> { emit_log!( window, format!( - "Installing Forge {} for Minecraft {}...", - forge_version, game_version + "Installing Forge {} for Minecraft {} in instance {}...", + forge_version, game_version, instance_id ) ); - let app_handle = window.app_handle(); - let game_dir = app_handle - .path() - .app_data_dir() - .map_err(|e| format!("Failed to get app data dir: {}", e))?; + let game_dir = instance_state + .get_instance_game_dir(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id))?; // Get Java path from config or detect let config = config_state.config.lock().unwrap().clone(); + let app_handle = window.app_handle(); let java_path_str = if !config.java_path.is_empty() && config.java_path != "java" { config.java_path.clone() } else { @@ -2075,6 +2093,85 @@ async fn list_openai_models( assistant.list_openai_models(&config.assistant).await } +// ==================== Instance Management Commands ==================== + +/// Create a new instance +#[tauri::command] +async fn create_instance( + window: Window, + state: State<'_, core::instance::InstanceState>, + name: String, +) -> Result<core::instance::Instance, String> { + let app_handle = window.app_handle(); + state.create_instance(name, app_handle) +} + +/// Delete an instance +#[tauri::command] +async fn delete_instance( + state: State<'_, core::instance::InstanceState>, + instance_id: String, +) -> Result<(), String> { + state.delete_instance(&instance_id) +} + +/// Update an instance +#[tauri::command] +async fn update_instance( + state: State<'_, core::instance::InstanceState>, + instance: core::instance::Instance, +) -> Result<(), String> { + state.update_instance(instance) +} + +/// Get all instances +#[tauri::command] +async fn list_instances( + state: State<'_, core::instance::InstanceState>, +) -> Result<Vec<core::instance::Instance>, String> { + Ok(state.list_instances()) +} + +/// Get a single instance by ID +#[tauri::command] +async fn get_instance( + state: State<'_, core::instance::InstanceState>, + instance_id: String, +) -> Result<core::instance::Instance, String> { + state + .get_instance(&instance_id) + .ok_or_else(|| format!("Instance {} not found", instance_id)) +} + +/// Set the active instance +#[tauri::command] +async fn set_active_instance( + state: State<'_, core::instance::InstanceState>, + instance_id: String, +) -> Result<(), String> { + state.set_active_instance(&instance_id) +} + +/// Get the active instance +#[tauri::command] +async fn get_active_instance( + state: State<'_, core::instance::InstanceState>, +) -> Result<Option<core::instance::Instance>, String> { + Ok(state.get_active_instance()) +} + +/// Duplicate an instance +#[tauri::command] +async fn duplicate_instance( + window: Window, + state: State<'_, core::instance::InstanceState>, + instance_id: String, + new_name: String, +) -> Result<core::instance::Instance, String> { + let app_handle = window.app_handle(); + state.duplicate_instance(&instance_id, new_name, app_handle) +} + #[tauri::command] async fn assistant_chat_stream( window: tauri::Window, @@ -2101,6 +2198,16 @@ fn main() { let config_state = core::config::ConfigState::new(app.handle()); app.manage(config_state); + // Initialize instance state + let instance_state = core::instance::InstanceState::new(app.handle()); + + // Migrate legacy data if needed + if let Err(e) = core::instance::migrate_legacy_data(app.handle(), &instance_state) { + eprintln!("[Startup] Warning: Failed to migrate legacy data: {}", e); + } + + app.manage(instance_state); + // Load saved account on startup let app_dir = app.path().app_data_dir().unwrap(); let storage = core::account_storage::AccountStorage::new(app_dir); @@ -2176,7 +2283,16 @@ fn main() { assistant_chat, assistant_chat_stream, list_ollama_models, - list_openai_models + list_openai_models, + // Instance management commands + create_instance, + delete_instance, + update_instance, + list_instances, + get_instance, + set_active_instance, + get_active_instance, + duplicate_instance ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 651d26b..c9ac368 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -1,5 +1,5 @@ -pub mod zip; pub mod path; +pub mod zip; // File system related utility functions #[allow(dead_code)] diff --git a/src-tauri/src/utils/path.rs b/src-tauri/src/utils/path.rs index deaebb5..1db6e5b 100644 --- a/src-tauri/src/utils/path.rs +++ b/src-tauri/src/utils/path.rs @@ -59,7 +59,7 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> { } } } - + // If still not found after PATH search, return specific error if !path.exists() { return Err( @@ -80,7 +80,7 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> { // Canonicalize and strip UNC prefix for clean path let canonical = std::fs::canonicalize(&path) .map_err(|e| format!("Failed to resolve Java path '{}': {}", path.display(), e))?; - + Ok(strip_unc_prefix(canonical)) } @@ -98,7 +98,7 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> { } } } - + // If still not found after PATH search, return specific error if !path.exists() { return Err( @@ -119,7 +119,7 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> { // Canonicalize to resolve symlinks and get absolute path let canonical = std::fs::canonicalize(&path) .map_err(|e| format!("Failed to resolve Java path '{}': {}", path.display(), e))?; - + Ok(strip_unc_prefix(canonical)) } @@ -196,23 +196,23 @@ mod tests { fn test_normalize_with_temp_file() { // Create a temporary file to test with an actual existing path let temp_dir = std::env::temp_dir(); - + #[cfg(target_os = "windows")] let temp_file = temp_dir.join("test_java_normalize.exe"); #[cfg(not(target_os = "windows"))] let temp_file = temp_dir.join("test_java_normalize"); - + // Create the file if let Ok(mut file) = fs::File::create(&temp_file) { let _ = file.write_all(b"#!/bin/sh\necho test"); drop(file); - + // Test normalization let result = normalize_java_path(temp_file.to_str().unwrap()); - + // Clean up let _ = fs::remove_file(&temp_file); - + // Verify result assert!(result.is_ok(), "Failed to normalize temp file path"); let normalized = result.unwrap(); @@ -227,12 +227,12 @@ mod tests { let unc_path = PathBuf::from(r"\\?\C:\Windows\System32\cmd.exe"); let stripped = strip_unc_prefix(unc_path); assert_eq!(stripped.to_string_lossy(), r"C:\Windows\System32\cmd.exe"); - + let normal_path = PathBuf::from(r"C:\Windows\System32\cmd.exe"); let unchanged = strip_unc_prefix(normal_path.clone()); assert_eq!(unchanged, normal_path); } - + #[cfg(not(target_os = "windows"))] { let path = PathBuf::from("/usr/bin/java"); |