use std::path::PathBuf; use crate::types::ModelId; #[derive(Debug, Clone, PartialEq, Eq)] pub struct AppPaths { app_data_dir: PathBuf, } impl AppPaths { pub fn current() -> Self { Self { app_data_dir: resolve_app_data_dir(), } } pub fn app_data_dir(&self) -> PathBuf { self.app_data_dir.clone() } pub fn database_path(&self) -> PathBuf { self.app_data_dir.join("magnotia.db") } pub fn recordings_dir(&self) -> PathBuf { self.app_data_dir.join("recordings") } pub fn crashes_dir(&self) -> PathBuf { self.app_data_dir.join("crashes") } pub fn logs_dir(&self) -> PathBuf { self.app_data_dir.join("logs") } pub fn diagnostic_reports_dir(&self) -> PathBuf { self.app_data_dir.join("diagnostic-reports") } pub fn models_dir(&self) -> PathBuf { self.app_data_dir.join("models") } pub fn speech_model_dir(&self, id: &ModelId) -> PathBuf { self.models_dir().join(id.as_str()) } pub fn llm_models_dir(&self) -> PathBuf { self.models_dir().join("llm") } pub fn migration_sentinel(&self, name: &str) -> PathBuf { self.app_data_dir.join(format!(".{name}.sentinel")) } } pub fn app_paths() -> AppPaths { AppPaths::current() } pub fn app_data_dir() -> PathBuf { app_paths().app_data_dir() } fn resolve_app_data_dir() -> PathBuf { #[cfg(target_os = "windows")] { let local_app_data = std::env::var("LOCALAPPDATA").unwrap_or_else(|_| ".".to_string()); return PathBuf::from(local_app_data).join("magnotia"); } #[cfg(target_os = "macos")] { let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); return PathBuf::from(home) .join("Library") .join("Application Support") .join("Magnotia"); } #[cfg(target_os = "linux")] { let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); let legacy = PathBuf::from(&home).join(".magnotia"); if legacy.exists() { return legacy; } if let Ok(xdg) = std::env::var("XDG_DATA_HOME") { if !xdg.is_empty() { return PathBuf::from(xdg).join("magnotia"); } } PathBuf::from(home) .join(".local") .join("share") .join("magnotia") } #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] { let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); PathBuf::from(home).join(".magnotia") } } #[cfg(test)] mod tests { use super::AppPaths; use crate::types::ModelId; use std::path::PathBuf; #[test] fn derives_all_paths_from_one_base() { let paths = AppPaths { app_data_dir: PathBuf::from("/tmp/magnotia-test"), }; assert_eq!( paths.database_path(), PathBuf::from("/tmp/magnotia-test/magnotia.db") ); assert_eq!( paths.speech_model_dir(&ModelId::new("whisper-base-en")), PathBuf::from("/tmp/magnotia-test/models/whisper-base-en") ); assert_eq!( paths.llm_models_dir(), PathBuf::from("/tmp/magnotia-test/models/llm") ); } }