Something went wrong. Try again.
My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495//! Watches the vault for zettels edited outside the daemon (e.g. by hand in//! an editor), re-indexes them and publishes the change, so clients see//! those edits too. Adapted from `Deimos::watch`, which feeds the old TUI.
use std::path::Path;
use color_eyre::eyre::{Context as _, Result};use fil_core::{ZettelId, frontmatter::FrontMatter};use notify::{ Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _, event::ModifyKind,};use tracing::{debug, error, info};
use super::{FilState, event::zettel_changes};
/// Runs until the watcher's channel closes; spawn it alongside the server.////// # Errors////// Returns an error if the vault can't be watched.pub async fn watch(state: FilState) -> Result<()> { let (tx, mut rx) = tokio::sync::mpsc::channel::<notify::Result<Event>>(64); // keep the watcher alive for as long as the loop runs let mut watcher = RecommendedWatcher::new( move |res| { let _ = tx.blocking_send(res); }, Config::default(), )?;
let root = state.kh.read().await.root.clone(); watcher .watch(&root, RecursiveMode::Recursive) .with_context(|| format!("failed to watch {}", root.display()))?; info!("watching {} for outside edits", root.display());
while let Some(res) = rx.recv().await { let Ok(event) = res.inspect_err(|e| error!("watcher error: {e:?}")) else { continue; };
// editors either write in place or write elsewhere and rename over let relevant = matches!( event.kind, EventKind::Create(_) | EventKind::Modify(ModifyKind::Data(_) | ModifyKind::Name(_)) ); if !relevant { continue; }
for path in event.paths.iter().filter(|path| is_zettel_file(path)) { reindex(&state, path).await; } }
Ok(())}
/// Only existing markdown files can be zettels; this also skips the/// database, which changes on every write the daemon makes.fn is_zettel_file(path: &Path) -> bool { path.extension().is_some_and(|ext| ext == "md") && path.exists()}
async fn reindex(state: &FilState, path: &Path) { let Ok(zid) = ZettelId::try_from(path.to_path_buf()) else { debug!("not a zettel: {}", path.display()); return; };
let mut kt = state.kh.write().await;
// the daemon's own writes come back as events too; those files already // match the index, so there is nothing to re-index or publish let unchanged = kt.index.zods().get(&zid).is_some_and(|zod| { FrontMatter::extract_from_file(path) .is_ok_and(|(fm, body)| fm == zod.fm && body == zod.body) }); if unchanged { drop(kt); return; }
if let Err(e) = kt.process_path(path).await { drop(kt); error!("failed to re-index {}: {e}", path.display()); return; } // fails for e.g. a markdown file that was never added as a zettel if let Ok(changes) = zettel_changes(&zid, &kt).await { state.publish(&mut kt, changes); } drop(kt);}