use std::path::{Path, PathBuf};
use std::sync::Arc;
use liwe::graph::Graph;
use liwe::model::Key;
use notify::{Event, EventKind, RecursiveMode, Watcher};
use tokio::sync::Mutex;
fn path_to_key(path: &Path, base_path: &Path) -> Option<Key> {
if path.extension().is_none_or(|ext| ext != "md") {
return None;
}
let relative = path.strip_prefix(base_path).ok()?;
let key_str = relative
.with_extension("")
.to_string_lossy()
.to_string();
Some(Key::name(&key_str))
}
pub fn start(graph: Arc<Mutex<Graph>>, base_path: PathBuf) {
start_with_config(graph, base_path, None);
}
pub fn start_with_config(
graph: Arc<Mutex<Graph>>,
base_path: PathBuf,
config: Option<notify::Config>,
) {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Event>();
let config = config.unwrap_or_default();
let mut watcher = notify::RecommendedWatcher::new(
move |res: notify::Result<Event>| {
if let Ok(event) = res {
let _ = tx.send(event);
}
},
config,
)
.expect("filesystem watcher");
watcher
.watch(&base_path, RecursiveMode::Recursive)
.expect("watch directory");
tokio::spawn(async move {
let _watcher = watcher;
while let Some(event) = rx.recv().await {
handle_event(&graph, &base_path, event).await;
}
});
}
pub fn start_polling(
graph: Arc<Mutex<Graph>>,
base_path: PathBuf,
interval: std::time::Duration,
) {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Event>();
let config = notify::Config::default()
.with_poll_interval(interval)
.with_compare_contents(true);
let mut watcher = notify::PollWatcher::new(
move |res: notify::Result<Event>| {
if let Ok(event) = res {
let _ = tx.send(event);
}
},
config,
)
.expect("poll watcher");
watcher
.watch(&base_path, RecursiveMode::Recursive)
.expect("watch directory");
tokio::spawn(async move {
let _watcher = watcher;
while let Some(event) = rx.recv().await {
handle_event(&graph, &base_path, event).await;
}
});
}
async fn handle_event(graph: &Arc<Mutex<Graph>>, base_path: &Path, event: Event) {
for path in &event.paths {
let Some(key) = path_to_key(path, base_path) else {
continue;
};
match event.kind {
EventKind::Create(_) | EventKind::Modify(_) => {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
};
tracing::debug!("file changed: {} -> key={}", path.display(), key);
let mut g = graph.lock().await;
g.update_document(key, content);
}
EventKind::Remove(_) => {
tracing::debug!("file removed: {} -> key={}", path.display(), key);
let mut g = graph.lock().await;
g.remove_document(key);
}
_ => {}
}
}
}