#![allow(clippy::manual_map)]
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use sysinfo::{System, SystemExt};
use tauri::AppHandle;
use tokio::runtime::Runtime;
use super::events::EventManager;
use super::types::{AppSettings, BatchJob, MetricsSnapshot, Notification};
use super::{
ActivityLogger, BackendManager, ModelDownloadManager, ModelRepositoryService, SecurityManager,
};
use crate::gpu::{GpuConfiguration, GpuManager};
pub struct AppState {
pub system: Arc<Mutex<System>>,
pub gpu_manager: Arc<GpuManager>,
pub backend_manager: Arc<BackendManager>,
pub metrics: Arc<Mutex<MetricsSnapshot>>,
pub activity_logger: Arc<ActivityLogger>,
pub settings: Arc<Mutex<AppSettings>>,
pub notifications: Arc<Mutex<Vec<Notification>>>,
pub batch_jobs: Arc<Mutex<Vec<BatchJob>>>,
pub security_manager: Arc<SecurityManager>,
pub event_manager: Arc<Mutex<Option<EventManager>>>,
pub model_repository: Arc<ModelRepositoryService>,
pub download_manager: Arc<ModelDownloadManager>,
}
impl AppState {
pub async fn new(app_handle: Option<AppHandle>) -> Result<Self, String> {
let mut system = System::new_all();
system.refresh_all();
let settings = Self::load_settings().await.unwrap_or_default();
const MAX_ACTIVITY_LOGS: usize = 500;
let activity_logger = Arc::new(ActivityLogger::new(MAX_ACTIVITY_LOGS));
let models_dir = PathBuf::from(&settings.models_directory);
let backend_manager = Arc::new(
BackendManager::with_models_dir(Arc::clone(&activity_logger), models_dir)
.await
.map_err(|e| format!("Failed to initialize backend manager: {}", e))?,
);
let gpu_manager = Arc::new(GpuManager::new(GpuConfiguration {
enabled: settings.prefer_gpu,
preferred_vendor: None,
..Default::default()
}));
if let Err(err) = gpu_manager.initialize().await {
tracing::warn!(
error = ?err,
"Failed to initialize GPU manager; GPU metrics will be unavailable"
);
}
let security_manager = Arc::new(SecurityManager::new(()));
let model_repository = Arc::new(ModelRepositoryService::new());
let download_manager = Arc::new(ModelDownloadManager::new());
let event_manager = if let Some(handle) = app_handle {
Some(EventManager::new(handle))
} else {
None
};
Ok(Self {
system: Arc::new(Mutex::new(system)),
gpu_manager,
backend_manager,
metrics: Arc::new(Mutex::new(MetricsSnapshot::default())),
activity_logger,
settings: Arc::new(Mutex::new(settings)),
notifications: Arc::new(Mutex::new(Vec::new())),
batch_jobs: Arc::new(Mutex::new(Vec::new())),
security_manager,
event_manager: Arc::new(Mutex::new(event_manager)),
model_repository,
download_manager,
})
}
async fn load_settings() -> Result<AppSettings, String> {
let config_path = Self::get_config_path();
if let Ok(contents) = tokio::fs::read_to_string(&config_path).await {
if let Ok(settings) = serde_json::from_str::<AppSettings>(&contents) {
return Ok(settings);
}
}
Ok(AppSettings::default())
}
fn get_config_path() -> PathBuf {
PathBuf::from(".").join("inferno-settings.json")
}
pub fn init_event_manager(&self, app_handle: AppHandle) {
let mut event_mgr = self
.event_manager
.lock()
.expect("Failed to lock event manager");
*event_mgr = Some(EventManager::new(app_handle));
}
pub async fn shutdown(&self) -> Result<(), String> {
let settings = self
.settings
.lock()
.map_err(|e| format!("Failed to lock settings: {}", e))?;
let config_path = Self::get_config_path();
let contents = serde_json::to_string_pretty(&*settings)
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
tokio::fs::write(&config_path, contents)
.await
.map_err(|e| format!("Failed to write settings file: {}", e))?;
let loaded_models = self.backend_manager.get_loaded_models();
for model_id in loaded_models {
let _ = self.backend_manager.unload_model(model_id).await;
}
self.gpu_manager.shutdown().await;
Ok(())
}
}
impl Default for AppState {
fn default() -> Self {
Runtime::new()
.expect("Failed to create Tokio runtime for AppState::default")
.block_on(Self::new(None))
.expect("Failed to initialize AppState")
}
}