notify/config.rs
1//! Configuration types
2
3use std::time::Duration;
4
5/// Default maximum number of paths to pass to FSEvents, chosen to stay
6/// well under the macOS default file descriptor soft limit (256).
7pub const DEFAULT_MAX_FSEVENT_PATHS: usize = 128;
8
9/// Indicates how the path should be watched
10#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
11pub struct WatchMode {
12 /// Indicates whether to watch sub-directories as well
13 pub recursive_mode: RecursiveMode,
14 /// Indicates what happens when the relationship of the physical entity and the file path changes
15 pub target_mode: TargetMode,
16}
17
18impl WatchMode {
19 /// Creates a WatchMode that watches directories recursively and tracks the file path
20 #[must_use]
21 pub fn recursive() -> Self {
22 Self {
23 recursive_mode: RecursiveMode::Recursive,
24 target_mode: TargetMode::TrackPath,
25 }
26 }
27
28 /// Creates a WatchMode that watches only the provided directory and tracks the file path
29 #[must_use]
30 pub fn non_recursive() -> Self {
31 Self {
32 recursive_mode: RecursiveMode::NonRecursive,
33 target_mode: TargetMode::TrackPath,
34 }
35 }
36
37 pub(crate) fn upgrade_with(&mut self, other: WatchMode) {
38 self.recursive_mode = self.recursive_mode.upgraded_with(other.recursive_mode);
39 self.target_mode = self.target_mode.upgraded_with(other.target_mode);
40 }
41}
42
43/// Indicates whether only the provided directory or its sub-directories as well should be watched
44#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
45pub enum RecursiveMode {
46 /// Watch all sub-directories as well, including directories created after installing the watch
47 Recursive,
48
49 /// Watch only the provided directory
50 NonRecursive,
51}
52
53impl RecursiveMode {
54 #[expect(clippy::trivially_copy_pass_by_ref)]
55 pub(crate) fn is_recursive(&self) -> bool {
56 match *self {
57 RecursiveMode::Recursive => true,
58 RecursiveMode::NonRecursive => false,
59 }
60 }
61
62 pub(crate) fn upgraded_with(self, other: Self) -> Self {
63 match self {
64 RecursiveMode::Recursive => self,
65 RecursiveMode::NonRecursive => {
66 if other == RecursiveMode::Recursive {
67 other
68 } else {
69 self
70 }
71 }
72 }
73 }
74}
75
76/// Indicates what happens when the relationship of the physical entity and the file path changes
77#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
78pub enum TargetMode {
79 /// Tracks the file path.
80 ///
81 /// If the underlying physical entity (inode/File ID) at this path is replaced
82 /// (e.g., by a move/rename operation), the watch continues to monitor the new entity
83 /// that now occupies the path.
84 ///
85 /// TODO: watching nested non-existent paths is not implemented yet. <https://github.com/rolldown/notify/issues/32>
86 TrackPath,
87
88 /// Does not track the file path, nor the physical entity.
89 ///
90 /// If the underlying physical entity (inode/File ID) is replaced
91 /// (e.g., by a move/rename operation), the watch stops monitoring.
92 ///
93 /// TODO: fsevents backend and Windows backend and polling backend does not unwatch on physical entity change yet. <https://github.com/rolldown/notify/issues/33>
94 NoTrack,
95}
96
97impl TargetMode {
98 pub(crate) fn upgraded_with(self, other: Self) -> Self {
99 match self {
100 TargetMode::TrackPath => self,
101 TargetMode::NoTrack => {
102 if other == TargetMode::TrackPath {
103 other
104 } else {
105 self
106 }
107 }
108 }
109 }
110}
111
112/// Watcher Backend configuration
113///
114/// This contains multiple settings that may relate to only one specific backend,
115/// such as to correctly configure each backend regardless of what is selected during runtime.
116///
117/// ```rust
118/// # use std::time::Duration;
119/// # use notify::Config;
120/// let config = Config::default()
121/// .with_poll_interval(Duration::from_secs(2))
122/// .with_compare_contents(true);
123/// ```
124///
125/// Some options can be changed during runtime, others have to be set when creating the watcher backend.
126#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
127pub struct Config {
128 /// See [Config::with_poll_interval]
129 poll_interval: Option<Duration>,
130
131 /// See [Config::with_compare_contents]
132 compare_contents: bool,
133
134 follow_symlinks: bool,
135
136 /// See [Config::with_max_fsevent_paths]
137 max_fsevent_paths: usize,
138}
139
140impl Config {
141 /// For the [`PollWatcher`](crate::PollWatcher) backend.
142 ///
143 /// Interval between each re-scan attempt. This can be extremely expensive for large
144 /// file trees so it is recommended to measure and tune accordingly.
145 ///
146 /// The default poll frequency is 30 seconds.
147 ///
148 /// This will enable automatic polling, overwriting [`with_manual_polling()`](Config::with_manual_polling).
149 #[must_use]
150 pub fn with_poll_interval(mut self, dur: Duration) -> Self {
151 // TODO: v7.0 break signature to option
152 self.poll_interval = Some(dur);
153 self
154 }
155
156 /// Returns current setting
157 #[must_use]
158 pub fn poll_interval(&self) -> Option<Duration> {
159 // Changed Signature to Option
160 self.poll_interval
161 }
162
163 /// For the [`PollWatcher`](crate::PollWatcher) backend.
164 ///
165 /// Disable automatic polling. Requires calling [`crate::PollWatcher::poll()`] manually.
166 ///
167 /// This will disable automatic polling, overwriting [`with_poll_interval()`](Config::with_poll_interval).
168 #[must_use]
169 pub fn with_manual_polling(mut self) -> Self {
170 self.poll_interval = None;
171 self
172 }
173
174 /// For the [`PollWatcher`](crate::PollWatcher) backend.
175 ///
176 /// Optional feature that will evaluate the contents of changed files to determine if
177 /// they have indeed changed using a fast hashing algorithm. This is especially important
178 /// for pseudo filesystems like those on Linux under /sys and /proc which are not obligated
179 /// to respect any other filesystem norms such as modification timestamps, file sizes, etc.
180 /// By enabling this feature, performance will be significantly impacted as all files will
181 /// need to be read and hashed at each `poll_interval`.
182 ///
183 /// This can't be changed during runtime. Off by default.
184 #[must_use]
185 pub fn with_compare_contents(mut self, compare_contents: bool) -> Self {
186 self.compare_contents = compare_contents;
187 self
188 }
189
190 /// Returns current setting
191 #[must_use]
192 pub fn compare_contents(&self) -> bool {
193 self.compare_contents
194 }
195
196 /// For the [INotifyWatcher](crate::INotifyWatcher), [KqueueWatcher](crate::KqueueWatcher),
197 /// and [PollWatcher](crate::PollWatcher).
198 ///
199 /// Determine if symbolic links should be followed when recursively watching a directory.
200 ///
201 /// This can't be changed during runtime. On by default.
202 #[must_use]
203 pub fn with_follow_symlinks(mut self, follow_symlinks: bool) -> Self {
204 self.follow_symlinks = follow_symlinks;
205 self
206 }
207
208 /// Returns current setting
209 #[must_use]
210 pub fn follow_symlinks(&self) -> bool {
211 self.follow_symlinks
212 }
213
214 /// For the [`FsEventWatcher`](crate::FsEventWatcher) backend.
215 ///
216 /// Maximum number of paths to pass to FSEvents. When the number of
217 /// watched paths exceeds this limit, the watcher automatically watches
218 /// parent directories instead of individual paths to reduce file
219 /// descriptor usage.
220 ///
221 /// The default is [`DEFAULT_MAX_FSEVENT_PATHS`]. Set to `0` to disable
222 /// consolidation.
223 ///
224 /// This can't be changed during runtime.
225 #[must_use]
226 pub fn with_max_fsevent_paths(mut self, max_paths: usize) -> Self {
227 self.max_fsevent_paths = max_paths;
228 self
229 }
230
231 /// Returns current setting.
232 ///
233 /// `0` means consolidation is disabled.
234 #[must_use]
235 pub fn max_fsevent_paths(&self) -> usize {
236 self.max_fsevent_paths
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 max_fsevent_paths: DEFAULT_MAX_FSEVENT_PATHS,
247 }
248 }
249}