1mod cache;
65mod time;
66
67#[cfg(test)]
68mod testing;
69
70#[cfg(not(target_family = "wasm"))]
71mod file_id_map;
72
73use std::{
74 cmp::Reverse,
75 collections::{BinaryHeap, HashMap, VecDeque},
76 path::{Path, PathBuf},
77 sync::{
78 atomic::{AtomicBool, Ordering},
79 Arc, Mutex,
80 },
81 time::{Duration, Instant},
82};
83
84use time::now;
85
86pub use cache::{FileIdCache, NoCache, RecommendedCache};
87
88#[cfg(not(target_family = "wasm"))]
89pub use file_id_map::FileIdMap;
90
91pub use file_id;
92pub use notify;
93pub use notify_types::debouncer_full::DebouncedEvent;
94
95use file_id::FileId;
96use notify::{
97 event::{ModifyKind, RemoveKind, RenameMode},
98 Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, WatcherKind,
99};
100
101pub trait DebounceEventHandler: Send + 'static {
122 fn handle_event(&mut self, event: DebounceEventResult);
124}
125
126impl<F> DebounceEventHandler for F
127where
128 F: FnMut(DebounceEventResult) + Send + 'static,
129{
130 fn handle_event(&mut self, event: DebounceEventResult) {
131 (self)(event);
132 }
133}
134
135#[cfg(feature = "crossbeam-channel")]
136impl DebounceEventHandler for crossbeam_channel::Sender<DebounceEventResult> {
137 fn handle_event(&mut self, event: DebounceEventResult) {
138 let _ = self.send(event);
139 }
140}
141
142#[cfg(feature = "flume")]
143impl DebounceEventHandler for flume::Sender<DebounceEventResult> {
144 fn handle_event(&mut self, event: DebounceEventResult) {
145 let _ = self.send(event);
146 }
147}
148
149impl DebounceEventHandler for std::sync::mpsc::Sender<DebounceEventResult> {
150 fn handle_event(&mut self, event: DebounceEventResult) {
151 let _ = self.send(event);
152 }
153}
154
155pub type DebounceEventResult = Result<Vec<DebouncedEvent>, Vec<Error>>;
158
159type DebounceData<T> = Arc<Mutex<DebounceDataInner<T>>>;
160
161#[derive(Debug, Clone, Default, PartialEq, Eq)]
162struct Queue {
163 events: VecDeque<DebouncedEvent>,
168}
169
170impl Queue {
171 fn was_created(&self) -> bool {
172 self.events.front().is_some_and(|event| {
173 matches!(
174 event.kind,
175 EventKind::Create(_) | EventKind::Modify(ModifyKind::Name(RenameMode::To))
176 )
177 })
178 }
179
180 fn was_removed(&self) -> bool {
181 self.events.front().is_some_and(|event| {
182 matches!(
183 event.kind,
184 EventKind::Remove(_) | EventKind::Modify(ModifyKind::Name(RenameMode::From))
185 )
186 })
187 }
188}
189
190#[derive(Debug)]
191pub(crate) struct DebounceDataInner<T> {
192 queues: HashMap<PathBuf, Queue>,
193 roots: Vec<(PathBuf, RecursiveMode)>,
194 cache: T,
195 rename_event: Option<(DebouncedEvent, Option<FileId>)>,
196 rescan_event: Option<DebouncedEvent>,
197 errors: Vec<Error>,
198 timeout: Duration,
199}
200
201impl<T: FileIdCache> DebounceDataInner<T> {
202 pub(crate) fn new(cache: T, timeout: Duration) -> Self {
203 Self {
204 queues: HashMap::new(),
205 roots: Vec::new(),
206 cache,
207 rename_event: None,
208 rescan_event: None,
209 errors: Vec::new(),
210 timeout,
211 }
212 }
213
214 pub fn debounced_events(&mut self) -> Vec<DebouncedEvent> {
216 let now = now();
217 let mut events_expired = Vec::with_capacity(self.queues.len());
218 let mut queues_remaining = HashMap::with_capacity(self.queues.len());
219
220 if let Some(event) = self.rescan_event.take() {
221 if now.saturating_duration_since(event.time) >= self.timeout {
222 log::trace!("debounced event: {event:?}");
223 events_expired.push(event);
224 } else {
225 self.rescan_event = Some(event);
226 }
227 }
228
229 for (path, mut queue) in self.queues.drain() {
232 let mut kind_index = HashMap::new();
233
234 while let Some(event) = queue.events.pop_front() {
235 if let Some(idx) = kind_index.get(&event.kind).copied() {
237 events_expired.remove(idx);
238
239 kind_index.values_mut().for_each(|i| {
240 if *i > idx {
241 *i -= 1
242 }
243 })
244 }
245
246 if now.saturating_duration_since(event.time) >= self.timeout {
247 kind_index.insert(event.kind, events_expired.len());
248
249 events_expired.push(event);
250 } else {
251 queue.events.push_front(event);
252 break;
253 }
254 }
255
256 if !queue.events.is_empty() {
257 queues_remaining.insert(path, queue);
258 }
259 }
260
261 self.queues = queues_remaining;
262
263 sort_events(events_expired)
264 }
265
266 pub fn errors(&mut self) -> Vec<Error> {
268 std::mem::take(&mut self.errors)
269 }
270
271 pub fn add_error(&mut self, error: Error) {
273 log::trace!("raw error: {error:?}");
274
275 self.errors.push(error);
276 }
277
278 pub fn add_event(&mut self, event: Event) {
280 log::trace!("raw event: {event:?}");
281
282 if event.need_rescan() {
283 self.cache.rescan(&self.roots);
284 self.rescan_event = Some(DebouncedEvent { event, time: now() });
285 return;
286 }
287
288 let path = match event.paths.first() {
289 Some(path) => path,
290 None => {
291 log::info!("skipping event with no paths: {event:?}");
292 return;
293 }
294 };
295
296 match &event.kind {
297 EventKind::Create(_) => {
298 let recursive_mode = self.recursive_mode(path);
299
300 self.cache.add_path(path, recursive_mode);
301
302 self.push_event(event, now());
303 }
304 EventKind::Modify(ModifyKind::Name(rename_mode)) => {
305 match rename_mode {
306 RenameMode::Any => {
307 if event.paths[0].exists() {
308 self.handle_rename_to(event);
309 } else {
310 self.handle_rename_from(event);
311 }
312 }
313 RenameMode::To => {
314 self.handle_rename_to(event);
315 }
316 RenameMode::From => {
317 self.handle_rename_from(event);
318 }
319 RenameMode::Both => {
320 }
322 RenameMode::Other => {
323 }
325 }
326 }
327 EventKind::Remove(_) => {
328 self.push_remove_event(event, now());
329 }
330 EventKind::Other => {
331 }
333 _ => {
334 if self.cache.cached_file_id(path).is_none() {
335 let recursive_mode = self.recursive_mode(path);
336
337 self.cache.add_path(path, recursive_mode);
338 }
339
340 self.push_event(event, now());
341 }
342 }
343 }
344
345 fn recursive_mode(&mut self, path: &Path) -> RecursiveMode {
346 self.roots
347 .iter()
348 .find_map(|(root, recursive_mode)| {
349 if path.starts_with(root) {
350 Some(*recursive_mode)
351 } else {
352 None
353 }
354 })
355 .unwrap_or(RecursiveMode::NonRecursive)
356 }
357
358 fn handle_rename_from(&mut self, event: Event) {
359 let time = now();
360 let path = &event.paths[0];
361
362 let file_id = self.cache.cached_file_id(path).map(|id| *id.as_ref());
364 self.rename_event = Some((DebouncedEvent::new(event.clone(), time), file_id));
365
366 self.cache.remove_path(path);
367
368 self.push_event(event, time);
369 }
370
371 fn handle_rename_to(&mut self, event: Event) {
372 let recursive_mode = self.recursive_mode(&event.paths[0]);
373
374 self.cache.add_path(&event.paths[0], recursive_mode);
375
376 let trackers_match = self
377 .rename_event
378 .as_ref()
379 .and_then(|(e, _)| e.tracker())
380 .and_then(|from_tracker| {
381 event
382 .attrs
383 .tracker()
384 .map(|to_tracker| from_tracker == to_tracker)
385 })
386 .unwrap_or_default();
387
388 let file_ids_match = self
389 .rename_event
390 .as_ref()
391 .and_then(|(_, id)| id.as_ref())
392 .and_then(|from_file_id| {
393 self.cache
394 .cached_file_id(&event.paths[0])
395 .map(|to_file_id| from_file_id == to_file_id.as_ref())
396 })
397 .unwrap_or_default();
398
399 if trackers_match || file_ids_match {
400 let (mut rename_event, _) = self.rename_event.take().unwrap(); let path = rename_event.paths.remove(0);
403 let time = rename_event.time;
404 self.push_rename_event(path, event, time);
405 } else {
406 self.push_event(event, now());
408 }
409
410 self.rename_event = None;
411 }
412
413 fn push_rename_event(&mut self, path: PathBuf, event: Event, time: Instant) {
414 self.cache.remove_path(&path);
415
416 let mut source_queue = self.queues.remove(&path).unwrap_or_default();
417
418 source_queue.events.pop_back();
420
421 let (remove_index, original_path, original_time) = source_queue
423 .events
424 .iter()
425 .enumerate()
426 .find_map(|(index, e)| {
427 if matches!(
428 e.kind,
429 EventKind::Modify(ModifyKind::Name(RenameMode::Both))
430 ) {
431 Some((Some(index), e.paths[0].clone(), e.time))
432 } else {
433 None
434 }
435 })
436 .unwrap_or((None, path, time));
437
438 if let Some(remove_index) = remove_index {
439 source_queue.events.remove(remove_index);
440 }
441
442 if source_queue.was_removed() {
444 let event = source_queue.events.pop_front().unwrap();
445
446 self.queues.insert(
447 event.paths[0].clone(),
448 Queue {
449 events: [event].into(),
450 },
451 );
452 }
453
454 for e in &mut source_queue.events {
456 e.paths = vec![event.paths[0].clone()];
457 }
458
459 if !source_queue.was_created() {
461 source_queue.events.push_front(DebouncedEvent {
462 event: Event {
463 kind: EventKind::Modify(ModifyKind::Name(RenameMode::Both)),
464 paths: vec![original_path, event.paths[0].clone()],
465 attrs: event.attrs,
466 },
467 time: original_time,
468 });
469 }
470
471 if let Some(target_queue) = self.queues.get_mut(&event.paths[0]) {
472 if !target_queue.was_created() {
473 let mut remove_event = DebouncedEvent {
474 event: Event {
475 kind: EventKind::Remove(RemoveKind::Any),
476 paths: vec![event.paths[0].clone()],
477 attrs: Default::default(),
478 },
479 time: original_time,
480 };
481 if !target_queue.was_removed() {
482 remove_event.event = remove_event.event.set_info("override");
483 }
484 source_queue.events.push_front(remove_event);
485 }
486 *target_queue = source_queue;
487 } else {
488 self.queues.insert(event.paths[0].clone(), source_queue);
489 }
490 }
491
492 fn push_remove_event(&mut self, event: Event, time: Instant) {
493 let path = &event.paths[0];
494
495 self.queues.retain(|p, _| !p.starts_with(path) || p == path);
497
498 self.cache.remove_path(path);
500
501 match self.queues.get_mut(path) {
502 Some(queue) if queue.was_created() => {
503 self.queues.remove(path);
504 }
505 Some(queue) => {
506 queue.events = [DebouncedEvent::new(event, time)].into();
507 }
508 None => {
509 self.push_event(event, time);
510 }
511 }
512 }
513
514 fn push_event(&mut self, event: Event, time: Instant) {
515 let path = &event.paths[0];
516
517 if let Some(queue) = self.queues.get_mut(path) {
518 if match event.kind {
521 EventKind::Modify(
522 ModifyKind::Any
523 | ModifyKind::Data(_)
524 | ModifyKind::Metadata(_)
525 | ModifyKind::Other,
526 )
527 | EventKind::Create(_) => !queue.was_created(),
528 _ => true,
529 } {
530 queue.events.push_back(DebouncedEvent::new(event, time));
531 }
532 } else {
533 self.queues.insert(
534 path.to_path_buf(),
535 Queue {
536 events: [DebouncedEvent::new(event, time)].into(),
537 },
538 );
539 }
540 }
541}
542
543#[derive(Debug)]
545pub struct Debouncer<T: Watcher, C: FileIdCache> {
546 watcher: T,
547 debouncer_thread: Option<std::thread::JoinHandle<()>>,
548 data: DebounceData<C>,
549 stop: Arc<AtomicBool>,
550}
551
552impl<T: Watcher, C: FileIdCache> Debouncer<T, C> {
553 pub fn stop(mut self) {
556 self.set_stop();
557 if let Some(t) = self.debouncer_thread.take() {
558 let _ = t.join();
559 }
560 }
561
562 pub fn stop_nonblocking(self) {
564 self.set_stop();
565 }
566
567 fn set_stop(&self) {
568 self.stop.store(true, Ordering::Relaxed);
569 }
570
571 #[deprecated = "`Debouncer` provides all methods from `Watcher` itself now. Remove `.watcher()` and use those methods directly."]
572 pub fn watcher(&mut self) {}
573
574 #[deprecated = "`Debouncer` now manages root paths automatically. Remove all calls to `add_root` and `remove_root`."]
575 pub fn cache(&mut self) {}
576
577 fn add_root(&mut self, path: impl Into<PathBuf>, recursive_mode: RecursiveMode) {
578 let path = path.into();
579
580 let mut data = self.data.lock().unwrap();
581
582 if data.roots.iter().any(|(p, _)| p == &path) {
584 return;
585 }
586
587 data.roots.push((path.clone(), recursive_mode));
588
589 data.cache.add_path(&path, recursive_mode);
590 }
591
592 fn remove_root(&mut self, path: impl AsRef<Path>) {
593 let mut data = self.data.lock().unwrap();
594
595 data.roots.retain(|(root, _)| !root.starts_with(&path));
596
597 data.cache.remove_path(path.as_ref());
598 }
599
600 pub fn watch(
601 &mut self,
602 path: impl AsRef<Path>,
603 recursive_mode: RecursiveMode,
604 ) -> notify::Result<()> {
605 self.watcher.watch(path.as_ref(), recursive_mode)?;
606 self.add_root(path.as_ref(), recursive_mode);
607 Ok(())
608 }
609
610 pub fn unwatch(&mut self, path: impl AsRef<Path>) -> notify::Result<()> {
611 self.watcher.unwatch(path.as_ref())?;
612 self.remove_root(path);
613 Ok(())
614 }
615
616 pub fn configure(&mut self, option: notify::Config) -> notify::Result<bool> {
617 self.watcher.configure(option)
618 }
619
620 pub fn kind() -> WatcherKind
621 where
622 Self: Sized,
623 {
624 T::kind()
625 }
626}
627
628impl<T: Watcher, C: FileIdCache> Drop for Debouncer<T, C> {
629 fn drop(&mut self) {
630 self.set_stop();
631 }
632}
633
634pub fn new_debouncer_opt<F: DebounceEventHandler, T: Watcher, C: FileIdCache + Send + 'static>(
640 timeout: Duration,
641 tick_rate: Option<Duration>,
642 mut event_handler: F,
643 file_id_cache: C,
644 config: notify::Config,
645) -> Result<Debouncer<T, C>, Error> {
646 let data = Arc::new(Mutex::new(DebounceDataInner::new(file_id_cache, timeout)));
647 let stop = Arc::new(AtomicBool::new(false));
648
649 let tick_div = 4;
650 let tick = match tick_rate {
651 Some(v) => {
652 if v > timeout {
653 return Err(Error::new(ErrorKind::Generic(format!(
654 "Invalid tick_rate, tick rate {v:?} > {timeout:?} timeout!"
655 ))));
656 }
657 v
658 }
659 None => timeout.checked_div(tick_div).ok_or_else(|| {
660 Error::new(ErrorKind::Generic(format!(
661 "Failed to calculate tick as {timeout:?}/{tick_div}!"
662 )))
663 })?,
664 };
665
666 let data_c = data.clone();
667 let stop_c = stop.clone();
668 let thread = std::thread::Builder::new()
669 .name("notify-rs debouncer loop".to_string())
670 .spawn(move || loop {
671 if stop_c.load(Ordering::Acquire) {
672 break;
673 }
674 std::thread::sleep(tick);
675 let send_data;
676 let errors;
677 {
678 let mut lock = data_c.lock().unwrap();
679 send_data = lock.debounced_events();
680 errors = lock.errors();
681 }
682 if !send_data.is_empty() {
683 event_handler.handle_event(Ok(send_data));
684 }
685 if !errors.is_empty() {
686 event_handler.handle_event(Err(errors));
687 }
688 })?;
689
690 let data_c = data.clone();
691 let watcher = T::new(
692 move |e: Result<Event, Error>| {
693 let mut lock = data_c.lock().unwrap();
694
695 match e {
696 Ok(e) => lock.add_event(e),
697 Err(e) => lock.add_error(e),
699 }
700 },
701 config,
702 )?;
703
704 let guard = Debouncer {
705 watcher,
706 debouncer_thread: Some(thread),
707 data,
708 stop,
709 };
710
711 Ok(guard)
712}
713
714pub fn new_debouncer<F: DebounceEventHandler>(
720 timeout: Duration,
721 tick_rate: Option<Duration>,
722 event_handler: F,
723) -> Result<Debouncer<RecommendedWatcher, RecommendedCache>, Error> {
724 new_debouncer_opt::<F, RecommendedWatcher, RecommendedCache>(
725 timeout,
726 tick_rate,
727 event_handler,
728 RecommendedCache::new(),
729 notify::Config::default(),
730 )
731}
732
733fn sort_events(events: Vec<DebouncedEvent>) -> Vec<DebouncedEvent> {
734 let mut sorted = Vec::with_capacity(events.len());
735
736 let mut events_by_path: HashMap<_, VecDeque<_>> =
738 events.into_iter().fold(HashMap::new(), |mut acc, event| {
739 acc.entry(event.paths.last().cloned().unwrap_or_default())
740 .or_default()
741 .push_back(event);
742 acc
743 });
744
745 let mut min_time_heap = events_by_path
748 .iter()
749 .map(|(path, events)| Reverse((events[0].time, path.clone())))
750 .collect::<BinaryHeap<_>>();
751
752 while let Some(Reverse((min_time, path))) = min_time_heap.pop() {
753 let events = events_by_path.get_mut(&path).unwrap();
756
757 let mut push_next = false;
758
759 while events.front().is_some_and(|event| event.time <= min_time) {
760 let event = events.pop_front().unwrap();
762 sorted.push(event);
763 push_next = true;
764 }
765
766 if push_next {
767 if let Some(event) = events.front() {
768 min_time_heap.push(Reverse((event.time, path)));
769 }
770 }
771 }
772
773 sorted
774}
775
776#[cfg(test)]
777mod tests {
778 use std::{fs, path::Path};
779
780 use super::*;
781
782 use pretty_assertions::assert_eq;
783 use rstest::rstest;
784 use tempfile::tempdir;
785 use testing::TestCase;
786 use time::MockTime;
787
788 #[rstest]
789 fn state(
790 #[values(
791 "add_create_event",
792 "add_create_event_after_remove_event",
793 "add_create_dir_event_twice",
794 "add_event_with_no_paths_is_ok",
795 "add_modify_any_event_after_create_event",
796 "add_modify_content_event_after_create_event",
797 "add_rename_from_event",
798 "add_rename_from_event_after_create_event",
799 "add_rename_from_event_after_modify_event",
800 "add_rename_from_event_after_create_and_modify_event",
801 "add_rename_from_event_after_rename_from_event",
802 "add_rename_to_event",
803 "add_rename_to_dir_event",
804 "add_rename_from_and_to_event",
805 "add_rename_from_and_to_event_after_create",
806 "add_rename_from_and_to_event_after_rename",
807 "add_rename_from_and_to_event_after_modify_content",
808 "add_rename_from_and_to_event_override_created",
809 "add_rename_from_and_to_event_override_modified",
810 "add_rename_from_and_to_event_override_removed",
811 "add_rename_from_and_to_event_with_file_ids",
812 "add_rename_from_and_to_event_with_different_file_ids",
813 "add_rename_from_and_to_event_with_different_tracker",
814 "add_rename_both_event",
815 "add_remove_event",
816 "add_remove_event_after_create_event",
817 "add_remove_event_after_modify_event",
818 "add_remove_event_after_create_and_modify_event",
819 "add_remove_parent_event_after_remove_child_event",
820 "add_errors",
821 "debounce_modify_events",
822 "emit_continuous_modify_content_events",
823 "emit_events_in_chronological_order",
824 "emit_events_with_a_prepended_rename_event",
825 "emit_close_events_only_once",
826 "emit_modify_event_after_close_event",
827 "emit_needs_rescan_event",
828 "read_file_id_without_create_event",
829 "sort_events_chronologically",
830 "sort_events_with_reordering"
831 )]
832 file_name: &str,
833 ) {
834 let file_content =
835 fs::read_to_string(Path::new(&format!("./test_cases/{file_name}.hjson"))).unwrap();
836 let mut test_case = deser_hjson::from_str::<TestCase>(&file_content).unwrap();
837
838 let time = now();
839 MockTime::set_time(time);
840
841 let mut state = test_case.state.into_debounce_data_inner(time);
842 state.roots = vec![(PathBuf::from("/"), RecursiveMode::Recursive)];
843
844 let mut prev_event_time = Duration::default();
845
846 for event in test_case.events {
847 let event_time = Duration::from_millis(event.time);
848 let event = event.into_debounced_event(time, None);
849 MockTime::advance(event_time - prev_event_time);
850 prev_event_time = event_time;
851 state.add_event(event.event);
852 }
853
854 for error in test_case.errors {
855 let error = error.into_notify_error();
856 state.add_error(error);
857 }
858
859 let expected_errors = std::mem::take(&mut test_case.expected.errors);
860 let expected_events = std::mem::take(&mut test_case.expected.events);
861 let expected_state = test_case.expected.into_debounce_data_inner(time);
862 assert_eq!(
863 state.queues, expected_state.queues,
864 "queues not as expected"
865 );
866 assert_eq!(
867 state.rename_event, expected_state.rename_event,
868 "rename event not as expected"
869 );
870 assert_eq!(
871 state.rescan_event, expected_state.rescan_event,
872 "rescan event not as expected"
873 );
874 assert_eq!(
875 state.cache.paths, expected_state.cache.paths,
876 "cache not as expected"
877 );
878
879 assert_eq!(
880 state
881 .errors
882 .iter()
883 .map(|e| format!("{e:?}"))
884 .collect::<Vec<_>>(),
885 expected_errors
886 .iter()
887 .map(|e| format!("{:?}", e.clone().into_notify_error()))
888 .collect::<Vec<_>>(),
889 "errors not as expected"
890 );
891
892 let backup_time = now();
893 let backup_queues = state.queues.clone();
894
895 for (delay, events) in expected_events {
896 MockTime::set_time(backup_time);
897 state.queues = backup_queues.clone();
898
899 match delay.as_str() {
900 "none" => {}
901 "short" => MockTime::advance(Duration::from_millis(10)),
902 "long" => MockTime::advance(Duration::from_millis(100)),
903 _ => {
904 if let Ok(ts) = delay.parse::<u64>() {
905 MockTime::set_time(time + Duration::from_millis(ts));
906 }
907 }
908 }
909
910 let events = events
911 .into_iter()
912 .map(|event| event.into_debounced_event(time, None))
913 .collect::<Vec<_>>();
914
915 assert_eq!(
916 state.debounced_events(),
917 events,
918 "debounced events after a `{delay}` delay"
919 );
920 }
921 }
922
923 #[test]
924 fn integration() -> Result<(), Box<dyn std::error::Error>> {
925 let dir = tempdir()?;
926
927 let (tx, rx) = std::sync::mpsc::channel();
929 let mut debouncer = new_debouncer(Duration::from_millis(10), None, tx)?;
930 debouncer.watch(dir.path(), RecursiveMode::Recursive)?;
931
932 let file_path = dir.path().join("file.txt");
934 fs::write(&file_path, b"Lorem ipsum")?;
935
936 println!("waiting for event at {}", file_path.display());
937
938 let deadline = Instant::now() + Duration::from_secs(10);
940 while deadline > Instant::now() {
941 let events = rx
942 .recv_timeout(deadline - Instant::now())
943 .expect("did not receive expected event")
944 .expect("received an error");
945
946 for event in events {
947 if event.event.paths == vec![file_path.clone()]
948 || event.event.paths == vec![file_path.canonicalize()?]
949 {
950 return Ok(());
951 }
952
953 println!("unexpected event: {event:?}");
954 }
955 }
956
957 panic!("did not receive expected event");
958 }
959}