|
| 1 | +//! # ApplicationState Struct |
| 2 | +//! |
| 3 | +//! Defines the main `ApplicationState` struct, which is the central, shared, |
| 4 | +//! thread-safe state container for the entire Mountain application. It is |
| 5 | +//! managed by Tauri and is accessible to all command handlers and Environment |
| 6 | +//! providers. |
| 7 | +
|
| 8 | +use std::{ |
| 9 | + collections::HashMap, |
| 10 | + path::{Path, PathBuf}, |
| 11 | + sync::{ |
| 12 | + Arc, |
| 13 | + Mutex as StandardMutex, |
| 14 | + atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering as AtomicOrdering}, |
| 15 | + }, |
| 16 | +}; |
| 17 | + |
| 18 | +use log::{error, info, warn}; |
| 19 | +use tauri::{AppHandle, Manager, Runtime}; |
| 20 | + |
| 21 | +use super::{DTO::*, Internal}; |
| 22 | +use crate::Environment::CommandProvider::CommandHandler; // TODO: Fix this path after refactor |
| 23 | + |
| 24 | +/// The central, shared, thread-safe state for the entire Mountain application. |
| 25 | +/// |
| 26 | +/// This struct consolidates all dynamic state required by the backend, from |
| 27 | +/// workspace information and configuration to the state of active UI components |
| 28 | +/// like terminals and WebViews. |
| 29 | +#[derive(Clone)] |
| 30 | +pub struct ApplicationState { |
| 31 | + // --- Workspace State --- |
| 32 | + pub WorkspaceFolders:Arc<StandardMutex<Vec<WorkspaceFolderStateDTO>>>, |
| 33 | + pub WorkspaceConfigurationPath:Arc<StandardMutex<Option<PathBuf>>>, |
| 34 | + pub IsTrusted:Arc<AtomicBool>, |
| 35 | + pub WindowState:Arc<StandardMutex<WindowStateDTO>>, |
| 36 | + |
| 37 | + // --- Configuration & Storage --- |
| 38 | + pub Configuration:Arc<StandardMutex<MergedConfigurationStateDTO>>, |
| 39 | + pub GlobalMemento:Arc<StandardMutex<HashMap<String, serde_json::Value>>>, |
| 40 | + pub GlobalMementoPath:PathBuf, |
| 41 | + pub WorkspaceMemento:Arc<StandardMutex<HashMap<String, serde_json::Value>>>, |
| 42 | + pub WorkspaceMementoPath:Arc<StandardMutex<Option<PathBuf>>>, |
| 43 | + |
| 44 | + // --- Extension & Provider Management --- |
| 45 | + pub CommandRegistry:Arc<StandardMutex<HashMap<String, CommandHandler<Runtime>>>>, |
| 46 | + pub LanguageProviders:Arc<StandardMutex<HashMap<u32, ProviderRegistrationDTO>>>, |
| 47 | + pub NextProviderHandle:Arc<AtomicU32>, |
| 48 | + pub ScannedExtensions:Arc<StandardMutex<HashMap<String, ExtensionDescriptionStateDTO>>>, |
| 49 | + pub EnabledProposedAPIs:Arc<StandardMutex<HashMap<String, Vec<String>>>>, |
| 50 | + pub ExtensionScanPaths:Arc<StandardMutex<Vec<PathBuf>>>, |
| 51 | + |
| 52 | + // --- Feature-specific State --- |
| 53 | + pub DiagnosticsMap:Arc<StandardMutex<HashMap<String, HashMap<String, Vec<MarkerDataDTO>>>>>, |
| 54 | + pub OpenDocuments:Arc<StandardMutex<HashMap<String, DocumentStateDTO>>>, |
| 55 | + pub OutputChannels:Arc<StandardMutex<HashMap<String, OutputChannelStateDTO>>>, |
| 56 | + pub ActiveTerminals:Arc<StandardMutex<HashMap<u64, Arc<StandardMutex<TerminalStateDTO>>>>>, |
| 57 | + pub NextTerminalIdentifier:Arc<AtomicU64>, |
| 58 | + pub ActiveWebViews:Arc<StandardMutex<HashMap<String, WebViewStateDTO>>>, |
| 59 | + pub ActiveCustomDocuments:Arc<StandardMutex<HashMap<String, CustomDocumentStateDTO>>>, |
| 60 | + pub ActiveStatusBarItems:Arc<StandardMutex<HashMap<String, Common::StatusBar::DTO::StatusBarEntryDTO>>>, |
| 61 | + pub ActiveTreeViews:Arc<StandardMutex<HashMap<String, TreeViewStateDTO>>>, |
| 62 | + |
| 63 | + // --- IPC & User Interface State --- |
| 64 | + pub PendingUserInterfaceRequests: Arc< |
| 65 | + StandardMutex< |
| 66 | + HashMap<String, tokio::sync::oneshot::Sender<Result<serde_json::Value, Common::Error::CommonError>>>, |
| 67 | + >, |
| 68 | + >, |
| 69 | +} |
| 70 | + |
| 71 | +impl Default for ApplicationState { |
| 72 | + fn default() -> Self { |
| 73 | + info!("[ApplicationState] Initializing default application state..."); |
| 74 | + let ApplicationNameForPaths = env!("CARGO_PKG_NAME"); |
| 75 | + let ApplicationDataDirectoryPath = |
| 76 | + dirs::config_dir().map(|p| p.join(ApplicationNameForPaths)).unwrap_or_else(|| { |
| 77 | + warn!( |
| 78 | + "[ApplicationState] Could not get config dir. Using relative path '.{}-appdata'.", |
| 79 | + ApplicationNameForPaths |
| 80 | + ); |
| 81 | + PathBuf::from(format!(".{}-appdata", ApplicationNameForPaths)) |
| 82 | + }); |
| 83 | + |
| 84 | + // This must be synchronous because the async runtime isn't available yet. |
| 85 | + if !ApplicationDataDirectoryPath.exists() { |
| 86 | + if let Err(e) = std::fs::create_dir_all(&ApplicationDataDirectoryPath) { |
| 87 | + error!( |
| 88 | + "[ApplicationState] CRITICAL: Failed to create app data directory at '{}': {}.", |
| 89 | + ApplicationDataDirectoryPath.display(), |
| 90 | + e |
| 91 | + ); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + let GlobalMementoFilePath = Internal::ResolveMementoStorageFilePath(&ApplicationDataDirectoryPath, true, ""); |
| 96 | + let InitialGlobalMementoMap = Internal::LoadInitialMementoFromDisk(&GlobalMementoFilePath); |
| 97 | + // let InitialCommandRegistryMap = |
| 98 | + // crate::Handler::Command::RegisterNativeCommands(); // TODO: Re-integrate |
| 99 | + |
| 100 | + info!("[ApplicationState] Default state initialization complete."); |
| 101 | + Self { |
| 102 | + WorkspaceFolders:Arc::new(StandardMutex::new(Vec::new())), |
| 103 | + WorkspaceConfigurationPath:Arc::new(StandardMutex::new(None)), |
| 104 | + IsTrusted:Arc::new(AtomicBool::new(false)), |
| 105 | + WindowState:Arc::new(StandardMutex::new(Default::default())), |
| 106 | + Configuration:Arc::new(StandardMutex::new(MergedConfigurationStateDTO::default())), |
| 107 | + GlobalMemento:Arc::new(StandardMutex::new(InitialGlobalMementoMap)), |
| 108 | + GlobalMementoPath:GlobalMementoFilePath, |
| 109 | + WorkspaceMemento:Arc::new(StandardMutex::new(HashMap::new())), |
| 110 | + WorkspaceMementoPath:Arc::new(StandardMutex::new(None)), |
| 111 | + CommandRegistry:Arc::new(StandardMutex::new(HashMap::new())), // TODO: Use InitialCommandRegistryMap |
| 112 | + DiagnosticsMap:Arc::new(StandardMutex::new(HashMap::new())), |
| 113 | + OpenDocuments:Arc::new(StandardMutex::new(HashMap::new())), |
| 114 | + OutputChannels:Arc::new(StandardMutex::new(HashMap::new())), |
| 115 | + LanguageProviders:Arc::new(StandardMutex::new(HashMap::new())), |
| 116 | + NextProviderHandle:Arc::new(AtomicU32::new(1)), |
| 117 | + ScannedExtensions:Arc::new(StandardMutex::new(HashMap::new())), |
| 118 | + EnabledProposedAPIs:Arc::new(StandardMutex::new(HashMap::new())), |
| 119 | + ExtensionScanPaths:Arc::new(StandardMutex::new(Vec::new())), |
| 120 | + ActiveTerminals:Arc::new(StandardMutex::new(HashMap::new())), |
| 121 | + NextTerminalIdentifier:Arc::new(AtomicU64::new(1)), |
| 122 | + PendingUserInterfaceRequests:Arc::new(StandardMutex::new(HashMap::new())), |
| 123 | + ActiveWebViews:Arc::new(StandardMutex::new(HashMap::new())), |
| 124 | + ActiveCustomDocuments:Arc::new(StandardMutex::new(HashMap::new())), |
| 125 | + ActiveStatusBarItems:Arc::new(StandardMutex::new(HashMap::new())), |
| 126 | + ActiveTreeViews:Arc::new(StandardMutex::new(HashMap::new())), |
| 127 | + } |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +impl ApplicationState { |
| 132 | + /// Generates a unique, filesystem-safe identifier string for the current |
| 133 | + /// workspace. |
| 134 | + pub fn GetWorkspaceIdentifier(&self) -> Result<String, String> { |
| 135 | + let LockErrorMapper = |e| format!("[AppState] Lock error: {}", e); |
| 136 | + let ConfigurationPathGuard = self.WorkspaceConfigurationPath.lock().map_err(LockErrorMapper)?; |
| 137 | + if let Some(ConfigurationPath) = ConfigurationPathGuard.as_ref() { |
| 138 | + return Ok(ConfigurationPath.file_name().unwrap_or_default().to_string_lossy().into_owned()); |
| 139 | + } |
| 140 | + drop(ConfigurationPathGuard); |
| 141 | + |
| 142 | + let FoldersGuard = self.WorkspaceFolders.lock().map_err(LockErrorMapper)?; |
| 143 | + if let Some(FirstFolder) = FoldersGuard.first() { |
| 144 | + let PathString = FirstFolder.URI.path(); |
| 145 | + // Create a more stable hash for the identifier. |
| 146 | + let Hash = format!("{:x}", md5::compute(PathString)); |
| 147 | + return Ok(format!( |
| 148 | + "{}-{}", |
| 149 | + FirstFolder.Name.replace(|c:char| !c.is_alphanumeric(), "_"), |
| 150 | + &Hash[..8] |
| 151 | + )); |
| 152 | + } |
| 153 | + |
| 154 | + Ok("NO_WORKSPACE".to_string()) |
| 155 | + } |
| 156 | + |
| 157 | + /// Returns the next available unique identifier for a language provider |
| 158 | + /// registration. |
| 159 | + pub fn GetNextProviderHandle(&self) -> u32 { self.NextProviderHandle.fetch_add(1, AtomicOrdering::Relaxed) } |
| 160 | + |
| 161 | + /// Returns the next available unique identifier for a terminal instance. |
| 162 | + pub fn GetNextTerminalIdentifier(&self) -> u64 { self.NextTerminalIdentifier.fetch_add(1, AtomicOrdering::Relaxed) } |
| 163 | + |
| 164 | + /// Updates the path to the workspace memento file and reloads its content |
| 165 | + /// from disk. |
| 166 | + pub fn UpdateWorkspaceMementoPathAndReload(&self, ApplicationDataDirectory:&Path) -> Result<(), String> { |
| 167 | + let LockErrorMapper = |e| format!("[AppState] Lock error: {}", e); |
| 168 | + let WorkspaceIdentifier = self.GetWorkspaceIdentifier()?; |
| 169 | + let mut PathGuard = self.WorkspaceMementoPath.lock().map_err(LockErrorMapper)?; |
| 170 | + |
| 171 | + if WorkspaceIdentifier == "NO_WORKSPACE" { |
| 172 | + if PathGuard.is_some() { |
| 173 | + *PathGuard = None; |
| 174 | + self.WorkspaceMemento.lock().map_err(LockErrorMapper)?.clear(); |
| 175 | + } |
| 176 | + return Ok(()); |
| 177 | + } |
| 178 | + |
| 179 | + let NewMementoPath = |
| 180 | + Internal::ResolveMementoStorageFilePath(ApplicationDataDirectory, false, &WorkspaceIdentifier); |
| 181 | + if PathGuard.as_ref() != Some(&NewMementoPath) { |
| 182 | + if let Some(Parent) = NewMementoPath.parent() { |
| 183 | + if !Parent.exists() { |
| 184 | + std::fs::create_dir_all(Parent).map_err(|e| e.to_string())?; |
| 185 | + } |
| 186 | + } |
| 187 | + *PathGuard = Some(NewMementoPath.clone()); |
| 188 | + let NewMementoContent = Internal::LoadInitialMementoFromDisk(&NewMementoPath); |
| 189 | + *self.WorkspaceMemento.lock().map_err(LockErrorMapper)? = NewMementoContent; |
| 190 | + } |
| 191 | + Ok(()) |
| 192 | + } |
| 193 | +} |
0 commit comments