Skip to main content

extended_notify/
lib.rs

1//! Extended filesystem watcher built on [notify].
2//!
3//! This crate extends notify with several features:
4//!
5//! - **Watch non-existent paths**: When you watch a path that doesn't exist,
6//!   the nearest existing ancestor is watched instead, and polling detects
7//!   when the target path comes into existence. This is useful for watching
8//!   config files that may be created later, or paths on removable media.
9//!
10//! - **Interest-based filtering**: Rather than receiving all events and
11//!   filtering yourself, specify exactly which event types matter to you.
12//!   Interests are hierarchical—subscribing to [`Interest::Delete`] receives
13//!   all deletion events, while [`Interest::DeleteFile`] only receives file
14//!   deletions.
15//!
16//! - **RAII watch handles**: The [`Watched`] handle returned by [`Watcher::add`]
17//!   automatically stops the watch when dropped. No manual cleanup required.
18//!
19//! - **Synthetic events**: When a non-existent path comes into existence (or
20//!   vice versa), synthetic [`Interest::Create`] and [`Interest::Delete`]
21//!   events are generated if you've subscribed to them.
22//!
23//! - **Establishment notification**: Subscribe to [`Interest::Established`] to
24//!   receive a synthetic event when the watch is first set up, useful for
25//!   triggering an initial read of the watched path.
26//!
27//! - **Multiple Watches**: Multiple watches for the same file or
28//!   directory are handled gracefully. Both watches will behave
29//!   correctly, but only one `Notify` watch will be created.
30//!
31//! # Example
32//!
33//! ```no_run
34//! use extended_notify::{ArcPath, EventBatch, EventHandler, Interest, WatcherConfigBuilder};
35//! use anyhow::Result;
36//! use enumflags2::make_bitflags;
37//!
38//! #[derive(Clone)]
39//! struct MyHandler;
40//!
41//! impl EventHandler for MyHandler {
42//!     async fn handle_event(&mut self, batch: EventBatch) -> Result<()> {
43//!         for (id, event) in batch.iter() {
44//!             println!("{:?}: {:?}", id, event);
45//!         }
46//!         Ok(())
47//!     }
48//! }
49//!
50//! # async fn example() -> Result<()> {
51//! let watcher = WatcherConfigBuilder::default()
52//!     .event_handler(MyHandler)
53//!     .build()?
54//!     .start()?;
55//!
56//! // Watch a path that may or may not exist yet
57//! let interest = make_bitflags!(Interest::{Create | Delete | Modify});
58//! let handle = watcher.add(ArcPath::from("/tmp/my-config"), interest)?;
59//!
60//! // Events flow to MyHandler::handle_event
61//! // Watch stops when handle is dropped
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! # Event Delivery
67//!
68//! Events are delivered in batches through the [`EventHandler`] trait. Each
69//! batch contains `(Id, Event)` pairs, where the [`Id`] identifies which watch
70//! generated the event. Multiple events for the same watch may be coalesced
71//! into a single [`Event`] with multiple paths.
72//!
73//! If your handler returns an error, the watcher task stops and all subsequent
74//! operations on the [`Watcher`] will fail.
75//!
76//! # Polling
77//!
78//! Polling runs alongside native filesystem notifications to handle cases
79//! notifications can't cover—primarily detecting when non-existent paths come
80//! into existence. By default, 100 paths are checked each second. Adjust with
81//! [`Watcher::set_poll_interval`] and [`Watcher::set_poll_batch`], or disable
82//! polling entirely by setting the batch size to 0.
83//!
84//! If you disable polling creation of a file and multiple levels of
85//! directories may not be detected. For example if you watch a file
86//! /foo/bar/baz that does not exist, and you disable polling, if only
87//! /foo exists to start, and /foo/bar is created, then /foo/bar/baz
88//! is created, you may not get the create event for /foo/bar/baz.
89
90use anyhow::{bail, Result};
91use derive_builder::Builder;
92use enumflags2::{bitflags, BitFlags};
93use fxhash::FxHashSet;
94use notify::event::{
95    AccessKind, CreateKind, DataChange, MetadataKind, ModifyKind, RemoveKind, RenameMode,
96};
97use notify_debouncer_full::{DebounceEventHandler, DebounceEventResult};
98use poolshark::global::GPooled;
99use std::{
100    borrow::Borrow,
101    hash::Hash,
102    ops::Deref,
103    path::{self, Path, PathBuf},
104    sync::{Arc, LazyLock},
105    time::Duration,
106};
107use tokio::{sync::mpsc, task};
108use watch_task::MAX_NOTIFY_BATCH;
109
110mod watch_task;
111
112#[cfg(test)]
113mod test;
114
115pub const MIN_POLL_INTERVAL: Duration = Duration::from_millis(100);
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct Id(u64);
119
120impl Id {
121    fn new() -> Self {
122        use std::sync::atomic::{AtomicU64, Ordering};
123        static NEXT: AtomicU64 = AtomicU64::new(0);
124        Self(NEXT.fetch_add(1, Ordering::Relaxed))
125    }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub struct ArcPath(Arc<PathBuf>);
130
131impl ArcPath {
132    pub fn get_mut(&mut self) -> Option<&mut PathBuf> {
133        Arc::get_mut(&mut self.0)
134    }
135
136    pub fn make_mut(&mut self) -> &mut PathBuf {
137        Arc::make_mut(&mut self.0)
138    }
139
140    pub fn root() -> Self {
141        static ROOT: LazyLock<ArcPath> =
142            LazyLock::new(|| ArcPath::from(path::MAIN_SEPARATOR_STR));
143        ROOT.clone()
144    }
145}
146
147impl AsRef<Path> for ArcPath {
148    fn as_ref(&self) -> &Path {
149        &*self.0
150    }
151}
152
153impl Deref for ArcPath {
154    type Target = PathBuf;
155
156    fn deref(&self) -> &Self::Target {
157        &*self.0
158    }
159}
160
161impl Borrow<Path> for ArcPath {
162    fn borrow(&self) -> &Path {
163        &*self.0
164    }
165}
166
167impl From<&Path> for ArcPath {
168    fn from(value: &Path) -> Self {
169        Self(Arc::new(value.into()))
170    }
171}
172
173impl From<&str> for ArcPath {
174    fn from(value: &str) -> Self {
175        Self(Arc::new(PathBuf::from(value)))
176    }
177}
178
179impl From<PathBuf> for ArcPath {
180    fn from(value: PathBuf) -> Self {
181        Self(Arc::new(value))
182    }
183}
184
185impl From<&PathBuf> for ArcPath {
186    fn from(value: &PathBuf) -> Self {
187        Self(Arc::new(value.clone()))
188    }
189}
190
191impl From<&ArcPath> for ArcPath {
192    fn from(value: &ArcPath) -> Self {
193        value.clone()
194    }
195}
196
197impl PartialEq<Path> for ArcPath {
198    fn eq(&self, other: &Path) -> bool {
199        &*self.0 == other
200    }
201}
202
203impl PartialOrd<Path> for ArcPath {
204    fn partial_cmp(&self, other: &Path) -> Option<std::cmp::Ordering> {
205        (**self.0).partial_cmp(other)
206    }
207}
208
209impl PartialEq<PathBuf> for ArcPath {
210    fn eq(&self, other: &PathBuf) -> bool {
211        &*self.0 == other
212    }
213}
214
215impl PartialOrd<PathBuf> for ArcPath {
216    fn partial_cmp(&self, other: &PathBuf) -> Option<std::cmp::Ordering> {
217        (**self.0).partial_cmp(other)
218    }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
222#[bitflags]
223#[repr(u64)]
224pub enum Interest {
225    /// Synthetic event fired when a watch is first established.
226    ///
227    /// This is not a filesystem event—it's generated immediately when the
228    /// watch is set up, regardless of whether the path exists. Useful for
229    /// triggering an initial read of watched files.
230    Established,
231    /// Matches any event type.
232    Any,
233    Access,
234    AccessOpen,
235    AccessClose,
236    AccessRead,
237    AccessOther,
238    Create,
239    CreateFile,
240    CreateFolder,
241    CreateOther,
242    Modify,
243    ModifyData,
244    ModifyDataSize,
245    ModifyDataContent,
246    ModifyDataOther,
247    ModifyMetadata,
248    ModifyMetadataAccessTime,
249    ModifyMetadataWriteTime,
250    ModifyMetadataPermissions,
251    ModifyMetadataOwnership,
252    ModifyMetadataExtended,
253    ModifyMetadataOther,
254    ModifyRename,
255    ModifyRenameTo,
256    ModifyRenameFrom,
257    ModifyRenameBoth,
258    ModifyRenameOther,
259    ModifyOther,
260    Delete,
261    DeleteFile,
262    DeleteFolder,
263    DeleteOther,
264    Other,
265}
266
267impl From<&notify::EventKind> for Interest {
268    fn from(kind: &notify::EventKind) -> Self {
269        match kind {
270            notify::EventKind::Any => Self::Any,
271            notify::EventKind::Access(AccessKind::Any) => Self::Access,
272            notify::EventKind::Access(AccessKind::Close(_)) => Self::AccessClose,
273            notify::EventKind::Access(AccessKind::Open(_)) => Self::AccessOpen,
274            notify::EventKind::Access(AccessKind::Read) => Self::AccessRead,
275            notify::EventKind::Access(AccessKind::Other) => Self::AccessOther,
276            notify::EventKind::Create(CreateKind::Any) => Self::Create,
277            notify::EventKind::Create(CreateKind::File) => Self::CreateFile,
278            notify::EventKind::Create(CreateKind::Folder) => Self::CreateFolder,
279            notify::EventKind::Create(CreateKind::Other) => Self::CreateOther,
280            notify::EventKind::Modify(ModifyKind::Any) => Self::Modify,
281            notify::EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {
282                Self::ModifyData
283            }
284            notify::EventKind::Modify(ModifyKind::Data(DataChange::Content)) => {
285                Self::ModifyDataContent
286            }
287            notify::EventKind::Modify(ModifyKind::Data(DataChange::Size)) => {
288                Self::ModifyDataSize
289            }
290            notify::EventKind::Modify(ModifyKind::Data(DataChange::Other)) => {
291                Self::ModifyDataOther
292            }
293            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => {
294                Self::ModifyMetadata
295            }
296            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::AccessTime)) => {
297                Self::ModifyMetadataAccessTime
298            }
299            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Extended)) => {
300                Self::ModifyMetadataExtended
301            }
302            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other)) => {
303                Self::ModifyMetadataOther
304            }
305            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Ownership)) => {
306                Self::ModifyMetadataOwnership
307            }
308            notify::EventKind::Modify(ModifyKind::Metadata(
309                MetadataKind::Permissions,
310            )) => Self::ModifyMetadataPermissions,
311            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime)) => {
312                Self::ModifyMetadataWriteTime
313            }
314            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Any)) => {
315                Self::ModifyRename
316            }
317            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
318                Self::ModifyRenameBoth
319            }
320            notify::EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
321                Self::ModifyRenameFrom
322            }
323            notify::EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
324                Self::ModifyRenameTo
325            }
326            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Other)) => {
327                Self::ModifyRenameOther
328            }
329            notify::EventKind::Modify(ModifyKind::Other) => Self::ModifyOther,
330            notify::EventKind::Remove(RemoveKind::Any) => Self::Delete,
331            notify::EventKind::Remove(RemoveKind::File) => Self::DeleteFile,
332            notify::EventKind::Remove(RemoveKind::Folder) => Self::DeleteFolder,
333            notify::EventKind::Remove(RemoveKind::Other) => Self::DeleteOther,
334            notify::EventKind::Other => Self::Other,
335        }
336    }
337}
338
339#[derive(Debug, Clone)]
340struct Watch {
341    path: ArcPath,
342    id: Id,
343    interest: BitFlags<Interest>,
344}
345
346impl Watch {
347    fn interested(&self, kind: &notify::EventKind) -> bool {
348        use Interest::*;
349        if self.interest.contains(Any) {
350            return true;
351        }
352        match kind {
353            notify::EventKind::Any => !self.interest.is_empty(),
354            notify::EventKind::Access(AccessKind::Any) => self
355                .interest
356                .intersects(Access | AccessClose | AccessOpen | AccessRead | AccessOther),
357            notify::EventKind::Access(AccessKind::Close(_)) => {
358                self.interest.intersects(Access | AccessClose)
359            }
360            notify::EventKind::Access(AccessKind::Open(_)) => {
361                self.interest.intersects(Access | AccessOpen)
362            }
363            notify::EventKind::Access(AccessKind::Read) => {
364                self.interest.intersects(Access | AccessRead)
365            }
366            notify::EventKind::Access(AccessKind::Other) => {
367                self.interest.intersects(Access | AccessOther)
368            }
369            notify::EventKind::Create(CreateKind::Any) => {
370                self.interest.intersects(Create | CreateFile | CreateFolder | CreateOther)
371            }
372            notify::EventKind::Create(CreateKind::File) => {
373                self.interest.intersects(Create | CreateFile)
374            }
375            notify::EventKind::Create(CreateKind::Folder) => {
376                self.interest.intersects(Create | CreateFolder)
377            }
378            notify::EventKind::Create(CreateKind::Other) => {
379                self.interest.intersects(Create | CreateOther)
380            }
381            notify::EventKind::Modify(ModifyKind::Any) => self.interest.intersects(
382                Modify
383                    | ModifyData
384                    | ModifyDataSize
385                    | ModifyDataContent
386                    | ModifyDataOther
387                    | ModifyMetadata
388                    | ModifyMetadataAccessTime
389                    | ModifyMetadataWriteTime
390                    | ModifyMetadataPermissions
391                    | ModifyMetadataOwnership
392                    | ModifyMetadataExtended
393                    | ModifyMetadataOther
394                    | ModifyRename
395                    | ModifyRenameTo
396                    | ModifyRenameFrom
397                    | ModifyRenameBoth
398                    | ModifyRenameOther
399                    | ModifyOther,
400            ),
401            notify::EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {
402                self.interest.intersects(
403                    Modify
404                        | ModifyData
405                        | ModifyDataSize
406                        | ModifyDataContent
407                        | ModifyDataOther,
408                )
409            }
410            notify::EventKind::Modify(ModifyKind::Data(DataChange::Content)) => {
411                self.interest.intersects(Modify | ModifyData | ModifyDataContent)
412            }
413            notify::EventKind::Modify(ModifyKind::Data(DataChange::Size)) => {
414                self.interest.intersects(Modify | ModifyData | ModifyDataSize)
415            }
416            notify::EventKind::Modify(ModifyKind::Data(DataChange::Other)) => {
417                self.interest.intersects(Modify | ModifyData | ModifyDataOther)
418            }
419            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)) => {
420                self.interest.intersects(
421                    Modify
422                        | ModifyMetadata
423                        | ModifyMetadataAccessTime
424                        | ModifyMetadataWriteTime
425                        | ModifyMetadataPermissions
426                        | ModifyMetadataOwnership
427                        | ModifyMetadataExtended
428                        | ModifyMetadataOther,
429                )
430            }
431            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::AccessTime)) => {
432                self.interest
433                    .intersects(Modify | ModifyMetadata | ModifyMetadataAccessTime)
434            }
435            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Extended)) => {
436                self.interest.intersects(Modify | ModifyMetadata | ModifyMetadataExtended)
437            }
438            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other)) => {
439                self.interest.intersects(Modify | ModifyMetadata | ModifyMetadataOther)
440            }
441            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::Ownership)) => {
442                self.interest
443                    .intersects(Modify | ModifyMetadata | ModifyMetadataOwnership)
444            }
445            notify::EventKind::Modify(ModifyKind::Metadata(
446                MetadataKind::Permissions,
447            )) => self
448                .interest
449                .intersects(Modify | ModifyMetadata | ModifyMetadataPermissions),
450            notify::EventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime)) => {
451                self.interest
452                    .intersects(Modify | ModifyMetadata | ModifyMetadataWriteTime)
453            }
454            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Any)) => {
455                self.interest.intersects(
456                    Modify
457                        | ModifyRename
458                        | ModifyRenameTo
459                        | ModifyRenameFrom
460                        | ModifyRenameBoth
461                        | ModifyRenameOther,
462                )
463            }
464            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
465                self.interest.intersects(Modify | ModifyRename | ModifyRenameBoth)
466            }
467            notify::EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
468                self.interest.intersects(Modify | ModifyRename | ModifyRenameFrom)
469            }
470            notify::EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
471                self.interest.intersects(Modify | ModifyRename | ModifyRenameTo)
472            }
473            notify::EventKind::Modify(ModifyKind::Name(RenameMode::Other)) => {
474                self.interest.intersects(Modify | ModifyRename | ModifyRenameOther)
475            }
476            notify::EventKind::Modify(ModifyKind::Other) => {
477                self.interest.intersects(Modify | ModifyOther)
478            }
479            notify::EventKind::Remove(RemoveKind::Any) => {
480                self.interest.intersects(Delete | DeleteFile | DeleteFolder | DeleteOther)
481            }
482            notify::EventKind::Remove(RemoveKind::File) => {
483                self.interest.intersects(Delete | DeleteFile)
484            }
485            notify::EventKind::Remove(RemoveKind::Folder) => {
486                self.interest.intersects(Delete | DeleteFolder)
487            }
488            notify::EventKind::Remove(RemoveKind::Other) => {
489                self.interest.intersects(Delete | DeleteOther)
490            }
491            notify::EventKind::Other => self.interest.contains(Other),
492        }
493    }
494}
495
496#[derive(Debug)]
497enum Cmd {
498    Watch(Watch),
499    Stop(Id),
500    SetPollInterval(Duration),
501    SetPollBatch(usize),
502}
503
504struct NotifyChan(mpsc::Sender<DebounceEventResult>);
505
506impl DebounceEventHandler for NotifyChan {
507    fn handle_event(&mut self, event: notify_debouncer_full::DebounceEventResult) {
508        let _ = self.0.blocking_send(event);
509    }
510}
511
512#[derive(Debug, Clone)]
513pub enum EventKind {
514    Error(Arc<anyhow::Error>),
515    Event(Interest),
516}
517
518#[derive(Debug)]
519pub struct Event {
520    pub paths: GPooled<FxHashSet<ArcPath>>,
521    pub event: EventKind,
522}
523
524/// A batch of events delivered to the [`EventHandler`].
525pub type EventBatch = GPooled<Vec<(Id, Event)>>;
526
527pub trait EventHandler: Send + 'static {
528    fn handle_event(
529        &mut self,
530        event: EventBatch,
531    ) -> impl Future<Output = Result<()>> + Send;
532}
533
534impl EventHandler for mpsc::Sender<EventBatch> {
535    fn handle_event(
536        &mut self,
537        event: EventBatch,
538    ) -> impl Future<Output = Result<()>> + Send {
539        async { Ok(self.send(event).await?) }
540    }
541}
542
543impl EventHandler for futures::channel::mpsc::Sender<EventBatch> {
544    fn handle_event(
545        &mut self,
546        event: EventBatch,
547    ) -> impl Future<Output = Result<()>> + Send {
548        use futures::SinkExt;
549        async { Ok(self.send(event).await?) }
550    }
551}
552
553/// A watched path
554///
555/// The watch will be stopped when this object is dropped.  This
556/// object is essentially the `Id`, you can use this as a key in a
557/// `HashMap` and look up the entry by `Id`
558#[derive(Debug)]
559pub struct Watched {
560    id: Id,
561    watcher: Watcher,
562}
563
564impl Watched {
565    /// Get the `Id` of this watched
566    pub fn id(&self) -> Id {
567        self.id
568    }
569}
570
571impl Borrow<Id> for Watched {
572    fn borrow(&self) -> &Id {
573        &self.id
574    }
575}
576
577impl<'a> Borrow<Id> for &'a Watched {
578    fn borrow(&self) -> &'a Id {
579        &self.id
580    }
581}
582
583impl PartialEq for Watched {
584    fn eq(&self, other: &Self) -> bool {
585        self.id == other.id
586    }
587}
588
589impl Eq for Watched {}
590
591impl PartialOrd for Watched {
592    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
593        Some(self.id.cmp(&other.id))
594    }
595}
596
597impl Ord for Watched {
598    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
599        self.id.cmp(&other.id)
600    }
601}
602
603impl Hash for Watched {
604    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
605        self.id.hash(state)
606    }
607}
608
609impl Drop for Watched {
610    fn drop(&mut self) {
611        let _ = self.watcher.0.send(Cmd::Stop(self.id));
612    }
613}
614
615#[derive(Debug, Clone, Builder)]
616pub struct WatcherConfig<T: EventHandler> {
617    /// The debounce timeout, events will not arrive faster than this. Default 250ms.
618    #[builder(default = "Duration::from_millis(250)")]
619    timeout: Duration,
620    /// How often the debouncer ticks. Default 1/4 of the timeout
621    #[builder(setter(strip_option), default)]
622    tick_rate: Option<Duration>,
623    /// The poll interval determines how often to poll a batch of
624    /// files. Polling is necessary to cover cases that filesystem
625    /// notifications can't handle, such as watching a path that
626    /// doesn't yet exist. Each poll interval, poll batch files will
627    /// be checked in parallel.
628    ///
629    /// The minimum poll interval is 100ms, an error will be returned
630    /// if you try to set the value lower than that.
631    ///
632    /// default 1 second
633    #[builder(default = "Duration::from_secs(1)")]
634    poll_interval: Duration,
635
636    /// How many files to poll each poll interval. If this is set to 0
637    /// polling is disabled.
638    ///
639    /// default 100
640    #[builder(default = "100")]
641    poll_batch: usize,
642    /// Where to send the events (required)
643    event_handler: T,
644}
645
646impl<T: EventHandler> WatcherConfig<T> {
647    /// Start the watcher
648    pub fn start(self) -> Result<Watcher> {
649        let (notify_tx, notify_rx) = mpsc::channel(MAX_NOTIFY_BATCH);
650        let watcher = notify_debouncer_full::new_debouncer(
651            self.timeout,
652            self.tick_rate,
653            NotifyChan(notify_tx),
654        )?;
655        let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
656        task::spawn(watch_task::watcher_loop(
657            self.poll_interval,
658            self.poll_batch,
659            watcher,
660            notify_rx,
661            cmd_rx,
662            self.event_handler,
663        ));
664        Ok(Watcher(cmd_tx))
665    }
666}
667
668/// An filesystem watcher
669///
670/// When this object is dropped the background task associated with it
671/// will stop. You may still receive some events on your provided
672/// event handler even after this, you can safely ignore them, or drop
673/// the handler.
674///
675/// If your provided event handler returns an error, the background
676/// task associated with this object will stop. From then on all the
677/// methods of this object will fail.
678#[derive(Debug, Clone)]
679pub struct Watcher(mpsc::UnboundedSender<Cmd>);
680
681impl Watcher {
682    /// Add a new watch
683    ///
684    /// Fails if the watcher task has died. Other errors will be sent
685    /// as events.
686    ///
687    /// The watch ends when the returned `Watched` is dropped. However
688    /// it cannot be guaranteed that no events will happen for a watch
689    /// after it has been dropped. You can handle this by ignoring
690    /// events with an unknown `Id`.
691    pub fn add(&self, path: ArcPath, interest: BitFlags<Interest>) -> Result<Watched> {
692        let id = Id::new();
693        self.0.send(Cmd::Watch(Watch { path, interest, id }))?;
694        Ok(Watched { id, watcher: self.clone() })
695    }
696
697    /// Set the poll interval
698    ///
699    /// The poll interval determines how often to poll a batch of
700    /// files. Polling is necessary to cover cases that filesystem
701    /// notifications can't handle, such as watching a path that
702    /// doesn't yet exist. Each poll interval, poll batch files will
703    /// be checked in parallel.
704    ///
705    /// The minimum poll interval is 100ms, an error will be returned
706    /// if you try to set the value lower than that.
707    pub fn set_poll_interval(&self, t: Duration) -> Result<()> {
708        if t < MIN_POLL_INTERVAL {
709            bail!("poll interval may not be less than {MIN_POLL_INTERVAL:?}")
710        }
711        Ok(self.0.send(Cmd::SetPollInterval(t))?)
712    }
713
714    /// Set the size of a poll batch
715    ///
716    /// How many files to poll each poll interval. If this is set to 0
717    /// polling is disabled.
718    pub fn set_poll_batch(&self, n: usize) -> Result<()> {
719        Ok(self.0.send(Cmd::SetPollBatch(n))?)
720    }
721}