Skip to main content

notify/
poll.rs

1//! Generic Watcher implementation based on polling
2//!
3//! Checks the `watch`ed paths periodically to detect changes. This implementation only uses
4//! Rust stdlib APIs and should work on all of the platforms it supports.
5
6use crate::{
7    Config, Error, EventHandler, PathsMut, Receiver, Result, Sender, WatchMode, Watcher,
8    poll::data::WatchData, unbounded,
9};
10use std::{
11    path::{Path, PathBuf},
12    sync::mpsc,
13    thread,
14    time::Duration,
15};
16
17/// Event sent for registered handlers on initial directory scans
18pub type ScanEvent = crate::Result<PathBuf>;
19
20/// Handler trait for receivers of [`ScanEvent`].
21/// Very much the same as [`EventHandler`], but including the Result.
22///
23/// See the full example for more information.
24pub trait ScanEventHandler: Send + 'static {
25    /// Handles an event.
26    fn handle_event(&mut self, event: ScanEvent);
27}
28
29impl<F> ScanEventHandler for F
30where
31    F: FnMut(ScanEvent) + Send + 'static,
32{
33    fn handle_event(&mut self, event: ScanEvent) {
34        (self)(event);
35    }
36}
37
38#[cfg(feature = "crossbeam-channel")]
39impl ScanEventHandler for crossbeam_channel::Sender<ScanEvent> {
40    fn handle_event(&mut self, event: ScanEvent) {
41        let result = self.send(event);
42        if let Err(e) = result {
43            tracing::error!(?e, "failed to send scan event result");
44        }
45    }
46}
47
48#[cfg(feature = "flume")]
49impl ScanEventHandler for flume::Sender<ScanEvent> {
50    fn handle_event(&mut self, event: ScanEvent) {
51        let result = self.send(event);
52        if let Err(e) = result {
53            tracing::error!(?e, "failed to send scan event result");
54        }
55    }
56}
57
58impl ScanEventHandler for std::sync::mpsc::Sender<ScanEvent> {
59    fn handle_event(&mut self, event: ScanEvent) {
60        let result = self.send(event);
61        if let Err(e) = result {
62            tracing::error!(?e, "failed to send scan event result");
63        }
64    }
65}
66
67impl ScanEventHandler for () {
68    fn handle_event(&mut self, _event: ScanEvent) {}
69}
70
71use data::DataBuilder;
72mod data {
73    use crate::{
74        Error, EventHandler, Result, WatchMode,
75        consolidating_path_trie::ConsolidatingPathTrie,
76        event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
77    };
78    use rustc_hash::FxBuildHasher;
79    use std::{
80        cell::RefCell,
81        collections::{HashMap, hash_map::RandomState},
82        fmt::{self, Debug},
83        fs::{File, FileType, Metadata},
84        hash::{BuildHasher, Hasher},
85        io::{self, Read},
86        path::{Path, PathBuf},
87        time::{Instant, SystemTime},
88    };
89    use walkdir::WalkDir;
90
91    use super::ScanEventHandler;
92
93    /// Builder for [`WatchData`] & [`PathData`].
94    pub(super) struct DataBuilder {
95        emitter: EventEmitter,
96        scan_emitter: Option<Box<RefCell<dyn ScanEventHandler>>>,
97
98        // TODO: May allow user setup their custom BuildHasher / BuildHasherDefault
99        // in future.
100        build_hasher: Option<RandomState>,
101
102        // current timestamp for building Data.
103        now: Instant,
104    }
105
106    impl DataBuilder {
107        pub(super) fn new<F, G>(
108            event_handler: F,
109            compare_content: bool,
110            scan_emitter: Option<G>,
111        ) -> Self
112        where
113            F: EventHandler,
114            G: ScanEventHandler,
115        {
116            let scan_emitter = match scan_emitter {
117                None => None,
118                Some(v) => {
119                    // workaround for a weird type resolution bug when directly going to dyn Trait
120                    let intermediate: Box<RefCell<dyn ScanEventHandler>> =
121                        Box::new(RefCell::new(v));
122                    Some(intermediate)
123                }
124            };
125            Self {
126                emitter: EventEmitter::new(event_handler),
127                scan_emitter,
128                build_hasher: compare_content.then(RandomState::default),
129                now: Instant::now(),
130            }
131        }
132
133        /// Update internal timestamp.
134        pub(super) fn update_timestamp(&mut self) {
135            self.now = Instant::now();
136        }
137
138        /// Create [`PathData`].
139        fn build_path_data(&self, meta_path: &MetaPath) -> PathData {
140            PathData::new(self, meta_path)
141        }
142    }
143
144    impl Debug for DataBuilder {
145        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146            f.debug_struct("DataBuilder")
147                .field("build_hasher", &self.build_hasher)
148                .field("now", &self.now)
149                .finish_non_exhaustive()
150        }
151    }
152
153    type SingleWatchHandlerMap = HashMap<PathBuf, /* recursive */ bool, FxBuildHasher>;
154
155    #[derive(Debug)]
156    struct WatchHandlers {
157        current: SingleWatchHandlerMap,
158        next: SingleWatchHandlerMap,
159        is_stale: bool,
160    }
161
162    impl WatchHandlers {
163        fn new() -> Self {
164            Self {
165                current: HashMap::default(),
166                next: HashMap::default(),
167                is_stale: false,
168            }
169        }
170
171        /// Recalculate from `watches`.
172        fn recalculate(&mut self, watches: &HashMap<PathBuf, WatchMode, FxBuildHasher>) {
173            self.next.clear();
174            self.is_stale = true;
175
176            let mut trie = ConsolidatingPathTrie::new(false, 0);
177            for (path, mode) in watches {
178                if mode.recursive_mode == crate::RecursiveMode::Recursive {
179                    trie.insert(path);
180                }
181            }
182            // insert non-recursive watches that are not covered by recursive watches
183            for (path, mode) in watches {
184                if mode.recursive_mode != crate::RecursiveMode::Recursive {
185                    self.next.insert(path.clone(), false);
186                }
187            }
188            // insert recursive watches
189            for path in trie.values() {
190                self.next.insert(path, true);
191            }
192        }
193
194        fn use_handlers(&mut self) -> (&SingleWatchHandlerMap, Option<SingleWatchHandlerMap>) {
195            if self.is_stale {
196                let old_next = std::mem::take(&mut self.next);
197                let old_current = std::mem::replace(&mut self.current, old_next);
198                self.is_stale = false;
199                return (&self.current, Some(old_current));
200            }
201            (&self.current, None)
202        }
203    }
204
205    #[derive(Debug)]
206    pub(super) struct WatchData {
207        // config part, won't change.
208        follow_symlinks: bool,
209
210        // current status part.
211        watches: HashMap<PathBuf, WatchMode, FxBuildHasher>,
212        watch_handlers: WatchHandlers,
213        all_path_data: HashMap<PathBuf, PathData, FxBuildHasher>,
214    }
215
216    impl WatchData {
217        /// Create a new `WatchData`.
218        pub fn new(follow_symlinks: bool) -> Self {
219            Self {
220                follow_symlinks,
221                watches: HashMap::default(),
222                watch_handlers: WatchHandlers::new(),
223                all_path_data: HashMap::default(),
224            }
225        }
226
227        pub fn add_watch(&mut self, path: PathBuf, mode: WatchMode) -> Result<()> {
228            if mode.target_mode == crate::TargetMode::NoTrack && !path.exists() {
229                return Err(crate::Error::path_not_found().add_path(path));
230            }
231
232            self.watches.insert(path, mode);
233            self.watch_handlers.recalculate(&self.watches);
234            Ok(())
235        }
236
237        pub fn add_watch_multiple(&mut self, paths: Vec<(PathBuf, WatchMode)>) -> Result<()> {
238            for (path, mode) in paths {
239                if mode.target_mode == crate::TargetMode::NoTrack && !path.exists() {
240                    return Err(crate::Error::path_not_found().add_path(path));
241                }
242
243                self.watches.insert(path, mode);
244            }
245            self.watch_handlers.recalculate(&self.watches);
246            Ok(())
247        }
248
249        pub fn remove_watch(&mut self, path: &Path) -> Result<()> {
250            self.watches.remove(path).ok_or(Error::watch_not_found())?;
251            self.watch_handlers.recalculate(&self.watches);
252            Ok(())
253        }
254
255        /// Rescan filesystem and update this `WatchData`.
256        ///
257        /// # Side effect
258        ///
259        /// This function may emit event by `data_builder.emitter`.
260        pub(super) fn rescan(&mut self, data_builder: &DataBuilder) {
261            let (watch_handlers, old_watch_handlers) = self.watch_handlers.use_handlers();
262
263            // scan current filesystem.
264            for (path, new_path_data) in
265                Self::scan_all_path_data(data_builder, watch_handlers, self.follow_symlinks)
266            {
267                let event_kind = if let Some(old_path_data) = self.all_path_data.get_mut(&path) {
268                    let event_kind =
269                        PathData::compare_to_kind(Some(&*old_path_data), Some(&new_path_data));
270                    *old_path_data = new_path_data;
271                    event_kind
272                } else {
273                    let event_kind = PathData::compare_to_kind(None, Some(&new_path_data));
274                    self.all_path_data.insert(path.clone(), new_path_data);
275                    event_kind
276                };
277
278                let is_initial = old_watch_handlers
279                    .as_ref()
280                    .is_some_and(|old_watch_handlers| {
281                        !old_watch_handlers.contains_key(&path)
282                            && !path.ancestors().skip(1).any(|ancestor| {
283                                old_watch_handlers
284                                    .get(ancestor)
285                                    .is_some_and(|is_recursive| *is_recursive)
286                            })
287                    });
288                if is_initial {
289                    // emit initial scans
290                    if let Some(ref emitter) = data_builder.scan_emitter {
291                        emitter.borrow_mut().handle_event(Ok(path.clone()));
292                    }
293                } else if let Some(event_kind) = event_kind {
294                    let event = Event::new(event_kind).add_path(path);
295                    data_builder.emitter.emit_ok(event);
296                }
297            }
298
299            // scan for disappeared paths.
300            let mut disappeared_paths = Vec::new();
301            for (path, path_data) in &self.all_path_data {
302                if path_data.last_check < data_builder.now {
303                    disappeared_paths.push(path.clone());
304                }
305            }
306
307            // remove disappeared paths
308            for path in disappeared_paths {
309                let old_path_data = self.all_path_data.remove(&path);
310
311                if let Some(event_kind) = PathData::compare_to_kind(old_path_data.as_ref(), None) {
312                    let event = Event::new(event_kind).add_path(path);
313                    data_builder.emitter.emit_ok(event);
314                }
315            }
316        }
317
318        /// Get all `PathData` by given configuration.
319        ///
320        /// # Side Effect
321        ///
322        /// This function may emit some IO Error events by `data_builder.emitter`.
323        fn scan_all_path_data(
324            data_builder: &DataBuilder,
325            watch_handlers: &HashMap<PathBuf, /* recursive */ bool, FxBuildHasher>,
326            follow_symlinks: bool,
327        ) -> impl Iterator<Item = (PathBuf, PathData)> {
328            tracing::trace!("rescanning");
329
330            watch_handlers.iter().flat_map(move |(path, is_recursive)| {
331                tracing::trace!(?path, is_recursive, "scanning watch handler");
332
333                // WalkDir return only one entry if root is a file (not a folder),
334                // so we can use single logic to do the both file & dir's jobs.
335                //
336                // See: https://docs.rs/walkdir/2.0.1/walkdir/struct.WalkDir.html#method.new
337                WalkDir::new(path)
338                    .follow_links(follow_symlinks)
339                    .max_depth(if *is_recursive { usize::MAX } else { 1 })
340                    .into_iter()
341                    .filter_map(|entry_res| match entry_res {
342                        Ok(entry) => Some(entry),
343                        Err(err) => {
344                            tracing::warn!("walkdir error scanning {err:?}");
345
346                            if let Some(io_error) = err.io_error() {
347                                if io_error.kind() == io::ErrorKind::NotFound {
348                                    return None;
349                                }
350                                // clone an io::Error, so we have to create a new one.
351                                let new_io_error = io::Error::new(io_error.kind(), err.to_string());
352                                data_builder.emitter.emit_io_err(new_io_error, err.path());
353                            } else {
354                                let crate_err =
355                                    Error::new(crate::ErrorKind::Generic(err.to_string()));
356                                data_builder.emitter.emit(Err(crate_err));
357                            }
358                            None
359                        }
360                    })
361                    .filter_map(move |entry| match entry.metadata() {
362                        Ok(metadata) => {
363                            let path = entry.into_path();
364                            let meta_path = MetaPath::from_parts_unchecked(path, metadata);
365                            let data_path = data_builder.build_path_data(&meta_path);
366
367                            Some((meta_path.into_path(), data_path))
368                        }
369                        Err(err) => {
370                            if let Some(io_error) = err.io_error()
371                                && io_error.kind() == io::ErrorKind::NotFound
372                            {
373                                return None;
374                            }
375
376                            // emit event.
377                            let path = entry.into_path();
378                            data_builder.emitter.emit_io_err(err, Some(path));
379
380                            None
381                        }
382                    })
383            })
384        }
385    }
386
387    /// Stored data for a one path locations.
388    ///
389    /// See [`WatchData`] for more detail.
390    #[derive(Debug, Clone)]
391    struct PathData {
392        /// File updated time.
393        mtime: SystemTime,
394
395        file_type: FileType,
396
397        /// Content's hash value, only available if user request compare file
398        /// contents and read successful.
399        hash: Option<u64>,
400
401        /// Checked time.
402        last_check: Instant,
403    }
404
405    impl PathData {
406        /// Create a new `PathData`.
407        fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData {
408            let metadata = meta_path.metadata();
409
410            PathData {
411                mtime: metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH),
412                file_type: metadata.file_type(),
413                hash: data_builder
414                    .build_hasher
415                    .as_ref()
416                    .filter(|_| metadata.is_file())
417                    .and_then(|build_hasher| {
418                        Self::get_content_hash(build_hasher, meta_path.path()).ok()
419                    }),
420
421                last_check: data_builder.now,
422            }
423        }
424
425        /// Get hash value for the data content in given file `path`.
426        fn get_content_hash(build_hasher: &RandomState, path: &Path) -> io::Result<u64> {
427            let mut hasher = build_hasher.build_hasher();
428            let mut file = File::open(path)?;
429            let mut buf = [0; 512];
430
431            loop {
432                let n = match file.read(&mut buf) {
433                    Ok(0) => break,
434                    Ok(len) => len,
435                    Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
436                    Err(e) => return Err(e),
437                };
438
439                hasher.write(&buf[..n]);
440            }
441
442            Ok(hasher.finish())
443        }
444
445        /// Get `CreateKind` by file type.
446        fn get_create_kind(&self) -> CreateKind {
447            #[expect(clippy::filetype_is_file)]
448            if self.file_type.is_dir() {
449                CreateKind::Folder
450            } else if self.file_type.is_file() {
451                CreateKind::File
452            } else {
453                CreateKind::Any
454            }
455        }
456
457        /// Get `RemoveKind` by file type.
458        fn get_remove_kind(&self) -> RemoveKind {
459            #[expect(clippy::filetype_is_file)]
460            if self.file_type.is_dir() {
461                RemoveKind::Folder
462            } else if self.file_type.is_file() {
463                RemoveKind::File
464            } else {
465                RemoveKind::Any
466            }
467        }
468
469        /// Get [`EventKind`] by compare two optional [`PathData`].
470        fn compare_to_kind(old: Option<&PathData>, new: Option<&PathData>) -> Option<EventKind> {
471            match (old, new) {
472                (Some(old), Some(new)) => {
473                    if new.mtime > old.mtime {
474                        Some(EventKind::Modify(ModifyKind::Metadata(
475                            MetadataKind::WriteTime,
476                        )))
477                    } else if new.hash != old.hash {
478                        Some(EventKind::Modify(ModifyKind::Data(DataChange::Any)))
479                    } else {
480                        None
481                    }
482                }
483                (None, Some(new)) => Some(EventKind::Create(new.get_create_kind())),
484                (Some(old), None) => Some(EventKind::Remove(old.get_remove_kind())),
485                (None, None) => None,
486            }
487        }
488    }
489
490    /// Compose path and its metadata.
491    ///
492    /// This data structure designed for make sure path and its metadata can be
493    /// transferred in consistent way, and may avoid some duplicated
494    /// `fs::metadata()` function call in some situations.
495    #[derive(Debug)]
496    pub(super) struct MetaPath {
497        path: PathBuf,
498        metadata: Metadata,
499    }
500
501    impl MetaPath {
502        /// Create `MetaPath` by given parts.
503        ///
504        /// # Invariant
505        ///
506        /// User must make sure the input `metadata` are associated with `path`.
507        fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self {
508            Self { path, metadata }
509        }
510
511        fn path(&self) -> &Path {
512            &self.path
513        }
514
515        fn metadata(&self) -> &Metadata {
516            &self.metadata
517        }
518
519        fn into_path(self) -> PathBuf {
520            self.path
521        }
522    }
523
524    /// Thin wrapper for outer event handler, for easy to use.
525    struct EventEmitter(
526        // Use `RefCell` to make sure `emit()` only need shared borrow of self (&self).
527        // Use `Box` to make sure EventEmitter is Sized.
528        Box<RefCell<dyn EventHandler>>,
529    );
530
531    impl EventEmitter {
532        fn new<F: EventHandler>(event_handler: F) -> Self {
533            Self(Box::new(RefCell::new(event_handler)))
534        }
535
536        /// Emit single event.
537        fn emit(&self, event: crate::Result<Event>) {
538            self.0.borrow_mut().handle_event(event);
539        }
540
541        /// Emit event.
542        fn emit_ok(&self, event: Event) {
543            self.emit(Ok(event));
544        }
545
546        /// Emit io error event.
547        fn emit_io_err<E, P>(&self, err: E, path: Option<P>)
548        where
549            E: Into<io::Error>,
550            P: Into<PathBuf>,
551        {
552            let e = crate::Error::io(err.into());
553            if let Some(path) = path {
554                self.emit(Err(e.add_path(path.into())));
555            } else {
556                self.emit(Err(e));
557            }
558        }
559    }
560}
561
562enum EventLoopMsg {
563    AddWatch(PathBuf, WatchMode, Sender<Result<()>>),
564    AddWatchMultiple(Vec<(PathBuf, WatchMode)>, Sender<Result<()>>),
565    RemoveWatch(PathBuf, Sender<Result<()>>),
566    #[cfg(test)]
567    WaitNextScan(Sender<Result<()>>),
568    /// currently used only for manual polling
569    Poll,
570    Shutdown,
571}
572
573struct PollPathsMut<'a> {
574    inner: &'a mut PollWatcher,
575    add_paths: Vec<(PathBuf, WatchMode)>,
576}
577impl<'a> PollPathsMut<'a> {
578    fn new(watcher: &'a mut PollWatcher) -> Self {
579        Self {
580            inner: watcher,
581            add_paths: Vec::new(),
582        }
583    }
584}
585impl PathsMut for PollPathsMut<'_> {
586    #[tracing::instrument(level = "debug", skip(self))]
587    fn add(&mut self, path: &Path, watch_mode: WatchMode) -> Result<()> {
588        self.add_paths.push((path.to_owned(), watch_mode));
589        Ok(())
590    }
591
592    #[tracing::instrument(level = "debug", skip(self))]
593    fn remove(&mut self, path: &Path) -> Result<()> {
594        self.inner.unwatch_inner(path)
595    }
596
597    #[tracing::instrument(level = "debug", skip(self))]
598    fn commit(self: Box<Self>) -> Result<()> {
599        let paths = self.add_paths;
600        self.inner.watch_multiple_inner(paths)
601    }
602}
603
604/// Polling based `Watcher` implementation.
605///
606/// By default scans through all files and checks for changed entries based on their change date.
607/// Can also be changed to perform file content change checks.
608///
609/// See [Config] for more details.
610#[derive(Debug)]
611pub struct PollWatcher {
612    delay: Option<Duration>,
613    follow_symlinks: bool,
614
615    event_loop_tx: Sender<EventLoopMsg>,
616}
617
618impl PollWatcher {
619    /// Create a new [`PollWatcher`], configured as needed.
620    pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
621        Ok(Self::with_opt::<_, ()>(event_handler, config, None))
622    }
623
624    /// Actively poll for changes. Can be combined with a timeout of 0 to perform only manual polling.
625    pub fn poll(&self) -> crate::Result<()> {
626        self.event_loop_tx
627            .send(EventLoopMsg::Poll)
628            .map_err(|_| Error::generic("failed to send poll message"))?;
629        Ok(())
630    }
631
632    #[cfg(test)]
633    pub(crate) fn wait_next_scan(&self) -> crate::Result<()> {
634        let (tx, rx) = unbounded();
635        self.event_loop_tx
636            .send(EventLoopMsg::WaitNextScan(tx))
637            .map_err(|_| Error::generic("failed to send WaitNextScan message"))?;
638        rx.recv().unwrap()
639    }
640
641    /// Returns a sender to initiate changes detection.
642    #[cfg(test)]
643    pub(crate) fn poll_sender(&self) -> Sender<()> {
644        let inner_tx = self.event_loop_tx.clone();
645        let (tx, rx) = unbounded();
646        thread::Builder::new()
647            .name("notify-rs poll loop".to_string())
648            .spawn(move || {
649                for () in &rx {
650                    if let Err(err) = inner_tx.send(EventLoopMsg::Poll) {
651                        tracing::error!(?err, "failed to send poll message");
652                    }
653                }
654            })
655            .unwrap();
656        tx
657    }
658
659    /// Create a new [`PollWatcher`] with an scan event handler.
660    ///
661    /// `scan_fallback` is called on the initial scan with all files seen by the pollwatcher.
662    pub fn with_initial_scan<F: EventHandler, G: ScanEventHandler>(
663        event_handler: F,
664        config: Config,
665        scan_callback: G,
666    ) -> crate::Result<PollWatcher> {
667        Ok(Self::with_opt(event_handler, config, Some(scan_callback)))
668    }
669
670    /// create a new [`PollWatcher`] with all options.
671    fn with_opt<F: EventHandler, G: ScanEventHandler>(
672        event_handler: F,
673        config: Config,
674        scan_callback: Option<G>,
675    ) -> PollWatcher {
676        let (tx, rx) = unbounded();
677
678        let poll_watcher = PollWatcher {
679            delay: config.poll_interval(),
680            follow_symlinks: config.follow_symlinks(),
681
682            event_loop_tx: tx,
683        };
684
685        let data_builder =
686            DataBuilder::new(event_handler, config.compare_contents(), scan_callback);
687        poll_watcher.run(rx, data_builder);
688
689        poll_watcher
690    }
691
692    fn run(&self, rx: Receiver<EventLoopMsg>, mut data_builder: DataBuilder) {
693        let delay = self.delay;
694        let follow_symlinks = self.follow_symlinks;
695
696        let result = thread::Builder::new()
697            .name("notify-rs poll loop".to_string())
698            .spawn(move || {
699                let mut watch_data = WatchData::new(follow_symlinks);
700
701                loop {
702                    data_builder.update_timestamp();
703                    watch_data.rescan(&data_builder);
704
705                    // TODO: v7.0 use delay - (Instant::now().saturating_duration_since(start))
706                    let result = if let Some(delay) = delay {
707                        rx.recv_timeout(delay).or_else(|e| match e {
708                            mpsc::RecvTimeoutError::Timeout => Ok(EventLoopMsg::Poll),
709                            mpsc::RecvTimeoutError::Disconnected => Err(mpsc::RecvError),
710                        })
711                    } else {
712                        rx.recv()
713                    };
714                    match result {
715                        Ok(EventLoopMsg::AddWatch(path, mode, resp_tx)) => {
716                            let result = resp_tx.send(watch_data.add_watch(path, mode));
717                            if let Err(e) = result {
718                                tracing::error!(?e, "failed to send AddWatch response");
719                            }
720                        }
721                        Ok(EventLoopMsg::AddWatchMultiple(paths, resp_tx)) => {
722                            let result = resp_tx.send(watch_data.add_watch_multiple(paths));
723                            if let Err(e) = result {
724                                tracing::error!(?e, "failed to send AddWatchMultiple response");
725                            }
726                        }
727                        Ok(EventLoopMsg::RemoveWatch(path, resp_tx)) => {
728                            let result = resp_tx.send(watch_data.remove_watch(&path));
729                            if let Err(e) = result {
730                                tracing::error!(?e, "failed to send RemoveWatch response");
731                            }
732                        }
733                        Ok(EventLoopMsg::Poll) => {
734                            // continue the loop
735                        }
736                        #[cfg(test)]
737                        Ok(EventLoopMsg::WaitNextScan(resp_tx)) => {
738                            let result = resp_tx.send(Ok(()));
739                            if let Err(e) = result {
740                                tracing::error!(?e, "failed to send WaitNextScan response");
741                            }
742                        }
743                        Ok(EventLoopMsg::Shutdown) => {
744                            break;
745                        }
746                        Err(e) => {
747                            tracing::error!(?e, "failed to receive poll message");
748                        }
749                    }
750                }
751            });
752        if let Err(e) = result {
753            tracing::error!(?e, "failed to start poll watcher thread");
754        }
755    }
756
757    /// Watch a path location.
758    fn watch_inner(&self, path: &Path, watch_mode: WatchMode) -> crate::Result<()> {
759        let (tx, rx) = unbounded();
760        self.event_loop_tx
761            .send(EventLoopMsg::AddWatch(path.to_path_buf(), watch_mode, tx))?;
762        rx.recv().unwrap()
763    }
764
765    fn watch_multiple_inner(&self, paths: Vec<(PathBuf, WatchMode)>) -> crate::Result<()> {
766        let (tx, rx) = unbounded();
767        self.event_loop_tx
768            .send(EventLoopMsg::AddWatchMultiple(paths, tx))?;
769        rx.recv().unwrap()
770    }
771
772    /// Unwatch a path.
773    ///
774    /// Return `Err(_)` if given path has't be monitored.
775    fn unwatch_inner(&self, path: &Path) -> crate::Result<()> {
776        let (tx, rx) = unbounded();
777        self.event_loop_tx
778            .send(EventLoopMsg::RemoveWatch(path.to_path_buf(), tx))?;
779        rx.recv().unwrap()
780    }
781}
782
783impl Watcher for PollWatcher {
784    /// Create a new [`PollWatcher`].
785    #[tracing::instrument(level = "debug", skip(event_handler))]
786    fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<Self> {
787        Self::new(event_handler, config)
788    }
789
790    #[tracing::instrument(level = "debug", skip(self))]
791    fn watch(&mut self, path: &Path, watch_mode: WatchMode) -> crate::Result<()> {
792        self.watch_inner(path, watch_mode)
793    }
794
795    #[tracing::instrument(level = "debug", skip(self))]
796    fn paths_mut<'me>(&'me mut self) -> Box<dyn PathsMut + 'me> {
797        Box::new(PollPathsMut::new(self))
798    }
799
800    #[tracing::instrument(level = "debug", skip(self))]
801    fn unwatch(&mut self, path: &Path) -> crate::Result<()> {
802        self.unwatch_inner(path)
803    }
804
805    fn kind() -> crate::WatcherKind {
806        crate::WatcherKind::PollWatcher
807    }
808}
809
810impl Drop for PollWatcher {
811    fn drop(&mut self) {
812        let result = self.event_loop_tx.send(EventLoopMsg::Shutdown);
813        if let Err(e) = result {
814            tracing::error!(?e, "failed to send shutdown message to poll watcher thread");
815        }
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    #[cfg(target_family = "wasm")]
822    use std::thread::sleep;
823    #[cfg(target_family = "wasm")]
824    use std::time::Duration;
825
826    use super::PollWatcher;
827    use crate::{Error, ErrorKind, RecursiveMode, TargetMode, WatchMode, Watcher, test::*};
828
829    fn watcher() -> (TestWatcher<PollWatcher>, Receiver) {
830        poll_watcher_channel()
831    }
832
833    #[test]
834    fn poll_watcher_is_send_and_sync() {
835        fn check<T: Send + Sync>() {}
836        check::<PollWatcher>();
837    }
838
839    #[test]
840    fn create_file() {
841        let tmpdir = testdir();
842        let (mut watcher, rx) = watcher();
843        watcher.watch_recursively(&tmpdir);
844        watcher.watcher.wait_next_scan().expect("wait next scan");
845
846        let path = tmpdir.path().join("entry");
847        std::fs::File::create_new(&path).expect("Unable to create");
848
849        rx.sleep_until_parent_contains(&path);
850        rx.sleep_until_exists(&path);
851
852        rx.wait_unordered_exact([
853            expected(&path).create_file(),
854            expected(tmpdir.path()).modify_meta_mtime().optional(),
855        ]);
856    }
857
858    #[test]
859    fn create_self_file() {
860        let tmpdir = testdir();
861        let (mut watcher, rx) = watcher();
862
863        let path = tmpdir.path().join("entry");
864
865        watcher.watch_nonrecursively(&path);
866        watcher.watcher.wait_next_scan().expect("wait next scan");
867
868        std::fs::File::create_new(&path).expect("create");
869
870        rx.sleep_until_exists(&path);
871        rx.wait_ordered_exact([expected(&path).create_file()]);
872    }
873
874    #[test]
875    fn create_self_file_no_track() {
876        let tmpdir = testdir();
877        let (mut watcher, _) = watcher();
878
879        let path = tmpdir.path().join("entry");
880
881        let result = watcher.watcher.watch(
882            &path,
883            WatchMode {
884                recursive_mode: RecursiveMode::NonRecursive,
885                target_mode: TargetMode::NoTrack,
886            },
887        );
888        assert!(matches!(
889            result,
890            Err(Error {
891                paths: _,
892                kind: ErrorKind::PathNotFound
893            })
894        ));
895    }
896
897    #[test]
898    fn create_self_file_nested() {
899        let tmpdir = testdir();
900        let (mut watcher, rx) = watcher();
901
902        let path = tmpdir.path().join("entry/nested");
903
904        watcher.watch_nonrecursively(&path);
905        watcher.watcher.wait_next_scan().expect("wait next scan");
906
907        std::fs::create_dir_all(path.parent().unwrap()).expect("create");
908        std::fs::File::create_new(&path).expect("create");
909
910        rx.wait_ordered_exact([expected(&path).create_file()]);
911    }
912
913    #[test]
914    fn create_dir() {
915        let tmpdir = testdir();
916        let (mut watcher, rx) = watcher();
917        watcher.watch_recursively(&tmpdir);
918        watcher.watcher.wait_next_scan().expect("wait next scan");
919
920        let path = tmpdir.path().join("entry");
921        std::fs::create_dir(&path).expect("Unable to create");
922
923        rx.sleep_until_parent_contains(&path);
924        rx.sleep_until_exists(&path);
925
926        rx.wait_unordered_exact([
927            expected(&path).create_folder(),
928            expected(tmpdir.path()).modify_meta_mtime().optional(),
929        ]);
930    }
931
932    #[test]
933    fn modify_file() {
934        let tmpdir = testdir();
935        let (mut watcher, rx) = watcher();
936        let path = tmpdir.path().join("entry");
937        std::fs::File::create_new(&path).expect("Unable to create");
938
939        rx.sleep_until_parent_contains(&path);
940
941        watcher.watch_recursively(&tmpdir);
942        watcher.watcher.wait_next_scan().expect("wait next scan");
943        std::fs::write(&path, b"123").expect("Unable to write");
944
945        assert!(
946            rx.sleep_until(|| std::fs::read_to_string(&path).is_ok_and(|content| content == "123")),
947            "the file wasn't modified"
948        );
949        rx.wait_unordered_exact([expected(&path).modify().multiple()]);
950    }
951
952    #[test]
953    fn rename_file() {
954        let tmpdir = testdir();
955        let (mut watcher, rx) = watcher();
956        let path = tmpdir.path().join("entry");
957        let new_path = tmpdir.path().join("new_entry");
958        std::fs::File::create_new(&path).expect("Unable to create");
959
960        rx.sleep_until_parent_contains(&path);
961
962        watcher.watch_recursively(&tmpdir);
963
964        watcher.watcher.wait_next_scan().expect("wait next scan");
965        std::fs::rename(&path, &new_path).expect("Unable to remove");
966
967        rx.sleep_while_exists(&path);
968        rx.sleep_until_exists(&new_path);
969
970        rx.sleep_while_parent_contains(&path);
971        rx.sleep_until_parent_contains(&new_path);
972
973        rx.wait_unordered_exact([
974            expected(&path).remove_file(),
975            expected(&new_path).create_file(),
976            expected(tmpdir.path()).modify_meta_mtime().optional(),
977        ]);
978    }
979
980    #[test]
981    fn rename_self_file() {
982        let tmpdir = testdir();
983        let (mut watcher, rx) = watcher();
984
985        let path = tmpdir.path().join("entry");
986        std::fs::File::create_new(&path).expect("create");
987
988        watcher.watch_nonrecursively(&path);
989        watcher.watcher.wait_next_scan().expect("wait next scan");
990        let new_path = tmpdir.path().join("renamed");
991
992        std::fs::rename(&path, &new_path).expect("rename");
993
994        rx.sleep_while_exists(&path);
995        rx.sleep_until_exists(&new_path);
996
997        rx.wait_unordered_exact([expected(&path).remove_file()])
998            .ensure_no_tail();
999
1000        std::fs::rename(&new_path, &path).expect("rename2");
1001        watcher.watcher.wait_next_scan().expect("wait next scan");
1002
1003        rx.sleep_while_exists(&new_path);
1004        rx.sleep_until_exists(&path);
1005
1006        rx.wait_unordered_exact([expected(&path).create_file()])
1007            .ensure_no_tail();
1008    }
1009
1010    #[test]
1011    fn rename_self_file_no_track() {
1012        let tmpdir = testdir();
1013        let (mut watcher, rx) = watcher();
1014
1015        let path = tmpdir.path().join("entry");
1016        std::fs::File::create_new(&path).expect("create");
1017
1018        watcher.watch(
1019            &path,
1020            WatchMode {
1021                recursive_mode: RecursiveMode::NonRecursive,
1022                target_mode: TargetMode::NoTrack,
1023            },
1024        );
1025        watcher.watcher.wait_next_scan().expect("wait next scan");
1026
1027        let new_path = tmpdir.path().join("renamed");
1028
1029        std::fs::rename(&path, &new_path).expect("rename");
1030
1031        rx.sleep_while_exists(&path);
1032        rx.sleep_until_exists(&new_path);
1033
1034        #[cfg(target_family = "wasm")]
1035        sleep(Duration::from_millis(100));
1036
1037        rx.wait_unordered_exact([
1038            expected(&path).modify_data_any().optional(),
1039            expected(&path).remove_file(),
1040        ])
1041        .ensure_no_tail();
1042
1043        let result = watcher.watcher.watch(
1044            &path,
1045            WatchMode {
1046                recursive_mode: RecursiveMode::NonRecursive,
1047                target_mode: TargetMode::NoTrack,
1048            },
1049        );
1050        assert!(matches!(
1051            result,
1052            Err(Error {
1053                paths: _,
1054                kind: ErrorKind::PathNotFound
1055            })
1056        ));
1057    }
1058
1059    #[test]
1060    fn delete_file() {
1061        let tmpdir = testdir();
1062        let (mut watcher, rx) = watcher();
1063        let path = tmpdir.path().join("entry");
1064        std::fs::File::create_new(&path).expect("Unable to create");
1065
1066        rx.sleep_until_parent_contains(&path);
1067
1068        watcher.watch_recursively(&tmpdir);
1069        watcher.watcher.wait_next_scan().expect("wait next scan");
1070
1071        std::fs::remove_file(&path).expect("Unable to remove");
1072
1073        rx.sleep_while_exists(&path);
1074        rx.sleep_while_parent_contains(&path);
1075
1076        rx.wait_unordered_exact([
1077            expected(&path).modify_data_any().optional(),
1078            expected(&path).remove_file(),
1079            expected(tmpdir.path()).modify_meta_mtime().optional(),
1080        ]);
1081    }
1082
1083    #[test]
1084    fn delete_self_file() {
1085        let tmpdir = testdir();
1086        let (mut watcher, rx) = watcher();
1087        let path = tmpdir.path().join("entry");
1088        std::fs::File::create_new(&path).expect("Unable to create");
1089
1090        watcher.watch_nonrecursively(&path);
1091        watcher.watcher.wait_next_scan().expect("wait next scan");
1092
1093        std::fs::remove_file(&path).expect("Unable to remove");
1094
1095        rx.sleep_while_exists(&path);
1096        rx.wait_ordered_exact([
1097            expected(&path).modify_data_any().optional(),
1098            expected(&path).remove_file(),
1099        ]);
1100
1101        std::fs::write(&path, "").expect("write");
1102
1103        rx.sleep_until_exists(&path);
1104        rx.wait_ordered_exact([expected(&path).create_file()]);
1105    }
1106
1107    #[test]
1108    fn delete_self_file_no_track() {
1109        let tmpdir = testdir();
1110        let (mut watcher, rx) = watcher();
1111        let path = tmpdir.path().join("entry");
1112        std::fs::File::create_new(&path).expect("Unable to create");
1113
1114        watcher.watch(
1115            &path,
1116            WatchMode {
1117                recursive_mode: RecursiveMode::NonRecursive,
1118                target_mode: TargetMode::NoTrack,
1119            },
1120        );
1121        watcher.watcher.wait_next_scan().expect("wait next scan");
1122
1123        std::fs::remove_file(&path).expect("Unable to remove");
1124
1125        rx.sleep_while_exists(&path);
1126        rx.wait_ordered_exact([
1127            expected(&path).modify_data_any().optional(),
1128            expected(&path).remove_file(),
1129        ]);
1130
1131        #[cfg(target_family = "wasm")]
1132        sleep(Duration::from_millis(100));
1133
1134        std::fs::write(&path, "").expect("write");
1135
1136        rx.ensure_empty_with_wait();
1137    }
1138
1139    #[test]
1140    fn create_write_overwrite() {
1141        let tmpdir = testdir();
1142        let (mut watcher, rx) = watcher();
1143        let overwritten_file = tmpdir.path().join("overwritten_file");
1144        let overwriting_file = tmpdir.path().join("overwriting_file");
1145        std::fs::write(&overwritten_file, "123").expect("write1");
1146
1147        rx.sleep_until_parent_contains(&overwritten_file);
1148        rx.sleep_until_exists(&overwritten_file);
1149
1150        watcher.watch_nonrecursively(&tmpdir);
1151        watcher.watcher.wait_next_scan().expect("wait next scan");
1152
1153        std::fs::File::create(&overwriting_file).expect("create");
1154        std::fs::write(&overwriting_file, "321").expect("write2");
1155        std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1156
1157        rx.sleep_while_exists(&overwriting_file);
1158        rx.sleep_while_parent_contains(&overwriting_file);
1159
1160        assert!(
1161            rx.sleep_until(
1162                || std::fs::read_to_string(&overwritten_file).is_ok_and(|cnt| cnt == "321")
1163            ),
1164            "file {overwritten_file:?} was not replaced"
1165        );
1166
1167        rx.wait_unordered([expected(&overwritten_file).modify()]);
1168    }
1169}