//! App state: the open [`Catalog`] and fuzzy [`NameIndex`], plus the db paths. use std::path::{Path, PathBuf}; use mtgc_core::{Catalog, NameIndex}; use serde::{Deserialize, Serialize}; /// Small app-shell preferences persisted across restarts (distinct from the /// user's library data) — currently just which library file was last open. #[derive(Default, Serialize, Deserialize)] struct PersistedConfig { library_path: Option, } fn config_path(data_dir: &Path) -> PathBuf { data_dir.join("config.json") } fn load_config(data_dir: &Path) -> PersistedConfig { std::fs::read_to_string(config_path(data_dir)) .ok() .and_then(|s| serde_json::from_str(&s).ok()) .unwrap_or_default() } fn save_config(data_dir: &Path, cfg: &PersistedConfig) { if let Ok(s) = serde_json::to_string_pretty(cfg) { let _ = std::fs::write(config_path(data_dir), s); } } /// Managed application state, guarded by a `Mutex` in Tauri's state store. pub struct AppState { data_dir: PathBuf, pub library_path: PathBuf, pub reference_path: PathBuf, pub catalog: Option, pub index: Option, /// Set while `update_card_data` is running; `cancel_update_card_data` /// flips it to abort the in-progress ingest. pub ingest_cancel: Option, } impl AppState { /// Initialize with the last-opened library path (or the default under the /// app data dir, on first run) and open the catalog if a reference db /// already exists. pub fn new(data_dir: PathBuf) -> Self { let cfg = load_config(&data_dir); let library_path = cfg .library_path .unwrap_or_else(|| data_dir.join("library.db")); let mut s = Self { reference_path: data_dir.join("reference.db"), library_path, data_dir, catalog: None, index: None, ingest_cancel: None, }; // Best-effort open; failures just leave the catalog unloaded. let _ = s.reopen(); s } /// Point at a different library file and persist it so the next launch /// reopens the same one. pub fn set_library_path(&mut self, path: PathBuf) { self.library_path = path.clone(); save_config( &self.data_dir, &PersistedConfig { library_path: Some(path), }, ); } /// (Re)open the catalog + rebuild the name index. Only opens when a reference /// db exists — attaching a non-existent file would create an empty (tableless) /// db and make every query fail. pub fn reopen(&mut self) -> Result<(), String> { if self.reference_path.exists() { let catalog = Catalog::open(&self.library_path, &self.reference_path) .map_err(|e| e.to_string())?; let index = catalog.build_name_index().map_err(|e| e.to_string())?; self.index = Some(index); self.catalog = Some(catalog); } else { self.catalog = None; self.index = None; } Ok(()) } pub fn catalog(&self) -> Result<&Catalog, String> { self.catalog .as_ref() .ok_or_else(|| "Card data not loaded yet — click 'Download card data'.".to_string()) } pub fn index(&self) -> Result<&NameIndex, String> { self.index .as_ref() .ok_or_else(|| "Card data not loaded yet — click 'Download card data'.".to_string()) } }