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::{
828        Error, ErrorKind, RecursiveMode, TargetMode, WatchMode, Watcher, event::EventKind, test::*,
829    };
830
831    fn watcher() -> (TestWatcher<PollWatcher>, Receiver) {
832        poll_watcher_channel()
833    }
834
835    #[test]
836    fn poll_watcher_is_send_and_sync() {
837        fn check<T: Send + Sync>() {}
838        check::<PollWatcher>();
839    }
840
841    #[test]
842    fn create_file() {
843        let tmpdir = testdir();
844        let (mut watcher, rx) = watcher();
845        watcher.watch_recursively(&tmpdir);
846        watcher.watcher.wait_next_scan().expect("wait next scan");
847
848        let path = tmpdir.path().join("entry");
849        std::fs::File::create_new(&path).expect("Unable to create");
850
851        rx.sleep_until_parent_contains(&path);
852        rx.sleep_until_exists(&path);
853
854        rx.wait_unordered_exact([
855            expected(&path).create_file(),
856            expected(tmpdir.path()).modify_meta_mtime().optional(),
857        ]);
858    }
859
860    #[test]
861    fn create_self_file() {
862        let tmpdir = testdir();
863        let (mut watcher, rx) = watcher();
864
865        let path = tmpdir.path().join("entry");
866
867        watcher.watch_nonrecursively(&path);
868        watcher.watcher.wait_next_scan().expect("wait next scan");
869
870        std::fs::File::create_new(&path).expect("create");
871
872        rx.sleep_until_exists(&path);
873        rx.wait_ordered_exact([expected(&path).create_file()]);
874    }
875
876    #[test]
877    fn create_self_file_no_track() {
878        let tmpdir = testdir();
879        let (mut watcher, _) = watcher();
880
881        let path = tmpdir.path().join("entry");
882
883        let result = watcher.watcher.watch(
884            &path,
885            WatchMode {
886                recursive_mode: RecursiveMode::NonRecursive,
887                target_mode: TargetMode::NoTrack,
888            },
889        );
890        assert!(matches!(
891            result,
892            Err(Error {
893                paths: _,
894                kind: ErrorKind::PathNotFound
895            })
896        ));
897    }
898
899    #[test]
900    fn create_self_file_nested() {
901        let tmpdir = testdir();
902        let (mut watcher, rx) = watcher();
903
904        let path = tmpdir.path().join("entry/nested");
905
906        watcher.watch_nonrecursively(&path);
907        watcher.watcher.wait_next_scan().expect("wait next scan");
908
909        std::fs::create_dir_all(path.parent().unwrap()).expect("create");
910        std::fs::File::create_new(&path).expect("create");
911
912        rx.wait_ordered_exact([expected(&path).create_file()]);
913    }
914
915    #[test]
916    fn create_dir() {
917        let tmpdir = testdir();
918        let (mut watcher, rx) = watcher();
919        watcher.watch_recursively(&tmpdir);
920        watcher.watcher.wait_next_scan().expect("wait next scan");
921
922        let path = tmpdir.path().join("entry");
923        std::fs::create_dir(&path).expect("Unable to create");
924
925        rx.sleep_until_parent_contains(&path);
926        rx.sleep_until_exists(&path);
927
928        rx.wait_unordered_exact([
929            expected(&path).create_folder(),
930            expected(tmpdir.path()).modify_meta_mtime().optional(),
931        ]);
932    }
933
934    #[test]
935    fn modify_file() {
936        let tmpdir = testdir();
937        let (mut watcher, rx) = watcher();
938        let path = tmpdir.path().join("entry");
939        std::fs::File::create_new(&path).expect("Unable to create");
940
941        rx.sleep_until_parent_contains(&path);
942
943        watcher.watch_recursively(&tmpdir);
944        watcher.watcher.wait_next_scan().expect("wait next scan");
945        std::fs::write(&path, b"123").expect("Unable to write");
946
947        assert!(
948            rx.sleep_until(|| std::fs::read_to_string(&path).is_ok_and(|content| content == "123")),
949            "the file wasn't modified"
950        );
951        rx.wait_unordered_exact([expected(&path).modify().multiple()]);
952    }
953
954    #[test]
955    fn rename_file() {
956        let tmpdir = testdir();
957        let (mut watcher, rx) = watcher();
958        let path = tmpdir.path().join("entry");
959        let new_path = tmpdir.path().join("new_entry");
960        std::fs::File::create_new(&path).expect("Unable to create");
961
962        rx.sleep_until_parent_contains(&path);
963
964        watcher.watch_recursively(&tmpdir);
965
966        watcher.watcher.wait_next_scan().expect("wait next scan");
967        std::fs::rename(&path, &new_path).expect("Unable to remove");
968
969        rx.sleep_while_exists(&path);
970        rx.sleep_until_exists(&new_path);
971
972        rx.sleep_while_parent_contains(&path);
973        rx.sleep_until_parent_contains(&new_path);
974
975        rx.wait_unordered_exact([
976            expected(&path).remove_file(),
977            expected(&new_path).create_file(),
978            expected(tmpdir.path()).modify_meta_mtime().optional(),
979        ]);
980    }
981
982    #[test]
983    fn rename_self_file() {
984        let tmpdir = testdir();
985        let (mut watcher, rx) = watcher();
986
987        let path = tmpdir.path().join("entry");
988        std::fs::File::create_new(&path).expect("create");
989
990        watcher.watch_nonrecursively(&path);
991        watcher.watcher.wait_next_scan().expect("wait next scan");
992        let new_path = tmpdir.path().join("renamed");
993
994        std::fs::rename(&path, &new_path).expect("rename");
995
996        rx.sleep_while_exists(&path);
997        rx.sleep_until_exists(&new_path);
998
999        rx.wait_unordered_exact([expected(&path).remove_file()])
1000            .ensure_no_tail();
1001
1002        std::fs::rename(&new_path, &path).expect("rename2");
1003        watcher.watcher.wait_next_scan().expect("wait next scan");
1004
1005        rx.sleep_while_exists(&new_path);
1006        rx.sleep_until_exists(&path);
1007
1008        rx.wait_unordered_exact([expected(&path).create_file()])
1009            .ensure_no_tail();
1010    }
1011
1012    #[test]
1013    fn rename_self_file_no_track() {
1014        let tmpdir = testdir();
1015        let (mut watcher, rx) = watcher();
1016
1017        let path = tmpdir.path().join("entry");
1018        std::fs::File::create_new(&path).expect("create");
1019
1020        watcher.watch(
1021            &path,
1022            WatchMode {
1023                recursive_mode: RecursiveMode::NonRecursive,
1024                target_mode: TargetMode::NoTrack,
1025            },
1026        );
1027        watcher.watcher.wait_next_scan().expect("wait next scan");
1028
1029        let new_path = tmpdir.path().join("renamed");
1030
1031        std::fs::rename(&path, &new_path).expect("rename");
1032
1033        rx.sleep_while_exists(&path);
1034        rx.sleep_until_exists(&new_path);
1035
1036        #[cfg(target_family = "wasm")]
1037        sleep(Duration::from_millis(100));
1038
1039        rx.wait_unordered_exact([
1040            expected(&path).modify_data_any().optional(),
1041            expected(&path).remove_file(),
1042        ])
1043        .ensure_no_tail();
1044
1045        let result = watcher.watcher.watch(
1046            &path,
1047            WatchMode {
1048                recursive_mode: RecursiveMode::NonRecursive,
1049                target_mode: TargetMode::NoTrack,
1050            },
1051        );
1052        assert!(matches!(
1053            result,
1054            Err(Error {
1055                paths: _,
1056                kind: ErrorKind::PathNotFound
1057            })
1058        ));
1059    }
1060
1061    #[test]
1062    fn delete_file() {
1063        let tmpdir = testdir();
1064        let (mut watcher, rx) = watcher();
1065        let path = tmpdir.path().join("entry");
1066        std::fs::File::create_new(&path).expect("Unable to create");
1067
1068        rx.sleep_until_parent_contains(&path);
1069
1070        watcher.watch_recursively(&tmpdir);
1071        watcher.watcher.wait_next_scan().expect("wait next scan");
1072
1073        std::fs::remove_file(&path).expect("Unable to remove");
1074
1075        rx.sleep_while_exists(&path);
1076        rx.sleep_while_parent_contains(&path);
1077
1078        rx.wait_unordered_exact([
1079            expected(&path).modify_data_any().optional(),
1080            expected(&path).remove_file(),
1081            expected(tmpdir.path()).modify_meta_mtime().optional(),
1082        ]);
1083    }
1084
1085    #[test]
1086    fn delete_self_file() {
1087        let tmpdir = testdir();
1088        let (mut watcher, rx) = watcher();
1089        let path = tmpdir.path().join("entry");
1090        std::fs::File::create_new(&path).expect("Unable to create");
1091
1092        watcher.watch_nonrecursively(&path);
1093        watcher.watcher.wait_next_scan().expect("wait next scan");
1094
1095        std::fs::remove_file(&path).expect("Unable to remove");
1096
1097        rx.sleep_while_exists(&path);
1098        rx.wait_ordered_exact([
1099            expected(&path).modify_data_any().optional(),
1100            expected(&path).remove_file(),
1101        ]);
1102
1103        std::fs::write(&path, "").expect("write");
1104
1105        rx.sleep_until_exists(&path);
1106        rx.wait_ordered_exact([expected(&path).create_file()]);
1107    }
1108
1109    #[test]
1110    fn delete_self_file_no_track() {
1111        let tmpdir = testdir();
1112        let (mut watcher, rx) = watcher();
1113        let path = tmpdir.path().join("entry");
1114        std::fs::File::create_new(&path).expect("Unable to create");
1115
1116        watcher.watch(
1117            &path,
1118            WatchMode {
1119                recursive_mode: RecursiveMode::NonRecursive,
1120                target_mode: TargetMode::NoTrack,
1121            },
1122        );
1123        watcher.watcher.wait_next_scan().expect("wait next scan");
1124
1125        std::fs::remove_file(&path).expect("Unable to remove");
1126
1127        rx.sleep_while_exists(&path);
1128        rx.wait_ordered_exact([
1129            expected(&path).modify_data_any().optional(),
1130            expected(&path).remove_file(),
1131        ]);
1132
1133        #[cfg(target_family = "wasm")]
1134        sleep(Duration::from_millis(100));
1135
1136        std::fs::write(&path, "").expect("write");
1137
1138        rx.ensure_empty_with_wait();
1139    }
1140
1141    #[test]
1142    fn create_write_overwrite() {
1143        let tmpdir = testdir();
1144        let (mut watcher, rx) = watcher();
1145        let overwritten_file = tmpdir.path().join("overwritten_file");
1146        let overwriting_file = tmpdir.path().join("overwriting_file");
1147        std::fs::write(&overwritten_file, "123").expect("write1");
1148
1149        rx.sleep_until_parent_contains(&overwritten_file);
1150        rx.sleep_until_exists(&overwritten_file);
1151
1152        watcher.watch_nonrecursively(&tmpdir);
1153        watcher.watcher.wait_next_scan().expect("wait next scan");
1154
1155        std::fs::File::create(&overwriting_file).expect("create");
1156        std::fs::write(&overwriting_file, "321").expect("write2");
1157        std::fs::rename(&overwriting_file, &overwritten_file).expect("rename");
1158
1159        rx.sleep_while_exists(&overwriting_file);
1160        rx.sleep_while_parent_contains(&overwriting_file);
1161
1162        assert!(
1163            rx.sleep_until(
1164                || std::fs::read_to_string(&overwritten_file).is_ok_and(|cnt| cnt == "321")
1165            ),
1166            "file {overwritten_file:?} was not replaced"
1167        );
1168
1169        rx.wait_unordered([expected(&overwritten_file).modify()]);
1170    }
1171
1172    fn assert_track_path_continues_after_recreating_file_in_nested_directory(
1173        upgrade_from_no_track: bool,
1174    ) {
1175        let tmpdir = testdir();
1176        let (mut watcher, mut rx) = watcher();
1177        let nested_dir = tmpdir.path().join("nested");
1178        let watched_file = nested_dir.join("watched");
1179        let moved_file = tmpdir.path().join("moved");
1180        std::fs::create_dir(&nested_dir).expect("create nested dir");
1181        std::fs::write(&watched_file, "initial").expect("write watched file");
1182
1183        watcher.watch_nonrecursively(&tmpdir);
1184        if upgrade_from_no_track {
1185            watcher.watch(
1186                &watched_file,
1187                WatchMode {
1188                    recursive_mode: RecursiveMode::NonRecursive,
1189                    target_mode: TargetMode::NoTrack,
1190                },
1191            );
1192        }
1193        watcher.watch_nonrecursively(&watched_file);
1194
1195        std::fs::rename(&watched_file, &moved_file).expect("move watched file");
1196        std::fs::copy(&moved_file, &watched_file).expect("recreate watched file");
1197        std::fs::remove_file(&moved_file).expect("remove moved file");
1198
1199        // Complete a scan, then drain the replacement events before checking the next write.
1200        watcher.watcher.poll().expect("scan replacement");
1201        watcher.watcher.wait_next_scan().expect("wait for scan");
1202        for _ in rx.iter() {}
1203
1204        std::fs::write(&watched_file, "updated").expect("update watched file");
1205        watcher.watcher.poll().expect("scan update");
1206        watcher.watcher.wait_next_scan().expect("wait for scan");
1207        let received_change = rx.iter().any(|event| {
1208            event.paths.iter().any(|path| path == &watched_file)
1209                && matches!(event.kind, EventKind::Create(_) | EventKind::Modify(_))
1210        });
1211
1212        assert!(
1213            received_change,
1214            "expected a change event after recreating the watched file"
1215        );
1216    }
1217
1218    #[test]
1219    fn track_path_continues_after_recreating_file_in_nested_directory() {
1220        assert_track_path_continues_after_recreating_file_in_nested_directory(false);
1221    }
1222
1223    #[test]
1224    fn track_path_upgrade_continues_after_recreating_file_in_nested_directory() {
1225        assert_track_path_continues_after_recreating_file_in_nested_directory(true);
1226    }
1227}