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}