use super::{Change, ChangeKind, RootKind, roots::is_watched};
use notify::{
Event,
event::{EventKind, ModifyKind, RenameMode},
};
use std::collections::{HashMap, hash_map::Entry};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{Receiver, TryRecvError};
pub(super) fn lead_with_desync(
root: &Path,
desynced: bool,
mut changes: Vec<Change>,
) -> Vec<Change> {
if desynced {
changes.insert(
0,
Change {
path: root.to_path_buf(),
kind: ChangeKind::Desynced,
},
);
}
changes
}
pub(super) fn drain(
rx: &Receiver<notify::Result<Event>>,
raw: &mut Vec<(PathBuf, EventKind)>,
) -> bool {
let mut desynced = false;
loop {
match rx.try_recv() {
Ok(Ok(event)) => desynced |= ingest(event, raw),
Ok(Err(_)) => desynced = true,
Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
}
}
desynced
}
fn ingest(event: Event, raw: &mut Vec<(PathBuf, EventKind)>) -> bool {
if event.need_rescan() {
return true;
}
let paths = event.paths;
let kind = event.kind;
if let EventKind::Modify(ModifyKind::Name(RenameMode::Both)) = kind
&& let [from, to] = paths.as_slice()
{
raw.push((
from.clone(),
EventKind::Modify(ModifyKind::Name(RenameMode::From)),
));
raw.push((
to.clone(),
EventKind::Modify(ModifyKind::Name(RenameMode::To)),
));
return false;
}
for path in paths {
raw.push((path, kind));
}
false
}
pub(super) fn coalesce(
repo_root: &Path,
root_kind: RootKind,
raw: Vec<(PathBuf, EventKind)>,
) -> Vec<Change> {
let mut renamed: HashMap<PathBuf, bool> = HashMap::new();
let mut order: Vec<PathBuf> = Vec::new();
for (path, kind) in raw {
if !is_watched(root_kind, repo_root, &path) {
continue;
}
let is_rename = matches!(kind, EventKind::Modify(ModifyKind::Name(_)));
match renamed.entry(path.clone()) {
Entry::Occupied(mut e) => *e.get_mut() |= is_rename,
Entry::Vacant(e) => {
order.push(path);
e.insert(is_rename);
}
}
}
order
.into_iter()
.filter_map(|p| {
let change_kind = classify(renamed.get(&p).copied().unwrap_or(false), &p)?;
Some(Change {
path: p,
kind: change_kind,
})
})
.collect()
}
fn classify(renamed: bool, path: &Path) -> Option<ChangeKind> {
if path.exists() {
Some(ChangeKind::Touched)
} else if renamed {
None
} else {
Some(ChangeKind::Removed)
}
}
#[cfg(test)]
mod tests;