notify-rs / notify

🔭 Cross-platform filesystem notification library for Rust.
https://docs.rs/notify
2.58k stars 207 forks source link

Include watched path in Event when watching multiple paths recursively #609

Open Javex opened 1 week ago

Javex commented 1 week ago

When watching multiple directories recursively, I would like to know which base path an event belongs to. For example with this setup:

pub fn watch(watches: Vec<&Path>, tx: Sender<MetricMessage>) -> Result<RecommendedWatcher> {
    let watches_matcher: Vec<PathBuf> = watches.iter().map(|p| PathBuf::from(p)).collect();
    let mut watcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| {
        handle_notify_event(&watches_matcher, &tx, res)
    })?;
    for watch in watches {
        watcher.watch(watch, RecursiveMode::Recursive)?;
    }

    Ok(watcher)
}

If I supply multiple paths and then handle the resulting events, I don't know which watch those were for. In my use case, I want to count events by base path to measure how busy they are.

Right now I do this:

    for base in base_paths {
        for event_path in paths.iter() {
            if event_path.starts_with(base) {
                return Ok(base.to_string_lossy().to_string());
            }
        }
    }

If there is a lot of events and many monitored paths this could get quite expensive. I noticed that inotify returns a WatchDescriptor which is contained in an Event. A similar approach would be great here, where the watcher returns some form of handle or descriptor that can be attached to each event.

This would have to be a separate descriptor that tracks which recursively created watches belong to which "root watch" as the event.wd descriptor used at https://github.com/notify-rs/notify/blob/main/notify/src/inotify.rs#L216 is some leaf node. So in the recursive case the original path would have to be tracked here where the information is currently "lost".

I think another wrinkle is the fact that the closure has to be specified upon calling INotifyWatcher::new which would make it difficult to pass any subsequent descriptors created by watcher.watch back to that closure. It would be great to have a separate allocation e.g. via builder pattern where I specify all my watches first, get handles back and then pass in the closure which can track those handles. Of course, this could all abstracted away by passing through the root path as part of the event rather than dealing with generic descriptors.

Is this something worth implementing?

0xpr03 commented 1 week ago

Please note that you have to make an equal proposal for handling this on all other backends (fsevent,kqueue,windows,pollwatcher) before this could proceed to a MR.