use std::path::{Component, Path};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use std::time::Duration;
use notify::{Event, EventKind};
use crate::error::Error;
use crate::workspace::SKIP_NAMES;
pub(crate) const STOP_TICK: Duration = Duration::from_millis(100);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WatchEvent {
Started,
Snapshotted {
snapshot: String,
},
}
#[derive(Debug, Clone, Default)]
pub struct WatchStop(Arc<AtomicBool>);
impl WatchStop {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn stop(&self) {
self.0.store(true, Ordering::Release);
}
pub(crate) fn stopped(&self) -> bool {
self.0.load(Ordering::Acquire)
}
}
pub(crate) type Pulse = Result<(), notify::Error>;
pub(crate) fn settle(
pulses: &Receiver<Pulse>,
debounce: Duration,
stop: &WatchStop,
) -> Result<(), Error> {
loop {
if stop.stopped() {
return Ok(());
}
match pulses.recv_timeout(debounce) {
Ok(Ok(())) => {}
Ok(Err(error)) => return Err(watcher_failed(&error)),
Err(RecvTimeoutError::Timeout) => return Ok(()),
Err(RecvTimeoutError::Disconnected) => return Err(watcher_gone()),
}
}
}
pub(crate) fn watcher_failed(error: ¬ify::Error) -> Error {
Error::Engine(format!("the file watcher failed: {error}"))
}
pub(crate) fn watcher_gone() -> Error {
Error::Engine("the file watcher stopped delivering events".to_owned())
}
pub(crate) fn event_is_content(root: &Path, event: &Event) -> bool {
if matches!(event.kind, EventKind::Access(_)) {
return false;
}
if event.paths.is_empty() {
return true;
}
event.paths.iter().any(|path| is_content_path(root, path))
}
fn is_content_path(root: &Path, path: &Path) -> bool {
let Ok(relative) = path.strip_prefix(root) else {
return true;
};
match relative.components().next() {
Some(Component::Normal(name)) => !SKIP_NAMES.iter().any(|skip| name == *skip),
_ => true,
}
}