Files
khadhroony-bot3/kb-app-demo-desktop/src/app_state.rs
2026-07-25 21:04:56 +02:00

182 lines
7.3 KiB
Rust

// file: kb-app-demo-desktop/src/app_state.rs
// version: 4
//! Shared Tauri application state and startup initialization.
/// Shared state managed by Tauri for the desktop demo application.
pub(crate) struct AppState {
config_path: std::string::String,
app_config: kb_config::AppConfig,
active_profile: kb_config::ProfileConfig,
logging_guard: std::sync::Mutex<kb_logging::LoggingGuard>,
http_pool: kb_onchain_transport::HttpEndpointPool,
ws_pool: std::sync::Mutex<std::option::Option<std::sync::Arc<kb_onchain_transport::WsEndpointPool>>>,
demo_ws_session: tokio::sync::Mutex<std::option::Option<std::sync::Arc<kb_onchain_transport::WsSession>>>,
demo_backfill_running: std::sync::atomic::AtomicBool,
demo_backfill_cancel_requested: std::sync::atomic::AtomicBool,
}
impl crate::AppState {
/// Initializes configuration, logging and shared runtime state.
pub(crate) fn initialize() -> kb_core::Result<crate::AppState> {
let config_path = resolve_config_path();
let app_config = match kb_config::read_config_json_file(&config_path) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let active_profile = match kb_config::active_profile(&app_config) {
std::result::Result::Ok(profile) => profile.clone(),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let logging_config = convert_logging_config(&active_profile.logging);
let logging_guard = match kb_logging::init_logging(&logging_config) {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&active_profile) {
std::result::Result::Ok(pool) => pool,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::AppState {
config_path: config_path.display().to_string(),
app_config,
active_profile,
logging_guard: std::sync::Mutex::new(logging_guard),
http_pool,
ws_pool: std::sync::Mutex::new(std::option::Option::None),
demo_ws_session: tokio::sync::Mutex::new(std::option::Option::None),
demo_backfill_running: std::sync::atomic::AtomicBool::new(false),
demo_backfill_cancel_requested: std::sync::atomic::AtomicBool::new(false),
});
}
/// Returns the path used to load the configuration file.
pub(crate) fn config_path(&self) -> &str {
return self.config_path.as_str();
}
/// Returns the complete parsed application configuration.
pub(crate) fn app_config(&self) -> &kb_config::AppConfig {
return &self.app_config;
}
/// Returns the active profile selected from the configuration.
pub(crate) fn active_profile(&self) -> &kb_config::ProfileConfig {
return &self.active_profile;
}
/// Returns the configured HTTP endpoint pool.
pub(crate) fn http_pool(&self) -> &kb_onchain_transport::HttpEndpointPool {
return &self.http_pool;
}
/// Returns the lazily initialized WebSocket endpoint pool.
pub(crate) fn demo_ws_pool(
&self,
) -> kb_core::Result<std::sync::Arc<kb_onchain_transport::WsEndpointPool>> {
let lock_result = self.ws_pool.lock();
let mut guard = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(kb_core::Error::invalid_state(
"demo WebSocket pool lock is poisoned",
));
},
};
if let std::option::Option::Some(pool) = guard.as_ref() {
return std::result::Result::Ok(std::sync::Arc::clone(pool));
}
let pool = match kb_onchain_transport::WsEndpointPool::from_profile(&self.active_profile) {
std::result::Result::Ok(value) => std::sync::Arc::new(value),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
*guard = std::option::Option::Some(std::sync::Arc::clone(&pool));
return std::result::Result::Ok(pool);
}
/// Returns the persistent WebSocket demo session slot.
pub(crate) fn demo_ws_session(
&self,
) -> &tokio::sync::Mutex<
std::option::Option<std::sync::Arc<kb_onchain_transport::WsSession>>,
> {
return &self.demo_ws_session;
}
/// Returns the single-campaign execution flag used by the backfill window.
pub(crate) fn demo_backfill_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_backfill_running;
}
/// Returns the cooperative cancellation flag used by the backfill window.
pub(crate) fn demo_backfill_cancel_requested(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_backfill_cancel_requested;
}
/// Returns the number of logging routes held by the logging guard.
pub(crate) fn logging_route_count(&self) -> usize {
let lock_result = self.logging_guard.lock();
let guard = match lock_result {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(_) => return 0,
};
return guard.route_count();
}
}
fn convert_logging_config(config: &kb_config::LoggingConfig) -> kb_logging::LoggingConfig {
let mut targets = std::vec::Vec::<kb_logging::LogTargetConfig>::new();
for target in &config.targets {
targets.push(kb_logging::LogTargetConfig {
name: target.name.clone(),
enabled: target.enabled,
sink: target.sink.clone(),
level: target.level.clone(),
path: target.path.clone(),
rotation: target.rotation.clone(),
format: target.format.clone(),
ansi: target.ansi,
targets: target.targets.clone(),
});
}
let mut target_filters = std::vec::Vec::<kb_logging::LogTargetFilterConfig>::new();
for filter in &config.target_filters {
target_filters.push(kb_logging::LogTargetFilterConfig {
target: filter.target.clone(),
level: filter.level.clone(),
});
}
return kb_logging::LoggingConfig {
default_level: config.default_level.clone(),
targets,
target_filters,
};
}
fn resolve_config_path() -> std::path::PathBuf {
let configured = std::env::var("KB_CONFIG_PATH").ok();
return resolve_config_path_from_value(configured.as_deref());
}
fn resolve_config_path_from_value(value: std::option::Option<&str>) -> std::path::PathBuf {
if let std::option::Option::Some(configured) = value {
let trimmed = configured.trim();
if !trimmed.is_empty() {
return std::path::PathBuf::from(trimmed);
}
}
return crate::workspace_root_dir().join("config/example.config.json");
}
#[cfg(test)]
mod tests {
#[test]
fn resolve_config_path_uses_workspace_example_by_default() {
let path = super::resolve_config_path_from_value(std::option::Option::None);
assert_eq!(
path.file_name().and_then(std::ffi::OsStr::to_str),
std::option::Option::Some("example.config.json")
);
}
}