use std::{path::PathBuf, sync::Arc, time::Duration};
use notify::{Error, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{
DebounceEventResult, DebouncedEvent, Debouncer, RecommendedCache, new_debouncer,
new_debouncer_opt,
};
use tokio::{
runtime::Handle,
sync::{
broadcast,
mpsc::{Receiver, channel},
},
};
use crate::utils::{is_ignored, strip_prefix};
pub(crate) async fn create_poll_watcher() -> Result<
(
Debouncer<PollWatcher, RecommendedCache>,
Receiver<Result<Vec<DebouncedEvent>, Vec<Error>>>,
),
String,
> {
let rt = Handle::current();
let (tx, rx) = channel::<Result<Vec<DebouncedEvent>, Vec<Error>>>(16);
new_debouncer_opt(
Duration::from_millis(200),
None,
move |result: DebounceEventResult| {
let tx = tx.clone();
rt.spawn(async move {
if let Err(err) = tx.send(result).await {
log::error!("Failed to send event result: {err}");
}
});
},
RecommendedCache::new(),
notify::Config::default().with_poll_interval(Duration::from_millis(200)),
)
.map(|d| (d, rx))
.map_err(|e| e.to_string())
}
pub(crate) async fn create_recommended_watcher() -> Result<
(
Debouncer<RecommendedWatcher, RecommendedCache>,
Receiver<Result<Vec<DebouncedEvent>, Vec<Error>>>,
),
String,
> {
let rt = Handle::current();
let (tx, rx) = channel::<Result<Vec<DebouncedEvent>, Vec<Error>>>(16);
new_debouncer(
Duration::from_millis(200),
None,
move |result: DebounceEventResult| {
let tx = tx.clone();
rt.spawn(async move {
if let Err(err) = tx.send(result).await {
log::error!("Failed to send event result: {err}");
}
});
},
)
.map(|d| (d, rx))
.map_err(|e| e.to_string())
}
pub async fn watch<W: Watcher>(
root_path: PathBuf,
mut debouncer: Debouncer<W, RecommendedCache>,
mut rx: Receiver<Result<Vec<DebouncedEvent>, Vec<Error>>>,
tx: Arc<broadcast::Sender<()>>,
ignore_files: bool,
) {
debouncer
.watch(&root_path, RecursiveMode::Recursive)
.unwrap();
while let Some(result) = rx.recv().await {
let mut files_changed = false;
match result {
Ok(events) => {
for e in events {
if ignore_files {
match e
.paths
.iter()
.map(|p| is_ignored(&root_path, p))
.collect::<Result<Vec<_>, _>>()
{
Ok(ignored_list) => {
if ignored_list.iter().all(|ignored| *ignored) {
log::debug!("Skipped ignored files: {:?}", e.paths);
continue;
}
}
Err(err) => {
log::error!("Failed to check ignore files: {err}");
continue;
}
}
}
use notify::EventKind::*;
match e.event.kind {
Create(_) => {
let path = e.event.paths[0].to_str().unwrap();
log::debug!("[CREATE] {path}");
files_changed = true;
}
Modify(kind) => {
use notify::event::ModifyKind::*;
match kind {
Name(kind) => {
use notify::event::RenameMode::*;
if let Both = kind {
let source_name = &e.event.paths[0];
let target_name = &e.event.paths[1];
log::debug!(
"[RENAME] {} -> {}",
strip_prefix(source_name, &root_path).display(),
strip_prefix(target_name, &root_path).display(),
);
files_changed = true;
}
}
_ => {
let paths = e.event.paths[0].to_str().unwrap();
log::debug!("[UPDATE] {paths}");
files_changed = true;
}
}
}
Remove(_) => {
let paths = e.event.paths[0].to_str().unwrap();
log::debug!("[REMOVE] {paths}");
files_changed = true;
}
_ => {}
}
}
}
Err(errors) => {
for err in errors {
log::error!("{err}");
}
}
}
if files_changed && let Err(err) = tx.send(()) {
log::debug!("Failed to broadcast: {err}");
}
}
}