//! The drafts vault, native side, for every app that sets [`crate::app::AppConfig::vault`]: opens the //! app's [`Vault`] in app data at launch and serves the shell-only `draft_list`, `draft_save`, //! `draft_revisions`, `draft_status`, `observation_list` and `data_export` ([`crate::commands::VAULT`]). //! A write is acknowledged only after its SQLite transaction committed. The app's own native code (its //! capture, a popover that reads drafts, a backup import) takes the same vault through [`lock`]; what //! it does with it is the app's. use crate::app::config; use crate::host::{data_file, shell_only}; use crate::vault::{Draft, DraftRevision, ExportData, Observation, SaveDraftInput, Vault, VaultRules}; use std::sync::{Mutex, MutexGuard}; use tauri::{AppHandle, Manager, Webview}; /// What an app declares about its vault: the database file in app data and its rules. #[derive(Clone, Copy, Debug)] pub struct VaultConfig { pub file: &'static str, pub rules: &'static VaultRules, } struct VaultState(Mutex); /// Opens the database. Does nothing unless the app set [`crate::app::AppConfig::vault`]. A database /// that cannot be opened (a newer schema, a damaged file) stops the launch rather than starting empty. pub(crate) fn init(app: &AppHandle) -> Result<(), Box> { let Some(vault) = config(app).vault else { return Ok(()) }; let path = data_file(app, vault.file).ok_or("STORAGE: no application data directory")?; app.manage(VaultState(Mutex::new(Vault::open(path, vault.rules).map_err(std::io::Error::other)?))); Ok(()) } /// The vault, locked. Hold it only for the call that needs it; never across a webview call. pub fn lock(app: &AppHandle) -> Result, String> { app.try_state::() .ok_or("DATABASE: vault unavailable; restart safely")? .inner() .0 .lock() .map_err(|_| "DATABASE: workspace state unavailable; restart safely".into()) } #[tauri::command] pub(crate) async fn draft_list(webview: Webview, app: AppHandle) -> Result, String> { shell_only(&webview)?; lock(&app)?.list_drafts() } #[tauri::command] pub(crate) async fn draft_save(webview: Webview, app: AppHandle, input: SaveDraftInput) -> Result { shell_only(&webview)?; lock(&app)?.save_draft(&input) } #[tauri::command] pub(crate) async fn draft_revisions(webview: Webview, app: AppHandle, draft_id: String) -> Result, String> { shell_only(&webview)?; lock(&app)?.revisions(&draft_id) } #[tauri::command] pub(crate) async fn draft_status(webview: Webview, app: AppHandle, id: String, expected_revision: i64, status: String) -> Result { shell_only(&webview)?; lock(&app)?.set_status(&id, expected_revision, &status) } #[tauri::command] pub(crate) async fn observation_list(webview: Webview, app: AppHandle) -> Result, String> { shell_only(&webview)?; lock(&app)?.observations() } #[tauri::command] pub(crate) async fn data_export(webview: Webview, app: AppHandle) -> Result { shell_only(&webview)?; lock(&app)?.export() } pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + Sync + 'static { tauri::generate_handler![draft_list, draft_save, draft_revisions, draft_status, observation_list, data_export] }