filesentry 0.1.0

robust (recursive) file watcher
Documentation
use std::mem::take;
use std::sync::atomic;

use crate::pending::PendingChanges;
use crate::tree::{FileTree, NodeId};
use crate::{EventDebouncer, Watcher};

pub struct Worker {
    pending_changes: PendingChanges,
    events: EventDebouncer,
    work_stack: Vec<(NodeId, usize)>,
    tree: FileTree,
    roots: Vec<(NodeId, bool)>,
    watcher: Watcher,
}

impl Watcher {
    fn should_wakeup(&self) -> bool {
        self.state.has_notifications.load(atomic::Ordering::Relaxed) || self.notify.is_shutdown()
    }
}

impl Worker {
    pub fn new(watcher: Watcher) -> Self {
        Worker {
            pending_changes: PendingChanges::default(),
            events: EventDebouncer::new(),
            work_stack: Vec::with_capacity(16),
            tree: FileTree::new(),
            roots: Vec::with_capacity(16),
            watcher,
        }
    }

    fn wait_for_changes(&mut self) -> bool {
        if self.events.is_empty() {
            self.watcher
                .notify
                .changes
                .take(&mut self.pending_changes, || self.watcher.should_wakeup());
            false
        } else {
            self.watcher.notify.changes.take_timeout(
                &mut self.pending_changes,
                self.watcher.state.config.lock().unwrap().settle_time,
                || self.watcher.should_wakeup(),
            )
        }
    }

    fn process_notifications(&mut self) {
        let has_notifications = self
            .watcher
            .state
            .has_notifications
            .swap(false, atomic::Ordering::Relaxed);
        if has_notifications {
            let notifications = take(&mut *self.watcher.state.notifications.lock().unwrap());
            for root in notifications.roots {
                let Some(node) = self.tree.add_root(root.path.clone(), root.recursive) else {
                    (root.notify)(true);
                    continue;
                };
                let crawl = self
                    .watcher
                    .state
                    .config
                    .lock()
                    .unwrap()
                    .make_walk_builder(root.path.as_std_path());
                if let Err(err) = self.watcher.notify.watch_dir(root.path.clone()) {
                    log::error!("faild to watch {:?}: {err}", root.path);
                    (root.notify)(false);
                    continue;
                }
                self.tree.crawl_root(crawl, root.recursive, |path| {
                    if let Err(err) = self.watcher.notify.watch_dir(path.clone()) {
                        log::error!("faild to watch {path:?}: {err}")
                    }
                });
                let i = self
                    .roots
                    .partition_point(|&(it, _)| self.tree[it].path < root.path);
                if root.recursive {
                    // for recursive roots remove any roots that are children
                    // to avoid duplicate crawls
                    let end = self.roots[i..]
                        .iter()
                        .position(|&(it, _)| !root.path.is_parent_of(&self.tree[it].path))
                        .unwrap_or(self.roots.len());
                    self.roots.splice(i..end, [(node, root.recursive)]);
                } else {
                    self.roots.insert(i, (node, root.recursive));
                };
                (root.notify)(true);
            }
        }
    }

    pub fn run(mut self) {
        loop {
            let setteled = self.wait_for_changes();
            if self.watcher.notify.is_shutdown() {
                break;
            }
            self.process_notifications();
            if setteled {
                let events = self.events.take();
                self.watcher
                    .state
                    .config
                    .lock()
                    .unwrap()
                    .handlers
                    .retain_mut(|handler| handler(events.clone()));
                continue;
            }
            if self.pending_changes.take_recrawl() {
                #[cfg(test)]
                self.watcher
                    .state
                    .recrawls
                    .fetch_add(1, atomic::Ordering::Relaxed);

                for &(root, _) in &self.roots {
                    let walk_builder = self
                        .watcher
                        .state
                        .config
                        .lock()
                        .unwrap()
                        .make_walk_builder(self.tree[root].path.as_std_path());
                    self.tree.crawl(
                        root,
                        walk_builder,
                        &mut self.work_stack,
                        |path, ty| self.events.add(path, ty),
                        |path| {
                            if let Err(err) = self.watcher.notify.watch_dir(path.clone()) {
                                log::error!("faild to watch {path:?}: {err}")
                            }
                        },
                    );
                }
                continue;
            }
            self.tree.apply_transaction(
                &mut self.pending_changes,
                |path, ty| self.events.add(path, ty),
                &mut self.work_stack,
                |path| {
                    self.watcher
                        .state
                        .config
                        .lock()
                        .unwrap()
                        .make_walk_builder(path)
                },
                |path| {
                    if let Err(err) = self.watcher.notify.watch_dir(path.clone()) {
                        log::error!("faild to watch {path:?}: {err}")
                    }
                },
            );
        }
    }
}