home
NOTE: Under Construction - I'm in the middle of upgrading my site and lots of stuff is kinda broken. Please forgive the mess.

A Simple Debouncer For Rust's notify File Watcher

July 2023

This is the initial Rust code I set up for my static site generator to rebuild the site when files change.

Code
```cargo
[dependencies]
notify = "6.1.1"
notify-debouncer-full = "0.3.1"
```

use notify::{RecursiveMode, Watcher};
use notify_debouncer_full::new_debouncer;
use std::path::PathBuf;
use std::time::Duration;
use std::time::SystemTime;

pub fn main() {
    let path = PathBuf::from("/Users/alan/Desktop");
    let _ = watch_files(path);
}

fn watch_files(path: PathBuf) -> notify::Result<()> {
    let (tx, rx) = std::sync::mpsc::channel();
    let mut debouncer = new_debouncer(Duration::from_millis(500), None, tx)?;
    debouncer.watcher().watch(&path, RecursiveMode::Recursive)?;
    debouncer.cache().add_root(&path, RecursiveMode::Recursive);
    let mut last_update = SystemTime::now();
    let debounce_time = Duration::from_secs(4);
    for _result in rx {
        if last_update.elapsed().unwrap() > debounce_time {
            do_update();
            last_update = SystemTime::now();
        }
    }

    Ok(())
}

fn do_update() {
    dbg!("Change detected");
}
Results
Process took to long and was halted

Details

I shipped the first version of my static site generator. I've very happy with it and with being able to post again. Now that it builds it needs to build without me pressing buttons every time. To make that happen I'm using Rust's notify crate.

The notify create watches the file system and triggers when events happen. There's a debounce feature to help prevent spamming but it only works on individual files. So, if you have 1,400 files update on your website it would trigger 1,400 builds.

Oops.

I added a simple debouncer on top on the one that comes with notify to prevent that. It checks a time stamp to see if more than 4 seconds have passed since that last build when a file change is detected. If that's the case, it sends the signal to build and updates the time stamp. Otherwise it does nothing.

I spent half a day trying to build a more sophisticated method. Then it clicked that I really don't need anything more that this.

  • I could probably pull out the notify debouncer. I think mine would cover anything that changed. I'll look into that if I refactor and setup to cover files that change in after the first one.

  • It would be better if the trigger for the rebuild happened at the end of the file updates. Otherwise the build may happen while files are still updating.

    The reason that matters is because the process I'm currently using to copy files to my site from my grimoire sends all 1,400 every time.

  • Another option would be to add a delay to the build process so it takes a bit of time to start after receiving the signal. Something else to look into.

  • Though, the ideal would be to figure out how to setup a timer for the debounce directly. That's what I spent the day trying to do but I don't know enough rust yet to make that happen.

  • I'm making a new version of this with some more logic in it. Specifically, if I edit a single file, only that file will be rebuilt. If I edit a template, it updates all the files. I'll write that one up too.

Keep these for writing up the larger one that wathces specifci files

// https://doc.rust-lang.org/std/time/struct.SystemTime.html // https://docs.rs/notify/latest/notify/event/enum.EventKind.html // https://docs.rs/notify/latest/notify/event/enum.ModifyKind.html

See also: [TODO: Make tlinks work] which uses _mini instead of _full. I'm probably not going to use it that much since any access to a file with _mini triggers an event and genearlly that's not what I want. I usually after create or update and sometimes delete

═══ § ═══

Footnotes And References