filesentry 0.1.0

robust (recursive) file watcher
Documentation
use std::ffi::OsStr;
use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool};
use std::{io, thread};

mod sys;

use hashbrown::DefaultHashBuilder;
use mio::{Poll, Waker};
use papaya::HashMap;

use crate::inotify::sys::{EventFlags, Inotify, Watch};
use crate::path::CannonicalPathBuf;
use crate::pending::{self, PendingChangesLock};

#[derive(Debug)]
pub(crate) struct InotifyWatcher {
    waker: mio::Waker,
    shutdown: AtomicBool,
    notify: Inotify,
    watches: HashMap<Watch, CannonicalPathBuf, DefaultHashBuilder>,
    pub changes: PendingChangesLock,
}

impl InotifyWatcher {
    pub fn shutdown(&self) {
        self.shutdown.store(true, atomic::Ordering::Relaxed);
        let _ = self.waker.wake();
        self.changes.notify();
    }

    pub fn is_shutdown(&self) -> bool {
        self.shutdown.load(atomic::Ordering::Relaxed)
    }

    pub fn new(#[cfg(test)] slow: bool) -> io::Result<Arc<Self>> {
        let mut poll = Poll::new()?;
        let waker = Waker::new(poll.registry(), sys::MESSAGE)?;
        let watcher = Arc::new(Self {
            waker,
            notify: Inotify::new()?,
            watches: HashMap::with_capacity_and_hasher(1024, DefaultHashBuilder::default()),
            changes: PendingChangesLock::default(),
            shutdown: AtomicBool::new(false),
        });
        let watcher_ = watcher.clone();
        thread::spawn(move || {
            watcher_.notify.event_loop(
                &mut poll,
                |path, event /* , timestamp */| {
                    watcher_.handle_event(path, event /* , timestamp */)
                },
                || watcher_.changes.notify(),
                || true,
                #[cfg(test)]
                slow,
            )
        });
        Ok(watcher)
    }

    pub fn watch_dir(&self, path: CannonicalPathBuf) -> io::Result<()> {
        let watch = self.notify.add_directory_watch(&*path)?;
        self.watches.pin().insert(watch, path);
        Ok(())
    }

    fn handle_event(&self, child: &OsStr, event: &sys::Event /*, timestamp: SystemTime */) {
        // need to recrawl everything anyway if the queue overflowed
        if event.flags.contains(EventFlags::Q_OVERFLOW) {
            self.changes.lock().recrawl();
            return;
        }
        let watches = self.watches.pin();
        let Some(dir) = watches.get(&event.wd) else {
            if event.wd.is_invalid()
                || event
                    .flags
                    .intersects(EventFlags::MOVE_SELF | EventFlags::IGNORED)
            {
                self.changes.lock().recrawl();
            }
            return;
        };

        let watch_deleted = event.flags.intersects(
            EventFlags::IGNORED
                | EventFlags::MOVE_SELF
                | EventFlags::DELETE_SELF
                | EventFlags::UNMOUNT,
        );
        if child.is_empty() || watch_deleted {
            if event.flags.contains(EventFlags::IGNORED) {
                watches.remove(&event.wd);
            }
            let path = dir.clone();
            self.changes
                .lock()
                .add_watcher(path, /* timestamp, */ pending::Flags::empty());
        } else {
            let path = dir.join(child);
            let mut pending = self.changes.lock();
            if event
                .flags
                .intersects(EventFlags::CREATE | EventFlags::DELETE)
            {
                pending.add_watcher(
                    path,
                    /* timestamp, */ pending::Flags::NEEDS_RECURSIVE_CRAWL,
                );
            } else {
                pending.add_watcher(path, /* timestamp, */ pending::Flags::empty());
            }
        }
    }
}