mod fold;
mod hub;
mod roots;
pub use roots::RootKind;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Receiver;
use notify::{Event, event::EventKind};
use fold::{coalesce, drain, lead_with_desync};
#[derive(Debug, thiserror::Error)]
#[error("filesystem watcher: {0}")]
pub struct WatchError(#[from] notify::Error);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChangeKind {
Touched,
Removed,
Desynced,
}
type Identity = (u64, u64);
fn identity_of(path: &Path) -> Option<Identity> {
let meta = std::fs::metadata(path).ok()?;
Some((meta.dev(), meta.ino()))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Change {
pub path: PathBuf,
pub kind: ChangeKind,
}
pub struct Watcher {
repo_root: PathBuf,
kind: RootKind,
armed: Option<Identity>,
rx: Receiver<notify::Result<Event>>,
}
impl Drop for Watcher {
fn drop(&mut self) {
hub::disarm(&self.repo_root);
}
}
impl Watcher {
pub fn new(repo_root: &Path) -> Result<Self, WatchError> {
Self::with_kind(repo_root, RootKind::Workspace)
}
pub fn with_kind(repo_root: &Path, kind: RootKind) -> Result<Self, WatchError> {
let repo_root = repo_root.canonicalize().map_err(notify::Error::from)?;
let rx = hub::arm(&repo_root)?;
let armed = identity_of(&repo_root);
Ok(Self {
repo_root,
kind,
armed,
rx,
})
}
pub fn is_stale(&self) -> bool {
identity_of(&self.repo_root) != self.armed
}
pub fn tick(&self) -> Vec<Change> {
let mut raw: Vec<(PathBuf, EventKind)> = Vec::new();
let desynced = drain(&self.rx, &mut raw);
let changes = coalesce(&self.repo_root, self.kind, raw);
lead_with_desync(&self.repo_root, desynced, changes)
}
}
#[cfg(test)]
mod tests;