use std::path::PathBuf; use std::sync::OnceLock; use tauri::Manager; /// Portable mode support for Handy. /// /// When a file named `portable` exists next to the executable, all user data /// (settings, models, recordings, database, logs) is stored in a `Data/` /// directory alongside the executable instead of `%APPDATA%`. static PORTABLE_DATA_DIR: OnceLock> = OnceLock::new(); /// Detect portable mode by looking for a `portable` marker file next to the exe. /// Must be called once at startup before Tauri initializes. pub fn init() { PORTABLE_DATA_DIR.get_or_init(|| { let exe_path = std::env::current_exe().ok()?; let exe_dir = exe_path.parent()?; if exe_dir.join("portable").exists() { let data_dir = exe_dir.join("Data"); if !data_dir.exists() { std::fs::create_dir_all(&data_dir).ok()?; } eprintln!("[portable] data dir: {}", data_dir.display()); Some(data_dir) } else { None } }); } /// Returns `true` if running in portable mode. pub fn is_portable() -> bool { PORTABLE_DATA_DIR.get().and_then(|v| v.as_ref()).is_some() } /// Get the portable data dir (if active). Does not require an AppHandle. /// Returns `None` when not in portable mode. pub fn data_dir() -> Option<&'static PathBuf> { PORTABLE_DATA_DIR.get().and_then(|v| v.as_ref()) } /// Portable-aware replacement for `app.path().app_data_dir()`. pub fn app_data_dir(app: &tauri::AppHandle) -> Result { if let Some(dir) = data_dir() { Ok(dir.clone()) } else { app.path().app_data_dir() } } /// Portable-aware replacement for `app.path().app_log_dir()`. pub fn app_log_dir(app: &tauri::AppHandle) -> Result { if let Some(dir) = data_dir() { Ok(dir.join("logs")) } else { app.path().app_log_dir() } } /// Resolve a relative path against the app data directory (portable-aware). /// Replaces `app.path().resolve(path, BaseDirectory::AppData)`. pub fn resolve_app_data(app: &tauri::AppHandle, relative: &str) -> Result { Ok(app_data_dir(app)?.join(relative)) } /// Get the path to use with `tauri-plugin-store`. /// Returns an absolute path in portable mode (so the store plugin writes to /// the portable Data dir) or the original relative path otherwise. pub fn store_path(relative: &str) -> PathBuf { if let Some(dir) = data_dir() { dir.join(relative) } else { PathBuf::from(relative) } }