waybetter/crates/waybetter-host/src/alerts_commands.rs

78 lines
3.4 KiB
Rust

//! Keyword alert settings, native side, for every app that sets [`crate::app::AppConfig::alerts`]:
//! the shell-only `alerts_get` / `alerts_set` ([`crate::commands::ALERTS`]) over the owner's
//! [`AlertSettings`] (saved in app data, validated by [`crate::alerts`]). Site tabs gain no command
//! here: native code hands the page runtime the keywords to watch ([`script`]) at every page load
//! and whenever they change. What a match looks like and the notification are the app's.
use crate::alerts::AlertSettings;
use crate::app::config;
use crate::host::{eval_in_site_tabs, read_json, shell_only, write_json};
use crate::tabs::deliver_script;
use std::sync::{Mutex, MutexGuard};
use tauri::{AppHandle, Manager, Webview};
pub use crate::alerts::AlertsConfig;
struct AlertState(Mutex<AlertSettings>);
/// Loads the saved settings. Does nothing unless the app set [`crate::app::AppConfig::alerts`].
pub(crate) fn init(app: &AppHandle) {
let Some(alerts) = config(app).alerts else { return };
let settings = read_json::<AlertSettings>(app, alerts.file).and_then(|settings| settings.validated().ok()).unwrap_or_default();
app.manage(AlertState(Mutex::new(settings)));
}
fn state(app: &AppHandle) -> Result<MutexGuard<'_, AlertSettings>, String> {
app.try_state::<AlertState>()
.ok_or("DATABASE: alert state unavailable; restart safely")?
.inner()
.0
.lock()
.map_err(|_| "DATABASE: alert state unavailable; restart safely".into())
}
/// The owner's alert settings.
pub fn settings(app: &AppHandle) -> Result<AlertSettings, String> {
Ok(state(app)?.clone())
}
/// The script that hands the page runtime the keywords to watch (empty = alerts off).
pub fn script(app: &AppHandle) -> String {
let (Some(alerts), Ok(settings)) = (config(app).alerts, settings(app)) else { return String::new() };
script_for(app, alerts, &settings)
}
fn script_for(app: &AppHandle, alerts: &AlertsConfig, settings: &AlertSettings) -> String {
let keywords = serde_json::to_string(settings.active_keywords()).unwrap_or_else(|_| "[]".into());
deliver_script(config(app).site.runtime_global, alerts.member, "set", alerts.queue, &keywords)
}
/// Validates, saves and keeps the settings, and does not touch the tabs: for the app's own
/// writers (a backup import), which then evaluate [`script`] themselves.
pub fn store(app: &AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
let alerts = config(app).alerts.ok_or_else(|| "UNAVAILABLE: this app has no alerts".to_owned())?;
let settings = settings.validated()?;
// The lock is held across the write so concurrent saves cannot leave disk and memory disagreeing.
let mut current = state(app)?;
write_json(app, alerts.file, &settings)?;
*current = settings.clone();
Ok(settings)
}
#[tauri::command]
pub(crate) async fn alerts_get(webview: Webview, app: AppHandle) -> Result<AlertSettings, String> {
shell_only(&webview)?;
settings(&app)
}
#[tauri::command]
pub(crate) async fn alerts_set(webview: Webview, app: AppHandle, settings: AlertSettings) -> Result<AlertSettings, String> {
shell_only(&webview)?;
let settings = store(&app, settings)?;
eval_in_site_tabs(&app, config(&app).site, &script(&app));
Ok(settings)
}
pub(crate) fn handler() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send + Sync + 'static {
tauri::generate_handler![alerts_get, alerts_set]
}