// file: crates/ksp-app-config-desk/src/app_state.rs // version: 9 //! Shared backend state owned by the Tauri application. /// Shared Config Desk application state managed by Tauri. pub(crate) struct AppState { config_management: ksp_config_lib::ConfigManagement, logging_runtime: std::sync::Mutex, splash_settings: crate::SplashSettings, splash_sequence_started: std::sync::atomic::AtomicBool, } impl AppState { /// Initializes Config ownership, the Logging runtime and durable application state. pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result { let config_management = crate::config_management(arguments); let config_management = match config_management { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let runtime_identity = crate::launch_identity(); let runtime_identity = match runtime_identity { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let logging_startup = crate::initialize_logging(&config_management, &runtime_identity); let logging_startup = match logging_startup { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let splash_settings = crate::SplashSettings::load(); let splash_settings = match splash_settings { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { ksp_logging_lib::warn!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, error_domain = error.code().domain(), error_code = error.code().code(), "managed splash timings are invalid; using transient in-memory defaults" ); crate::SplashSettings::fallback() }, }; ksp_logging_lib::debug!( target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WINDOWS, minimum_ms = splash_settings.minimum_ms(), minimum_source = splash_settings.minimum_source(), fade_in_ms = splash_settings.fade_in_ms(), fade_in_source = splash_settings.fade_in_source(), fade_out_ms = splash_settings.fade_out_ms(), fade_out_source = splash_settings.fade_out_source(), expected_backend_lifecycle_ms = splash_settings.expected_backend_lifecycle_ms(), "resolved Config Desk splash timings" ); return std::result::Result::Ok(Self { config_management, logging_runtime: std::sync::Mutex::new(LoggingRuntimeState { guard: logging_startup.guard, active_profile_id: logging_startup.active_profile_id, selection_source: logging_startup.selection_source, generation: 1, fallback_active: logging_startup.fallback_active, startup_diagnostic: logging_startup.startup_diagnostic, }), splash_settings, splash_sequence_started: std::sync::atomic::AtomicBool::new(false), }); } /// Builds a safe initial frontend snapshot without exposing resolved secret values or arbitrary error context. pub(crate) fn snapshot(&self) -> ksp_core_lib::Result { let document_count = self.config_management.engine().registry().descriptors().count(); let document_count = u32::try_from(document_count); let document_count = match document_count { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_APP_STATE_INVALID, "Config registry contains too many descriptors for the desktop DTO") .with_source(error), ); }, }; let runtime = self.logging_runtime.lock(); let runtime = match runtime { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Config Desk Logging runtime state lock is poisoned", )); }, }; let _keep_guard_alive = &runtime.guard; return std::result::Result::Ok(crate::AppSnapshotDto { application_version: env!("CARGO_PKG_VERSION").to_owned(), config_document_count: document_count, active_logging_profile: runtime.active_profile_id.clone(), logging_generation: runtime.generation, fallback_logging_active: runtime.fallback_active, startup_diagnostic: runtime.startup_diagnostic.clone(), }); } /// Returns the Config management facade owned by the application state. #[must_use] pub(crate) const fn config_management(&self) -> &ksp_config_lib::ConfigManagement { return &self.config_management; } /// Replaces the active Logging runtime with freshly resolved settings and advances the runtime generation only after success. pub(crate) fn reinitialize_logging_runtime( &self, profile_id: &str, selection_source: &str, settings: &ksp_logging_lib::LoggingSettings, ) -> ksp_core_lib::Result { let runtime = self.logging_runtime.lock(); let mut runtime = match runtime { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Config Desk Logging runtime state lock is poisoned", )); }, }; let reinitialized = ksp_logging_lib::reinitialize(&mut runtime.guard, settings); if let std::result::Result::Err(error) = reinitialized { return std::result::Result::Err(error); } runtime.active_profile_id = std::option::Option::Some(profile_id.to_owned()); runtime.selection_source = selection_source.to_owned(); runtime.generation = runtime.generation.saturating_add(1); runtime.fallback_active = false; runtime.startup_diagnostic = std::option::Option::None; return std::result::Result::Ok(runtime.generation); } /// Returns a safe observable snapshot of the currently active Logging runtime. pub(crate) fn logging_runtime_status(&self) -> ksp_core_lib::Result { let runtime = self.logging_runtime.lock(); let runtime = match runtime { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_LOCK_FAILED, "Config Desk Logging runtime state lock is poisoned", )); }, }; let identity = runtime.guard.runtime_identity(); let identity = match identity { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_INVALID, "Config Desk Logging runtime is missing its application launch identity", )); }, }; let console_enabled = match runtime.guard.settings().console() { std::option::Option::Some(console) => console.enabled(), std::option::Option::None => false, }; let dropped = runtime.guard.dropped_lines(); let mut files = std::vec::Vec::::new(); for metadata in runtime.guard.active_file_outputs() { files.push(crate::project_logging_file(&metadata)); } return std::result::Result::Ok(crate::LoggingRuntimeStatusDto { active_profile: runtime.active_profile_id.clone(), selection_source: runtime.selection_source.clone(), generation: runtime.generation, fallback_active: runtime.fallback_active, application_id: identity.application_id().to_owned(), launch_timestamp: identity.launch_timestamp().to_owned(), console_enabled, dropped_console_lines: crate::count_to_u64(dropped.console()), dropped_file_lines: crate::count_to_u64(dropped.file()), dropped_total_lines: crate::count_to_u64(dropped.total()), files, }); } /// Returns the resolved splash timings captured during application bootstrap. #[must_use] pub(crate) const fn splash_settings(&self) -> crate::SplashSettings { return self.splash_settings; } /// Marks the one-shot splash lifecycle as started and reports whether this caller won the transition. pub(crate) fn begin_splash_sequence(&self) -> bool { return self .splash_sequence_started .compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire) .is_ok(); } } struct LoggingRuntimeState { guard: ksp_logging_lib::LoggingGuard, active_profile_id: std::option::Option, selection_source: String, generation: u32, fallback_active: bool, startup_diagnostic: std::option::Option, }