1use crate::{
7 Config, Error, EventHandler, Receiver, RecursiveMode, Sender, Watcher,
8 paths::{WatchPath, absolute_path},
9 unbounded,
10};
11use std::{
12 collections::HashMap,
13 path::{Path, PathBuf},
14 sync::{
15 Arc, Mutex,
16 atomic::{AtomicBool, Ordering},
17 },
18 thread,
19 time::Duration,
20};
21
22pub(crate) enum PollMessage {
23 Poll,
24 PollAndWait(Sender<crate::Result<()>>),
25}
26
27pub type ScanEvent = crate::Result<PathBuf>;
29
30pub trait ScanEventHandler: Send + 'static {
35 fn handle_event(&mut self, event: ScanEvent);
37}
38
39impl<F> ScanEventHandler for F
40where
41 F: FnMut(ScanEvent) + Send + 'static,
42{
43 fn handle_event(&mut self, event: ScanEvent) {
44 (self)(event);
45 }
46}
47
48#[cfg(feature = "crossbeam-channel")]
49impl ScanEventHandler for crossbeam_channel::Sender<ScanEvent> {
50 fn handle_event(&mut self, event: ScanEvent) {
51 let _ = self.send(event);
52 }
53}
54
55#[cfg(feature = "flume")]
56impl ScanEventHandler for flume::Sender<ScanEvent> {
57 fn handle_event(&mut self, event: ScanEvent) {
58 let _ = self.send(event);
59 }
60}
61
62impl ScanEventHandler for std::sync::mpsc::Sender<ScanEvent> {
63 fn handle_event(&mut self, event: ScanEvent) {
64 let _ = self.send(event);
65 }
66}
67
68impl ScanEventHandler for () {
69 fn handle_event(&mut self, _event: ScanEvent) {}
70}
71
72use data::{DataBuilder, WatchData};
73mod data {
74 use crate::{
75 EventHandler, RecursiveMode,
76 event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
77 paths::{WatchPath, reported_path},
78 };
79 use notify_types::event::EventKindMask;
80 use std::{
81 cell::RefCell,
82 collections::HashMap,
83 fmt::{self, Debug},
84 fs::{self, File, Metadata},
85 io::{self, Read},
86 path::{Path, PathBuf},
87 time::Instant,
88 };
89 use walkdir::WalkDir;
90 use xxhash_rust::xxh3::Xxh3Default;
91
92 use super::ScanEventHandler;
93
94 const NANOS_PER_SECOND: i128 = 1_000_000_000;
95
96 fn system_time_to_nanos(time: std::time::SystemTime) -> i128 {
97 match time.duration_since(std::time::SystemTime::UNIX_EPOCH) {
98 Ok(d) => d.as_nanos() as i128,
99 Err(e) => -(e.duration().as_nanos() as i128),
100 }
101 }
102
103 pub(super) struct DataBuilder {
105 emitter: EventEmitter,
106 scan_emitter: Option<Box<RefCell<dyn ScanEventHandler>>>,
107 compare_contents: bool,
108
109 now: Instant,
111 }
112
113 impl DataBuilder {
114 pub(super) fn new<F, G>(
115 event_handler: F,
116 compare_content: bool,
117 scan_emitter: Option<G>,
118 event_kinds: EventKindMask,
119 ) -> Self
120 where
121 F: EventHandler,
122 G: ScanEventHandler,
123 {
124 let scan_emitter = match scan_emitter {
125 None => None,
126 Some(v) => {
127 let intermediate: Box<RefCell<dyn ScanEventHandler>> =
129 Box::new(RefCell::new(v));
130 Some(intermediate)
131 }
132 };
133 Self {
134 emitter: EventEmitter::new(event_handler, event_kinds),
135 scan_emitter,
136 compare_contents: compare_content,
137 now: Instant::now(),
138 }
139 }
140
141 pub(super) fn update_timestamp(&mut self) {
143 self.now = Instant::now();
144 }
145
146 pub(super) fn build_watch_data(
151 &self,
152 root: WatchPath,
153 is_recursive: bool,
154 follow_symlinks: bool,
155 ) -> crate::Result<WatchData> {
156 WatchData::new(self, root, is_recursive, follow_symlinks)
157 }
158
159 fn build_path_data(&self, meta_path: &MetaPath) -> PathData {
161 PathData::new(self, meta_path)
162 }
163 }
164
165 impl Debug for DataBuilder {
166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 f.debug_struct("DataBuilder")
168 .field("compare_contents", &self.compare_contents)
169 .field("now", &self.now)
170 .finish()
171 }
172 }
173
174 #[derive(Debug)]
175 pub(super) struct WatchData {
176 root: PathBuf,
178 requested_root: PathBuf,
179 is_recursive: bool,
180 follow_symlinks: bool,
181
182 all_path_data: HashMap<PathBuf, PathData>,
184 }
185
186 impl WatchData {
187 fn new(
193 data_builder: &DataBuilder,
194 root: WatchPath,
195 is_recursive: bool,
196 follow_symlinks: bool,
197 ) -> crate::Result<Self> {
198 fs::metadata(&root.absolute)
208 .map_err(|e| crate::Error::io_watch(e).add_path(root.requested.clone()))?;
209
210 let all_path_data = Self::scan_all_path_data(
211 data_builder,
212 root.absolute.clone(),
213 root.requested.clone(),
214 is_recursive,
215 follow_symlinks,
216 true,
217 )
218 .collect();
219
220 Ok(Self {
221 root: root.absolute,
222 requested_root: root.requested,
223 is_recursive,
224 follow_symlinks,
225 all_path_data,
226 })
227 }
228
229 pub(super) fn rescan(&mut self, data_builder: &mut DataBuilder) {
235 for (path, new_path_data) in Self::scan_all_path_data(
237 data_builder,
238 self.root.clone(),
239 self.requested_root.clone(),
240 self.is_recursive,
241 self.follow_symlinks,
242 false,
243 ) {
244 let event_kind = if let Some(old_path_data) = self.all_path_data.get_mut(&path) {
245 let event_kind =
246 PathData::compare_to_kind(Some(&*old_path_data), Some(&new_path_data));
247 *old_path_data = new_path_data;
248 event_kind
249 } else {
250 let event_kind = PathData::compare_to_kind(None, Some(&new_path_data));
251 self.all_path_data.insert(path.clone(), new_path_data);
252 event_kind
253 };
254
255 if let Some(event_kind) = event_kind {
256 let event = Event::new(event_kind).add_path(reported_path(
257 &self.root,
258 &self.requested_root,
259 &path,
260 ));
261 data_builder.emitter.emit_ok(event);
262 }
263 }
264
265 let mut disappeared_paths = Vec::new();
267 for (path, path_data) in self.all_path_data.iter() {
268 if path_data.last_check < data_builder.now {
269 disappeared_paths.push(path.clone());
270 }
271 }
272
273 for path in disappeared_paths {
275 let old_path_data = self.all_path_data.remove(&path);
276
277 if let Some(event_kind) = PathData::compare_to_kind(old_path_data.as_ref(), None) {
278 let event = Event::new(event_kind).add_path(reported_path(
279 &self.root,
280 &self.requested_root,
281 &path,
282 ));
283 data_builder.emitter.emit_ok(event);
284 }
285 }
286 }
287
288 fn scan_all_path_data(
294 data_builder: &'_ DataBuilder,
295 root: PathBuf,
296 requested_root: PathBuf,
297 is_recursive: bool,
298 follow_symlinks: bool,
299 is_initial: bool,
301 ) -> impl Iterator<Item = (PathBuf, PathData)> + '_ {
302 log::trace!("rescanning {root:?}");
303 WalkDir::new(root.clone())
308 .follow_links(follow_symlinks)
309 .max_depth(Self::dir_scan_depth(is_recursive))
310 .into_iter()
311 .filter_map(|entry_res| match entry_res {
312 Ok(entry) => Some(entry),
313 Err(err) => {
314 log::warn!("walkdir error scanning {err:?}");
315
316 if let Some(io_error) = err.io_error() {
317 let new_io_error = io::Error::new(io_error.kind(), err.to_string());
319 data_builder.emitter.emit_io_err(new_io_error, err.path());
320 } else {
321 let crate_err =
322 crate::Error::new(crate::ErrorKind::Generic(err.to_string()));
323 data_builder.emitter.emit(Err(crate_err));
324 }
325 None
326 }
327 })
328 .filter_map(move |entry| match entry.metadata() {
329 Ok(metadata) => {
330 let path = entry.into_path();
331 if is_initial {
332 if let Some(ref emitter) = data_builder.scan_emitter {
334 emitter.borrow_mut().handle_event(Ok(reported_path(
335 &root,
336 &requested_root,
337 &path,
338 )));
339 }
340 }
341 let meta_path = MetaPath::from_parts_unchecked(path, metadata);
342 let data_path = data_builder.build_path_data(&meta_path);
343
344 Some((meta_path.into_path(), data_path))
345 }
346 Err(e) => {
347 let path = entry.into_path();
349 data_builder.emitter.emit_io_err(e, Some(path));
350
351 None
352 }
353 })
354 }
355
356 fn dir_scan_depth(is_recursive: bool) -> usize {
357 if is_recursive { usize::MAX } else { 1 }
358 }
359
360 pub(super) fn recursive_mode(&self) -> RecursiveMode {
361 if self.is_recursive {
362 RecursiveMode::Recursive
363 } else {
364 RecursiveMode::NonRecursive
365 }
366 }
367
368 pub(super) fn requested_root(&self) -> &Path {
369 &self.requested_root
370 }
371 }
372
373 #[derive(Debug, Clone)]
377 struct PathData {
378 mtime: i128,
380
381 hash: Option<u64>,
384
385 last_check: Instant,
387 }
388
389 impl PathData {
390 fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData {
392 let metadata = meta_path.metadata();
393 let compare_contents = data_builder.compare_contents && metadata.is_file();
394 let mut mtime = metadata.modified().map_or(0, system_time_to_nanos);
395 if metadata.is_dir() || compare_contents {
396 mtime = mtime / NANOS_PER_SECOND * NANOS_PER_SECOND;
401 }
402
403 PathData {
404 mtime,
405 hash: if compare_contents {
406 content_hash(meta_path.path()).ok()
407 } else {
408 None
409 },
410
411 last_check: data_builder.now,
412 }
413 }
414
415 fn compare_to_kind(old: Option<&PathData>, new: Option<&PathData>) -> Option<EventKind> {
416 match (old, new) {
417 (Some(old), Some(new)) => {
418 if new.mtime > old.mtime {
419 Some(EventKind::Modify(ModifyKind::Metadata(
420 MetadataKind::WriteTime,
421 )))
422 } else if new.hash != old.hash {
423 Some(EventKind::Modify(ModifyKind::Data(DataChange::Any)))
424 } else {
425 None
426 }
427 }
428 (None, Some(_new)) => Some(EventKind::Create(CreateKind::Any)),
429 (Some(_old), None) => Some(EventKind::Remove(RemoveKind::Any)),
430 (None, None) => None,
431 }
432 }
433 }
434
435 pub(super) fn content_hash(path: &Path) -> io::Result<u64> {
437 let mut hasher = Xxh3Default::new();
438 let mut file = File::open(path)?;
439 let mut buf = [0u8; 8 * 1024];
440
441 loop {
442 let n = match file.read(&mut buf) {
443 Ok(0) => break,
444 Ok(len) => len,
445 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
446 Err(e) => return Err(e),
447 };
448
449 hasher.update(&buf[..n]);
450 }
451
452 Ok(hasher.digest())
453 }
454
455 #[derive(Debug)]
461 pub(super) struct MetaPath {
462 path: PathBuf,
463 metadata: Metadata,
464 }
465
466 impl MetaPath {
467 fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self {
473 Self { path, metadata }
474 }
475
476 fn path(&self) -> &Path {
477 &self.path
478 }
479
480 fn metadata(&self) -> &Metadata {
481 &self.metadata
482 }
483
484 fn into_path(self) -> PathBuf {
485 self.path
486 }
487 }
488
489 struct EventEmitter {
491 handler: Box<RefCell<dyn EventHandler>>,
494 event_kinds: EventKindMask,
496 }
497
498 impl EventEmitter {
499 fn new<F: EventHandler>(event_handler: F, event_kinds: EventKindMask) -> Self {
500 Self {
501 handler: Box::new(RefCell::new(event_handler)),
502 event_kinds,
503 }
504 }
505
506 fn emit(&self, event: crate::Result<Event>) {
508 self.handler.borrow_mut().handle_event(event);
509 }
510
511 fn emit_ok(&self, event: Event) {
513 if self.event_kinds.matches(&event.kind) {
515 self.emit(Ok(event))
516 }
517 }
518
519 fn emit_io_err<E, P>(&self, err: E, path: Option<P>)
521 where
522 E: Into<io::Error>,
523 P: Into<PathBuf>,
524 {
525 let e = crate::Error::io(err.into());
526 if let Some(path) = path {
527 self.emit(Err(e.add_path(path.into())));
528 } else {
529 self.emit(Err(e));
530 }
531 }
532 }
533}
534
535#[derive(Debug)]
542pub struct PollWatcher {
543 watches: Arc<Mutex<HashMap<PathBuf, WatchData>>>,
544 data_builder: Arc<Mutex<DataBuilder>>,
545 want_to_stop: Arc<AtomicBool>,
546 message_channel: Sender<PollMessage>,
549 delay: Option<Duration>,
550 follow_sylinks: bool,
551}
552
553impl PollWatcher {
554 pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
556 Self::with_opt::<_, ()>(event_handler, config, None)
557 }
558
559 pub fn poll(&self) -> crate::Result<()> {
561 self.message_channel
562 .send(PollMessage::Poll)
563 .map_err(|_| Error::generic("failed to send poll message"))?;
564 Ok(())
565 }
566
567 pub fn poll_blocking(&self) -> crate::Result<()> {
571 let (done_tx, done_rx) = std::sync::mpsc::channel();
572 self.message_channel
573 .send(PollMessage::PollAndWait(done_tx))
574 .map_err(|_| Error::generic("failed to send poll message"))?;
575 done_rx
576 .recv()
577 .map_err(|_| Error::generic("poll thread disconnected"))?
578 }
579
580 #[cfg(test)]
582 pub(crate) fn poll_sender(&self) -> Sender<PollMessage> {
583 self.message_channel.clone()
584 }
585
586 pub fn with_initial_scan<F: EventHandler, G: ScanEventHandler>(
590 event_handler: F,
591 config: Config,
592 scan_callback: G,
593 ) -> crate::Result<PollWatcher> {
594 Self::with_opt(event_handler, config, Some(scan_callback))
595 }
596
597 fn with_opt<F: EventHandler, G: ScanEventHandler>(
599 event_handler: F,
600 config: Config,
601 scan_callback: Option<G>,
602 ) -> crate::Result<PollWatcher> {
603 let data_builder = DataBuilder::new(
604 event_handler,
605 config.compare_contents(),
606 scan_callback,
607 config.event_kinds(),
608 );
609
610 let (tx, rx) = unbounded::<PollMessage>();
611
612 let poll_watcher = PollWatcher {
613 watches: Default::default(),
614 data_builder: Arc::new(Mutex::new(data_builder)),
615 want_to_stop: Arc::new(AtomicBool::new(false)),
616 delay: config.poll_interval(),
617 follow_sylinks: config.follow_symlinks(),
618 message_channel: tx,
619 };
620
621 poll_watcher.run(rx);
622
623 Ok(poll_watcher)
624 }
625
626 fn run(&self, rx: Receiver<PollMessage>) {
627 let watches = Arc::clone(&self.watches);
628 let data_builder = Arc::clone(&self.data_builder);
629 let want_to_stop = Arc::clone(&self.want_to_stop);
630 let delay = self.delay;
631
632 let _ = thread::Builder::new()
633 .name("notify-rs poll loop".to_string())
634 .spawn(move || {
635 let mut first_auto_scan = true;
637
638 loop {
639 if want_to_stop.load(Ordering::SeqCst) {
640 break;
641 }
642
643 let msg = match delay {
644 Some(_delay) if first_auto_scan => {
645 first_auto_scan = false;
646 None
647 }
648 Some(delay) => match rx.recv_timeout(delay) {
649 Ok(msg) => Some(msg),
650 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => None,
651 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
652 },
653 None => match rx.recv() {
654 Ok(msg) => Some(msg),
655 Err(_) => break,
656 },
657 };
658
659 let scan_res = {
664 let mut watches = watches.lock().unwrap_or_else(|e| e.into_inner());
665 let mut data_builder =
666 data_builder.lock().unwrap_or_else(|e| e.into_inner());
667
668 data_builder.update_timestamp();
669
670 let vals = watches.values_mut();
671 for watch_data in vals {
672 watch_data.rescan(&mut data_builder);
673 }
674
675 Ok(())
676 };
677
678 if let Some(PollMessage::PollAndWait(done)) = msg {
680 let _ = done.send(scan_res);
681 }
682 }
683 });
684 }
685
686 fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
691 let watch_path = WatchPath::new(path)?;
692
693 let mut watches = self.watches.lock().unwrap_or_else(|e| e.into_inner());
697 let mut data_builder = self.data_builder.lock().unwrap_or_else(|e| e.into_inner());
698
699 data_builder.update_timestamp();
700
701 let watch_data = data_builder.build_watch_data(
702 watch_path.clone(),
703 recursive_mode.is_recursive(),
704 self.follow_sylinks,
705 )?;
706
707 watches.insert(watch_path.absolute, watch_data);
708
709 Ok(())
710 }
711
712 fn unwatch_inner(&mut self, path: &Path) -> crate::Result<()> {
716 let path = absolute_path(path)?;
717
718 self.watches
720 .lock()
721 .unwrap_or_else(|e| e.into_inner())
722 .remove(&path)
723 .map(|_| ())
724 .ok_or_else(crate::Error::watch_not_found)
725 }
726}
727
728impl Watcher for PollWatcher {
729 fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<Self> {
731 Self::new(event_handler, config)
732 }
733
734 fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
735 self.watch_inner(path, recursive_mode)
736 }
737
738 fn unwatch(&mut self, path: &Path) -> crate::Result<()> {
739 self.unwatch_inner(path)
740 }
741
742 fn watched_paths(&self) -> crate::Result<Vec<(PathBuf, RecursiveMode)>> {
743 let watches = self.watches.lock().map_err(crate::Error::from)?;
744 Ok(watches
745 .values()
746 .map(|watch| (watch.requested_root().to_path_buf(), watch.recursive_mode()))
747 .collect())
748 }
749
750 fn kind() -> crate::WatcherKind {
751 crate::WatcherKind::PollWatcher
752 }
753}
754
755impl Drop for PollWatcher {
756 fn drop(&mut self) {
757 self.want_to_stop.store(true, Ordering::Relaxed);
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::PollWatcher;
764 use crate::{Config, RecursiveMode, Watcher, test::*};
765
766 fn watcher() -> (TestWatcher<PollWatcher>, Receiver) {
767 poll_watcher_channel()
768 }
769
770 fn set_write_time_ahead(path: &std::path::Path) {
775 let ahead = std::time::SystemTime::now() + std::time::Duration::from_secs(3600);
776 std::fs::File::options()
777 .write(true)
778 .open(path)
779 .expect("open to set write time")
780 .set_modified(ahead)
781 .expect("set write time");
782 }
783
784 #[test]
785 fn poll_watcher_is_send_and_sync() {
786 fn check<T: Send + Sync>() {}
787 check::<PollWatcher>();
788 }
789
790 #[test]
791 fn watch_nonexistent_path_returns_path_not_found() {
792 let tmpdir = testdir();
793 let path = tmpdir.path().join("does_not_exist");
794
795 let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
796 let err = watcher
797 .watch(&path, RecursiveMode::Recursive)
798 .expect_err("watching a nonexistent path must return an error");
799
800 assert!(matches!(err.kind, crate::ErrorKind::PathNotFound));
801 assert_eq!(err.paths, vec![path]);
802
803 let watched = watcher.watched_paths().expect("list watched paths");
804 assert!(
805 watched.is_empty(),
806 "a failed watch must not be registered, got {watched:?}"
807 );
808 }
809
810 #[test]
811 fn watch_existing_paths_succeed() {
812 let tmpdir = testdir();
813 let file = tmpdir.path().join("entry");
814 std::fs::write(&file, "contents").expect("write file");
815
816 let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
817 watcher
818 .watch(&file, RecursiveMode::NonRecursive)
819 .expect("watch existing file");
820 watcher
821 .watch(tmpdir.path(), RecursiveMode::Recursive)
822 .expect("watch existing directory");
823
824 let watched = watcher.watched_paths().expect("list watched paths");
825 assert_eq!(watched.len(), 2);
826 }
827
828 #[test]
829 fn detects_subsecond_mtime_change_without_content_hashing() {
830 use std::{
831 fs,
832 sync::mpsc,
833 time::{Duration, SystemTime},
834 };
835
836 use crate::event::{EventKind, MetadataKind, ModifyKind};
837
838 let tmpdir = testdir();
839 let path = tmpdir.path().join("entry");
840 fs::write(&path, "initial").expect("write initial contents");
841
842 let current_second = SystemTime::now()
843 .duration_since(SystemTime::UNIX_EPOCH)
844 .expect("current time must be after the Unix epoch")
845 .as_secs();
846 let initial_mtime = SystemTime::UNIX_EPOCH
847 + Duration::from_secs(current_second)
848 + Duration::from_millis(100);
849 fs::File::options()
850 .write(true)
851 .open(&path)
852 .expect("open file to set initial mtime")
853 .set_modified(initial_mtime)
854 .expect("set initial mtime");
855
856 let (tx, rx) = mpsc::channel();
857 let mut watcher =
858 PollWatcher::new(tx, Config::default().with_manual_polling()).expect("create watcher");
859 watcher
860 .watch(tmpdir.path(), RecursiveMode::Recursive)
861 .expect("watch directory");
862
863 fs::write(&path, "updated").expect("write updated contents");
864 fs::File::options()
865 .write(true)
866 .open(&path)
867 .expect("open file to set updated mtime")
868 .set_modified(initial_mtime + Duration::from_millis(100))
869 .expect("set updated mtime");
870 watcher.poll().expect("poll for changes");
871
872 let event = rx
873 .recv_timeout(Duration::from_secs(5))
874 .expect("receive subsecond mtime change")
875 .expect("watch event must not be an error");
876 assert_eq!(event.paths, vec![path]);
877 assert_eq!(
878 event.kind,
879 EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime))
880 );
881 }
882
883 #[cfg(unix)]
884 #[test]
885 fn detects_subsecond_symlink_mtime_changes() {
886 use std::{os::unix::fs::symlink, sync::mpsc};
887
888 use nix::{
889 fcntl::AT_FDCWD,
890 sys::{
891 stat::{UtimensatFlags, utimensat},
892 time::TimeSpec,
893 },
894 };
895
896 use crate::event::{Event, EventKind, MetadataKind, ModifyKind};
897
898 for compare_contents in [false, true] {
899 let tmpdir = testdir();
900 let path = tmpdir.path().join("link");
901 symlink("missing", &path).expect("create dangling symlink");
902
903 let set_mtime = |nanos| {
904 utimensat(
905 AT_FDCWD,
906 &path,
907 &TimeSpec::UTIME_OMIT,
908 &TimeSpec::new(1_700_000_000, nanos),
909 UtimensatFlags::NoFollowSymlink,
910 )
911 .expect("set symlink mtime");
912 };
913 set_mtime(100_000_000);
914
915 let (tx, rx) = mpsc::channel();
916 let config = Config::default()
917 .with_manual_polling()
918 .with_follow_symlinks(false)
919 .with_compare_contents(compare_contents);
920 let mut watcher = PollWatcher::new(tx, config).expect("create watcher");
921 watcher
922 .watch(tmpdir.path(), RecursiveMode::Recursive)
923 .expect("watch directory");
924
925 for nanos in [200_000_000, 300_000_000] {
926 set_mtime(nanos);
927 watcher.poll_blocking().expect("poll for changes");
928
929 let events = rx
930 .try_iter()
931 .collect::<crate::Result<Vec<_>>>()
932 .expect("watch events must not be errors");
933 assert_eq!(
934 events,
935 vec![
936 Event::new(EventKind::Modify(ModifyKind::Metadata(
937 MetadataKind::WriteTime,
938 )))
939 .add_path(path.clone())
940 ],
941 "compare_contents={compare_contents}, nanos={nanos}"
942 );
943 }
944
945 watcher.poll_blocking().expect("poll without changes");
946 assert!(rx.try_recv().is_err(), "unchanged symlink emitted an event");
947 }
948 }
949
950 #[test]
951 fn unwatch_with_poisoned_mutex_does_not_panic() {
952 use std::{path::Path, sync::Arc};
953
954 let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
955
956 let watches = Arc::clone(&watcher.watches);
957 let _ = std::thread::spawn(move || {
958 let _guard = watches.lock().expect("lock watches");
959 panic!("poison watches mutex for test");
960 })
961 .join();
962
963 let result = watcher.unwatch_inner(Path::new("/path/that/is/not/watched"));
965 assert!(result.is_err());
966 }
967
968 #[test]
969 fn watched_paths_reflect_watch_and_unwatch() {
970 let tmpdir = testdir();
971 let dir_a = tmpdir.path().join("a");
972 let dir_b = tmpdir.path().join("b");
973 std::fs::create_dir(&dir_a).expect("create dir a");
974 std::fs::create_dir(&dir_b).expect("create dir b");
975
976 let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
977
978 watcher
979 .watch(&dir_a, RecursiveMode::Recursive)
980 .expect("watch dir a");
981 watcher
982 .watch(&dir_b, RecursiveMode::NonRecursive)
983 .expect("watch dir b");
984
985 let watched = watcher.watched_paths().expect("list watched paths");
986 assert!(watched.contains(&(
987 dir_a.canonicalize().expect("canonicalize dir a"),
988 RecursiveMode::Recursive,
989 )));
990 assert!(watched.contains(&(
991 dir_b.canonicalize().expect("canonicalize dir b"),
992 RecursiveMode::NonRecursive,
993 )));
994
995 watcher.unwatch(&dir_a).expect("unwatch dir a");
996
997 let watched = watcher
998 .watched_paths()
999 .expect("list watched paths after unwatch");
1000 assert!(!watched.contains(&(
1001 dir_a.canonicalize().expect("canonicalize dir a"),
1002 RecursiveMode::Recursive,
1003 )));
1004 assert!(watched.contains(&(
1005 dir_b.canonicalize().expect("canonicalize dir b"),
1006 RecursiveMode::NonRecursive,
1007 )));
1008 }
1009
1010 #[test]
1011 fn rewatching_same_path_replaces_recursive_mode() {
1012 let tmpdir = testdir();
1013 let root = tmpdir.path().canonicalize().expect("canonicalize root");
1014
1015 let mut watcher = PollWatcher::new(|_| {}, Config::default()).expect("create watcher");
1016
1017 watcher
1018 .watch(tmpdir.path(), RecursiveMode::Recursive)
1019 .expect("watch recursively");
1020 watcher
1021 .watch(tmpdir.path(), RecursiveMode::NonRecursive)
1022 .expect("watch non-recursively");
1023
1024 let watched = watcher.watched_paths().expect("list watched paths");
1025 assert!(watched.contains(&(root.clone(), RecursiveMode::NonRecursive)));
1026 assert!(!watched.contains(&(root.clone(), RecursiveMode::Recursive)));
1027 assert_eq!(
1028 watched.iter().filter(|(path, _mode)| path == &root).count(),
1029 1
1030 );
1031
1032 watcher.unwatch(tmpdir.path()).expect("unwatch");
1033 let watched = watcher.watched_paths().expect("list watched paths");
1034 assert!(!watched.iter().any(|(path, _mode)| path == &root));
1035 }
1036
1037 #[test]
1038 fn create_file() {
1039 let tmpdir = testdir();
1040 let (mut watcher, mut rx) = watcher();
1041 watcher.watch_recursively(&tmpdir);
1042
1043 let path = tmpdir.path().join("entry");
1044 std::fs::File::create_new(&path).expect("Unable to create");
1045
1046 rx.sleep_until_parent_contains(&path);
1047 rx.sleep_until_exists(&path);
1048 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1049
1050 rx.wait_unordered_exact([
1051 expected(&path).create_any(),
1052 expected(tmpdir.path()).modify_meta_mtime().optional(),
1053 ]);
1054 }
1055
1056 #[test]
1057 fn create_dir() {
1058 let tmpdir = testdir();
1059 let (mut watcher, mut rx) = watcher();
1060 watcher.watch_recursively(&tmpdir);
1061
1062 let path = tmpdir.path().join("entry");
1063 std::fs::create_dir(&path).expect("Unable to create");
1064
1065 rx.sleep_until_parent_contains(&path);
1066 rx.sleep_until_exists(&path);
1067 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1068
1069 rx.wait_unordered_exact([
1070 expected(&path).create_any(),
1071 expected(tmpdir.path()).modify_meta_mtime().optional(),
1072 ]);
1073 }
1074
1075 #[test]
1076 fn modify_file() {
1077 let tmpdir = testdir();
1078 let (mut watcher, mut rx) = watcher();
1079 let path = tmpdir.path().join("entry");
1080 std::fs::File::create_new(&path).expect("Unable to create");
1081
1082 rx.sleep_until_parent_contains(&path);
1083 rx.sleep_until_exists(&path);
1084 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1085
1086 set_write_time_ahead(&path);
1087 watcher.watch_recursively(&tmpdir);
1088
1089 std::fs::write(&path, b"123").expect("Unable to write");
1090
1091 assert!(
1092 rx.sleep_until(|| std::fs::read_to_string(&path).is_ok_and(|content| content == "123")),
1093 "the file wasn't modified"
1094 );
1095 rx.wait_ordered_exact([expected(&path).modify_data_any()]);
1096 }
1097
1098 #[test]
1099 fn remove_file() {
1100 let tmpdir = testdir();
1101 let (mut watcher, mut rx) = watcher();
1102 let path = tmpdir.path().join("entry");
1103 std::fs::File::create_new(&path).expect("Unable to create");
1104
1105 rx.sleep_until_parent_contains(&path);
1106 rx.sleep_until_exists(&path);
1107 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1108
1109 watcher.watch_recursively(&tmpdir);
1110
1111 std::fs::remove_file(&path).expect("Unable to remove");
1112
1113 rx.sleep_while_exists(&path);
1114 rx.sleep_while_parent_contains(&path);
1115 rx.sleep_until_walkdir_returns_set::<&str>(tmpdir.path(), []);
1116
1117 rx.wait_unordered_exact([
1118 expected(&path).remove_any(),
1119 expected(tmpdir.path()).modify_meta_mtime().optional(),
1120 ]);
1121 }
1122
1123 #[test]
1124 fn rename_file() {
1125 let tmpdir = testdir();
1126 let (mut watcher, mut rx) = watcher();
1127 let path = tmpdir.path().join("entry");
1128 let new_path = tmpdir.path().join("new_entry");
1129 std::fs::File::create_new(&path).expect("Unable to create");
1130
1131 rx.sleep_until_parent_contains(&path);
1132 rx.sleep_until_exists(&path);
1133 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&path]);
1134
1135 watcher.watch_recursively(&tmpdir);
1136
1137 std::fs::rename(&path, &new_path).expect("Unable to remove");
1138
1139 rx.sleep_while_exists(&path);
1140 rx.sleep_until_exists(&new_path);
1141
1142 rx.sleep_while_parent_contains(&path);
1143 rx.sleep_until_parent_contains(&new_path);
1144
1145 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [&new_path]);
1146
1147 rx.wait_unordered_exact([
1148 expected(&path).remove_any(),
1149 expected(&new_path).create_any(),
1150 expected(tmpdir.path()).modify_meta_mtime().optional(),
1151 ]);
1152 }
1153
1154 #[test]
1155 fn create_write_overwrite() {
1156 let tmpdir = testdir();
1157 let (mut watcher, mut rx) = watcher();
1158 let overwritten_file = tmpdir.path().join("overwritten_file");
1159 let overwriting_file = tmpdir.path().join("overwriting_file");
1160 std::fs::write(&overwritten_file, "123").expect("write1");
1161
1162 rx.sleep_until_parent_contains(&overwritten_file);
1163 rx.sleep_until_exists(&overwritten_file);
1164 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [overwritten_file.clone()]);
1165
1166 set_write_time_ahead(&overwritten_file);
1167
1168 watcher.watch_nonrecursively(&tmpdir);
1169
1170 std::fs::File::create(&overwriting_file).expect("create");
1171 std::fs::write(&overwriting_file, "321").expect("write2");
1172 std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1173
1174 rx.sleep_while_exists(&overwriting_file);
1175 rx.sleep_while_parent_contains(&overwriting_file);
1176 rx.sleep_until_walkdir_returns_set(tmpdir.path(), [overwritten_file.clone()]);
1177
1178 assert!(
1179 rx.sleep_until(
1180 || std::fs::read_to_string(&overwritten_file).is_ok_and(|cnt| cnt == "321")
1181 ),
1182 "file {overwritten_file:?} was not replaced"
1183 );
1184
1185 rx.wait_unordered([expected(&overwritten_file).modify_data_any()]);
1186 }
1187
1188 #[test]
1189 fn poll_watcher_respects_event_kind_mask() {
1190 use crate::{Config, Watcher};
1191 use notify_types::event::EventKindMask;
1192 use std::time::Duration;
1193
1194 let tmpdir = testdir();
1195 let (tx, rx) = std::sync::mpsc::channel();
1196
1197 let config = Config::default()
1199 .with_event_kinds(EventKindMask::CREATE)
1200 .with_compare_contents(true)
1201 .with_manual_polling();
1202
1203 let mut watcher = PollWatcher::new(tx, config).expect("create watcher");
1204 watcher
1205 .watch(tmpdir.path(), crate::RecursiveMode::Recursive)
1206 .expect("watch");
1207
1208 let path = tmpdir.path().join("test_file");
1209
1210 std::fs::write(&path, "initial").expect("write initial");
1212 watcher.poll().expect("poll 1");
1213
1214 let event = rx
1216 .recv_timeout(Duration::from_secs(1))
1217 .expect("should receive CREATE event")
1218 .expect("event should not be an error");
1219 assert!(
1220 event.kind.is_create(),
1221 "Expected CREATE event, got: {event:?}"
1222 );
1223
1224 std::fs::write(&path, "modified content").expect("write modified");
1226 watcher.poll().expect("poll 2");
1227
1228 std::thread::sleep(Duration::from_millis(100));
1230
1231 let remaining: Vec<_> = rx.try_iter().filter_map(|r| r.ok()).collect();
1233 assert!(
1234 !remaining.iter().any(|e| e.kind.is_modify()),
1235 "Should not receive MODIFY events with CREATE-only mask, got: {remaining:?}"
1236 );
1237 }
1238}