80 lines
3.3 KiB
Rust
80 lines
3.3 KiB
Rust
//! 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<Vault>);
|
|
|
|
/// 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<dyn std::error::Error>> {
|
|
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<MutexGuard<'_, Vault>, String> {
|
|
app.try_state::<VaultState>()
|
|
.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<Vec<Draft>, String> {
|
|
shell_only(&webview)?;
|
|
lock(&app)?.list_drafts()
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn draft_save(webview: Webview, app: AppHandle, input: SaveDraftInput) -> Result<Draft, String> {
|
|
shell_only(&webview)?;
|
|
lock(&app)?.save_draft(&input)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn draft_revisions(webview: Webview, app: AppHandle, draft_id: String) -> Result<Vec<DraftRevision>, 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<Draft, String> {
|
|
shell_only(&webview)?;
|
|
lock(&app)?.set_status(&id, expected_revision, &status)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn observation_list(webview: Webview, app: AppHandle) -> Result<Vec<Observation>, String> {
|
|
shell_only(&webview)?;
|
|
lock(&app)?.observations()
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub(crate) async fn data_export(webview: Webview, app: AppHandle) -> Result<ExportData, String> {
|
|
shell_only(&webview)?;
|
|
lock(&app)?.export()
|
|
}
|
|
|
|
pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
|
|
tauri::generate_handler![draft_list, draft_save, draft_revisions, draft_status, observation_list, data_export]
|
|
}
|