Skip to main content

notify/
config.rs

1//! Configuration types
2
3use notify_types::event::EventKindMask;
4use std::{
5    path::{Path, PathBuf},
6    time::Duration,
7};
8
9/// Indicates whether only the provided directory or its sub-directories as well should be watched
10#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
11pub enum RecursiveMode {
12    /// Watch all sub-directories as well, including directories created after installing the watch
13    Recursive,
14
15    /// Watch only the provided directory
16    NonRecursive,
17}
18
19impl RecursiveMode {
20    pub(crate) fn is_recursive(&self) -> bool {
21        match *self {
22            RecursiveMode::Recursive => true,
23            RecursiveMode::NonRecursive => false,
24        }
25    }
26}
27
28/// Controls how path separators are represented in emitted event paths on Windows.
29///
30/// This applies to [`ReadDirectoryChangesWatcher`](crate::ReadDirectoryChangesWatcher).
31#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
32pub enum WindowsPathSeparatorStyle {
33    /// Infer separator style from each watched input path.
34    ///
35    /// For example, watching `C:/tmp` yields paths with `/`, while watching `C:\tmp`
36    /// yields paths with `\`.
37    #[default]
38    Auto,
39
40    /// Always use `/` in emitted event paths.
41    Slash,
42
43    /// Always use `\` in emitted event paths.
44    Backslash,
45}
46
47/// Watcher Backend configuration
48///
49/// This contains multiple settings that may relate to only one specific backend,
50/// such as to correctly configure each backend regardless of what is selected during runtime.
51///
52/// ```rust
53/// # use std::time::Duration;
54/// # use notify::Config;
55/// let config = Config::default()
56///     .with_poll_interval(Duration::from_secs(2))
57///     .with_compare_contents(true);
58/// ```
59///
60/// Some options can be changed during runtime, others have to be set when creating the watcher backend.
61#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
62pub struct Config {
63    /// See [Config::with_poll_interval]
64    poll_interval: Option<Duration>,
65
66    /// See [Config::with_compare_contents]
67    compare_contents: bool,
68
69    follow_symlinks: bool,
70
71    /// See [Config::with_event_kinds]
72    event_kinds: EventKindMask,
73
74    /// See [Config::with_windows_path_separator_style]
75    windows_path_separator_style: WindowsPathSeparatorStyle,
76
77    /// See [Config::with_fsevent_latency]
78    fsevent_latency: Duration,
79}
80
81impl Config {
82    /// For the [`PollWatcher`](crate::PollWatcher) backend.
83    ///
84    /// Interval between each re-scan attempt. This can be extremely expensive for large
85    /// file trees so it is recommended to measure and tune accordingly.
86    ///
87    /// The default poll frequency is 30 seconds.
88    ///
89    /// This will enable automatic polling, overwriting [`with_manual_polling()`](Config::with_manual_polling).
90    #[must_use]
91    pub fn with_poll_interval(mut self, dur: Duration) -> Self {
92        // TODO: v7.0 break signature to option
93        self.poll_interval = Some(dur);
94        self
95    }
96
97    /// Returns current setting
98    #[must_use]
99    pub fn poll_interval(&self) -> Option<Duration> {
100        // Changed Signature to Option
101        self.poll_interval
102    }
103
104    /// For the [`PollWatcher`](crate::PollWatcher) backend.
105    ///
106    /// Disable automatic polling. Requires calling [`crate::PollWatcher::poll()`] manually.
107    ///
108    /// This will disable automatic polling, overwriting [`with_poll_interval()`](Config::with_poll_interval).
109    #[must_use]
110    pub fn with_manual_polling(mut self) -> Self {
111        self.poll_interval = None;
112        self
113    }
114
115    /// For the [`PollWatcher`](crate::PollWatcher) backend.
116    ///
117    /// Optional feature that will evaluate the contents of changed files to determine if
118    /// they have indeed changed using a fast hashing algorithm.  This is especially important
119    /// for pseudo filesystems like those on Linux under /sys and /proc which are not obligated
120    /// to respect any other filesystem norms such as modification timestamps, file sizes, etc.
121    /// By enabling this feature, performance will be significantly impacted as all files will
122    /// need to be read and hashed at each `poll_interval`.
123    ///
124    /// This can't be changed during runtime. Off by default.
125    #[must_use]
126    pub fn with_compare_contents(mut self, compare_contents: bool) -> Self {
127        self.compare_contents = compare_contents;
128        self
129    }
130
131    /// Returns current setting
132    #[must_use]
133    pub fn compare_contents(&self) -> bool {
134        self.compare_contents
135    }
136
137    /// For the [INotifyWatcher](crate::INotifyWatcher), [KqueueWatcher](crate::KqueueWatcher),
138    /// and [PollWatcher](crate::PollWatcher).
139    ///
140    /// Determine if symbolic links should be followed when recursively watching a directory.
141    ///
142    /// This can't be changed during runtime. On by default.
143    #[must_use]
144    pub fn with_follow_symlinks(mut self, follow_symlinks: bool) -> Self {
145        self.follow_symlinks = follow_symlinks;
146        self
147    }
148
149    /// Returns current setting
150    #[must_use]
151    pub fn follow_symlinks(&self) -> bool {
152        self.follow_symlinks
153    }
154
155    /// Filter which event kinds are monitored.
156    ///
157    /// This allows you to control which types of filesystem events are delivered
158    /// to your event handler. On backends that support kernel-level filtering
159    /// (inotify), the mask is translated to native flags for optimal
160    /// performance. On other backends (kqueue, Windows, FSEvents, PollWatcher),
161    /// filtering is applied in userspace.
162    ///
163    /// The default is [`EventKindMask::ALL`], which includes all events.
164    /// Use [`EventKindMask::CORE`] to exclude access events.
165    ///
166    /// This can't be changed during runtime.
167    ///
168    /// # Example
169    ///
170    /// ```rust
171    /// use notify::{Config, EventKindMask};
172    ///
173    /// // Only monitor file creation and deletion
174    /// let config = Config::default()
175    ///     .with_event_kinds(EventKindMask::CREATE | EventKindMask::REMOVE);
176    ///
177    /// // Monitor everything including access events
178    /// let config_all = Config::default()
179    ///     .with_event_kinds(EventKindMask::ALL);
180    /// ```
181    #[must_use]
182    pub fn with_event_kinds(mut self, event_kinds: EventKindMask) -> Self {
183        self.event_kinds = event_kinds;
184        self
185    }
186
187    /// Returns current setting
188    #[must_use]
189    pub fn event_kinds(&self) -> EventKindMask {
190        self.event_kinds
191    }
192
193    /// For the [`ReadDirectoryChangesWatcher`](crate::ReadDirectoryChangesWatcher) backend.
194    ///
195    /// Controls path separator normalization for emitted event paths on Windows.
196    ///
197    /// The default is [`WindowsPathSeparatorStyle::Auto`], which preserves the
198    /// separator style implied by each watched input path.
199    ///
200    /// This can't be changed during runtime.
201    #[must_use]
202    pub fn with_windows_path_separator_style(mut self, style: WindowsPathSeparatorStyle) -> Self {
203        self.windows_path_separator_style = style;
204        self
205    }
206
207    /// Returns current setting.
208    #[must_use]
209    pub fn windows_path_separator_style(&self) -> WindowsPathSeparatorStyle {
210        self.windows_path_separator_style
211    }
212
213    /// For the [`FsEventWatcher`](crate::FsEventWatcher) backend.
214    ///
215    /// The latency passed to `FSEventStreamCreate`: how long the FSEvents service waits
216    /// after hearing about an event from the kernel before invoking the callback. A larger
217    /// value lets the system coalesce bursts of changes into fewer callbacks at the cost of
218    /// higher delivery latency.
219    ///
220    /// This watcher is created with `kFSEventStreamCreateFlagNoDefer`, so the first event in
221    /// an idle period is delivered immediately; only subsequent events get coalesced within
222    /// the latency window.
223    ///
224    /// The default is [`Duration::ZERO`].
225    ///
226    /// This can't be changed during runtime.
227    #[must_use]
228    pub fn with_fsevent_latency(mut self, latency: Duration) -> Self {
229        self.fsevent_latency = latency;
230        self
231    }
232
233    /// Returns current setting
234    #[must_use]
235    pub fn fsevent_latency(&self) -> Duration {
236        self.fsevent_latency
237    }
238}
239
240impl Default for Config {
241    fn default() -> Self {
242        Self {
243            poll_interval: Some(Duration::from_secs(30)),
244            compare_contents: false,
245            follow_symlinks: true,
246            event_kinds: EventKindMask::ALL,
247            windows_path_separator_style: WindowsPathSeparatorStyle::Auto,
248            fsevent_latency: Duration::ZERO,
249        }
250    }
251}
252
253/// Single watch backend configuration
254///
255/// This contains some settings that may relate to only one specific backend,
256/// such as to correctly configure each backend regardless of what is selected during runtime.
257#[derive(Clone, Debug)]
258pub struct WatchPathConfig {
259    recursive_mode: RecursiveMode,
260    dereference_symlinks: bool,
261}
262
263impl WatchPathConfig {
264    /// Creates new instance with provided [`RecursiveMode`]
265    #[must_use]
266    pub fn new(recursive_mode: RecursiveMode) -> Self {
267        Self {
268            recursive_mode,
269            dereference_symlinks: true,
270        }
271    }
272
273    /// Set [`RecursiveMode`] for the watch
274    #[must_use]
275    pub fn with_recursive_mode(mut self, recursive_mode: RecursiveMode) -> Self {
276        self.recursive_mode = recursive_mode;
277        self
278    }
279
280    /// Returns current setting
281    #[must_use]
282    pub fn recursive_mode(&self) -> RecursiveMode {
283        self.recursive_mode
284    }
285
286    /// Set whether a symbolic link is followed to its destination, for
287    /// [`RecursiveMode::NonRecursive`] watches on the [INotifyWatcher](crate::INotifyWatcher).
288    #[must_use]
289    pub fn with_dereference_symlinks(mut self, dereference_symlinks: bool) -> Self {
290        self.dereference_symlinks = dereference_symlinks;
291        self
292    }
293
294    /// Returns current setting
295    #[must_use]
296    pub fn dereference_symlinks(&self) -> bool {
297        self.dereference_symlinks
298    }
299}
300
301/// An operation to apply to a watcher
302///
303/// See [`Watcher::update_paths`](crate::Watcher::update_paths) for more information.
304#[derive(Debug)]
305pub enum PathOp {
306    /// Path should be watched
307    Watch(PathBuf, WatchPathConfig),
308
309    /// Path should be unwatched
310    Unwatch(PathBuf),
311}
312
313impl PathOp {
314    /// Watch the path with [`RecursiveMode::Recursive`]
315    #[must_use]
316    pub fn watch_recursive<P: Into<PathBuf>>(path: P) -> Self {
317        Self::Watch(path.into(), WatchPathConfig::new(RecursiveMode::Recursive))
318    }
319
320    /// Watch the path with [`RecursiveMode::NonRecursive`]
321    #[must_use]
322    pub fn watch_non_recursive<P: Into<PathBuf>>(path: P) -> Self {
323        Self::Watch(
324            path.into(),
325            WatchPathConfig::new(RecursiveMode::NonRecursive),
326        )
327    }
328
329    /// Unwatch the path
330    #[must_use]
331    pub fn unwatch<P: Into<PathBuf>>(path: P) -> Self {
332        Self::Unwatch(path.into())
333    }
334
335    /// Returns the path associated with this operation.
336    #[must_use]
337    pub fn as_path(&self) -> &Path {
338        match self {
339            PathOp::Watch(p, _) => p,
340            PathOp::Unwatch(p) => p,
341        }
342    }
343
344    /// Returns the path associated with this operation.
345    #[must_use]
346    pub fn into_path(self) -> PathBuf {
347        match self {
348            PathOp::Watch(p, _) => p,
349            PathOp::Unwatch(p) => p,
350        }
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use notify_types::event::EventKindMask;
358
359    #[test]
360    fn config_default_event_kinds_is_all() {
361        let config = Config::default();
362        assert_eq!(config.event_kinds(), EventKindMask::ALL);
363    }
364
365    #[test]
366    fn config_with_event_kinds() {
367        let mask = EventKindMask::CREATE | EventKindMask::REMOVE;
368        let config = Config::default().with_event_kinds(mask);
369        assert_eq!(config.event_kinds(), mask);
370    }
371
372    #[test]
373    fn config_with_all_events_includes_access() {
374        let config = Config::default().with_event_kinds(EventKindMask::ALL);
375        assert!(config.event_kinds().intersects(EventKindMask::ALL_ACCESS));
376    }
377
378    #[test]
379    fn config_with_empty_mask() {
380        let config = Config::default().with_event_kinds(EventKindMask::empty());
381        assert!(config.event_kinds().is_empty());
382    }
383
384    #[test]
385    fn event_kind_mask_default_matches_config_default() {
386        // Verify cross-crate consistency: both defaults should be ALL
387        assert_eq!(EventKindMask::default(), Config::default().event_kinds());
388        assert_eq!(EventKindMask::default(), EventKindMask::ALL);
389    }
390
391    #[test]
392    fn config_default_windows_separator_style_is_auto() {
393        let config = Config::default();
394        assert_eq!(
395            config.windows_path_separator_style(),
396            WindowsPathSeparatorStyle::Auto
397        );
398    }
399
400    #[test]
401    fn config_with_windows_separator_style() {
402        let config =
403            Config::default().with_windows_path_separator_style(WindowsPathSeparatorStyle::Slash);
404        assert_eq!(
405            config.windows_path_separator_style(),
406            WindowsPathSeparatorStyle::Slash
407        );
408    }
409
410    #[test]
411    fn config_default_fsevent_latency_is_zero() {
412        assert_eq!(Config::default().fsevent_latency(), Duration::ZERO);
413    }
414
415    #[test]
416    fn config_with_fsevent_latency() {
417        let latency = Duration::from_millis(250);
418        let config = Config::default().with_fsevent_latency(latency);
419        assert_eq!(config.fsevent_latency(), latency);
420    }
421}