Watch Files with Debounced Events

Receive filesystem notifications through notify, keep the watcher alive with a handle, and collapse rapid events into one batch.

RustData Integrity

Code

snippet
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::Path;
use std::sync::mpsc::{channel, Receiver};
use std::time::{Duration, Instant};

struct WatchHandle {
    _watcher: RecommendedWatcher,
    events: Receiver<Event>,
}

fn watch(path: &Path) -> notify::Result<WatchHandle> {
    let (sender, events) = channel();
    let mut watcher = RecommendedWatcher::new(
        move |result: notify::Result<Event>| {
            if let Ok(event) = result {
                let _ = sender.send(event);
            }
        },
        Config::default(),
    )?;
    watcher.watch(path, RecursiveMode::Recursive)?;
    Ok(WatchHandle {
        _watcher: watcher,
        events,
    })
}

fn main() -> notify::Result<()> {
    let handle = watch(Path::new("."))?;
    let started = Instant::now();
    let mut last = Instant::now();
    let mut pending = false;
    let mut count = 0;

    loop {
        match handle.events.recv_timeout(Duration::from_millis(200)) {
            Ok(event) => {
                last = Instant::now();
                pending = true;
                count += 1;
                println!("raw event: {:?}", event.kind);
            }
            Err(_) if pending && last.elapsed() >= Duration::from_millis(500) => {
                println!("debounced batch of {count} events ready");
                break;
            }
            Err(_) if !pending && started.elapsed() >= Duration::from_secs(2) => {
                println!("no changes observed");
                break;
            }
            Err(_) => {}
        }
    }
    Ok(())
}