diff options
Diffstat (limited to 'src-tauri/src/core')
| -rw-r--r-- | src-tauri/src/core/instance.rs | 325 | ||||
| -rw-r--r-- | src-tauri/src/core/mod.rs | 1 |
2 files changed, 326 insertions, 0 deletions
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; |