feat: add Tauri event hook and lore.db file watcher
Adds real-time updates when lore syncs new GitLab data by watching the lore.db file for changes. React hook (src/hooks/useTauriEvents.ts): - useTauriEvent(): Subscribe to a single Tauri event with auto-cleanup - useTauriEvents(): Subscribe to multiple events with a handler map - Typed payloads for each event type: - global-shortcut-triggered: toggle-window | quick-capture - lore-data-changed: void (refresh trigger) - sync-status: started | completed | failed - error-notification: code + message Rust watcher (src-tauri/src/watcher.rs): - Watches lore's data directory for lore.db modifications - Uses notify crate with 2-second poll interval - Emits "lore-data-changed" event to frontend on file change - Handles atomic writes by watching parent directory - Gracefully handles missing lore.db (logs warning, skips watcher) Test coverage: - Hook subscription and cleanup behavior - Focus store test fix: clear localStorage before each test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
85
src-tauri/src/watcher.rs
Normal file
85
src-tauri/src/watcher.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
//! File watcher for lore.db changes
|
||||
//!
|
||||
//! Watches the lore database file for modifications and emits events
|
||||
//! to trigger frontend refresh when lore syncs new data.
|
||||
|
||||
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
/// Get the path to lore's database file
|
||||
fn lore_db_path() -> Option<PathBuf> {
|
||||
dirs::data_local_dir().map(|d| d.join("lore").join("lore.db"))
|
||||
}
|
||||
|
||||
/// Start watching lore.db for changes.
|
||||
///
|
||||
/// When the file is modified, emits a "lore-data-changed" event to the frontend.
|
||||
/// Returns the watcher handle (must be kept alive for watching to continue).
|
||||
pub fn start_lore_watcher(app: AppHandle) -> Option<RecommendedWatcher> {
|
||||
let db_path = lore_db_path()?;
|
||||
|
||||
// Check if the file exists
|
||||
if !db_path.exists() {
|
||||
tracing::warn!("lore.db not found at {:?}, skipping file watcher", db_path);
|
||||
return None;
|
||||
}
|
||||
|
||||
let parent_dir = db_path.parent()?;
|
||||
|
||||
// Create a channel for receiving events
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// Create the watcher with a debounce of 1 second
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
move |res: Result<Event, notify::Error>| {
|
||||
if let Ok(event) = res {
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
},
|
||||
Config::default().with_poll_interval(Duration::from_secs(2)),
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
// Watch the lore directory (not just the file, for atomic write detection)
|
||||
if let Err(e) = watcher.watch(parent_dir, RecursiveMode::NonRecursive) {
|
||||
tracing::error!("Failed to start lore.db watcher: {}", e);
|
||||
return None;
|
||||
}
|
||||
|
||||
tracing::info!("Watching lore.db for changes at {:?}", db_path);
|
||||
|
||||
// Spawn a thread to handle events
|
||||
std::thread::spawn(move || {
|
||||
for event in rx {
|
||||
// Only react to modify events on the actual db file
|
||||
if matches!(
|
||||
event.kind,
|
||||
EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)
|
||||
) {
|
||||
let affects_db = event.paths.iter().any(|p| p.ends_with("lore.db"));
|
||||
if affects_db {
|
||||
tracing::debug!("lore.db changed, emitting refresh event");
|
||||
let _ = app.emit("lore-data-changed", ());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Some(watcher)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_lore_db_path_returns_expected_location() {
|
||||
let path = lore_db_path();
|
||||
assert!(path.is_some());
|
||||
let path = path.unwrap();
|
||||
assert!(path.ends_with("lore/lore.db"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user