File Watching in Rust with notify-rs — Hot Folders for a Sync App
Source: Dev.to
Basic setup
[dependencies]
notify = "6"
use notify::{RecommendedWatcher, RecursiveMode, Watcher, Config};
use std::sync::mpsc;
fn watch_directory(path: &str) -> Result {
let (tx, rx) = mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
for res in rx {
match res {
Ok(event) => handle_event(event),
Err(e) => log::error!("Watch error: {:?}", e),
}
}
Ok(())
}
RecommendedWatcher uses FSEvents on macOS – the native file‑system event API. It has low overhead and fast notification.
The double‑fire problem
File‑save operations often trigger multiple events (e.g., Modify, Modify, Create, Modify). You typically want a single sync trigger, not four.
Debounce
use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::path::PathBuf;
struct Debouncer {
last_seen: HashMap,
delay: Duration,
}
impl Debouncer {
fn should_process(&mut self, path: &PathBuf) -> bool {
let now = Instant::now();
let last = self
.last_seen
.entry(path.clone())
.or_insert(Instant::now() - self.delay * 2);
if now.duration_since(*last) >= self.delay {
*last = now;
true
} else {
false
}
}
}
A 300–500 ms debounce window covers most editor save behaviors without feeling slow.
Filtering what to watch
Not every file change should trigger a sync. Example filter:
fn should_ignore(path: &Path) -> bool {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
// Hidden files
name.starts_with('.')
// Temp files
|| name.ends_with(".tmp")
|| name.ends_with('~')
// macOS metadata
|| name == ".DS_Store"
// Files already being synced
|| name.ends_with(".sync")
}
Running the watcher in Tauri
The watcher must run in a background thread so it doesn’t block the Tokio runtime:
std::thread::spawn(move || {
if let Err(e) = watch_directory(&path) {
log::error!("Watcher failed: {:?}", e);
}
});
Communicate back to Tauri via channels or the app handle’s emit system.
The verdict
notify‑rs with FSEvents on macOS is solid. The double‑fire problem requires debouncing—build it in from the start. Filter aggressively to avoid triggering on irrelevant changes.
If this was useful, a ❤️ helps more than you’d think — thanks!
Hiyoko PDF Vault →
X →