Skip to content

Commit a357ff3

Browse files
committed
Merge branch 'main' into paths-mut-rework
2 parents e1a23fd + 978fe71 commit a357ff3

File tree

12 files changed

+179
-44
lines changed

12 files changed

+179
-44
lines changed

CHANGELOG.md

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
# Changelog
22

3-
## notify 8.2.0 (unreleased)
4-
- FEATURE: notify a user if the `max_user_watches` has been reached implicitly (`INotifyWatcher`) [#698]
5-
- FIX: `INotifyWatcher` ignores events with unknown watch descriptors (instead of `EventMask::Q_OVERFLOW`) [#700]
3+
## notify 8.3.0 (unreleased)
64
- FEATURE: deprecate `Watcher::paths_mut` and introduce `update_paths` [#705]
75

6+
## notify 8.2.0 (2025-08-03)
7+
- FEATURE: notify user if inotify's `max_user_watches` has been reached [#698]
8+
- FIX: `INotifyWatcher` ignore events with unknown watch descriptors (instead of `EventMask::Q_OVERFLOW`) [#700]
9+
810
[#698]: https://github.com/notify-rs/notify/pull/698
911
[#700]: https://github.com/notify-rs/notify/pull/700
10-
[#705]: https://github.com/notify-rs/notify/pull/705
11-
12-
## notify 8.1.0 (2025-07-03)
13-
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
14-
- FIX: kqueue-backend: do not double unwatch top-level directory when recursively unwatching [#683]
15-
- FIX: Return the crate error `PathNotFound` instead bubbling up the std::io error [#685]
16-
- FIX: fix server hangs when trashing folders on Windows [#674]
1712

18-
## debouncer-full 0.6.0 (unreleased)
19-
- FEATURE: allow `FileIdCache` trait implementations to choose ownership of the returned file-ids
20-
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
13+
## debouncer-full 0.6.0 (2025-08-03)
14+
- FEATURE: allow `FileIdCache` trait implementations to choose ownership of the returned file-ids [#664]
15+
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate [#680]
2116
- FIX: skip all `Modify` events right after a `Create` event, unless it's a rename event [#701]
2217

18+
[#664]: https://github.com/notify-rs/notify/pull/664
19+
[#680]: https://github.com/notify-rs/notify/pull/680
2320
[#701]: https://github.com/notify-rs/notify/pull/701
2421

25-
## debouncer-mini 0.7.0 (unreleased)
26-
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
22+
## debouncer-mini 0.7.0 (2025-08-03)
23+
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate [#680]
24+
25+
## file-id 0.2.3 (2025-08-03)
26+
- CHANGE: implement `AsRef<FileId>` for `FileId` [#664]
2727

28-
## file-id 0.2.3 (unreleased)
29-
- CHANGE: implement `AsRef<FileId>` for `FileId`
28+
## notify 8.1.0 (2025-07-03)
29+
- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
30+
- FIX: kqueue-backend: do not double unwatch top-level directory when recursively unwatching [#683]
31+
- FIX: Return the crate error `PathNotFound` instead bubbling up the std::io error [#685]
32+
- FIX: fix server hangs when trashing folders on Windows [#674]
3033

3134
## notify 8.0.0 (2025-01-10)
3235

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ crossbeam-channel = "0.5.0"
2424
flume = "0.11.1"
2525
deser-hjson = "2.2.4"
2626
env_logger = "0.11.2"
27-
file-id = { version = "0.2.2", path = "file-id" }
27+
file-id = { version = "0.2.3", path = "file-id" }
2828
fsevent-sys = "4.0.0"
2929
futures = "0.3.30"
3030
inotify = { version = "0.11.0", default-features = false }
@@ -35,13 +35,13 @@ log = "0.4.17"
3535
mio = { version = "1.0", features = ["os-ext"] }
3636
web-time = "1.1.0"
3737
nix = "0.29.0"
38-
notify = { version = "8.1.0", path = "notify" }
39-
notify-debouncer-full = { version = "0.5.0", path = "notify-debouncer-full" }
40-
notify-debouncer-mini = { version = "0.6.0", path = "notify-debouncer-mini" }
38+
notify = { version = "8.2.0", path = "notify" }
39+
notify-debouncer-full = { version = "0.6.0", path = "notify-debouncer-full" }
40+
notify-debouncer-mini = { version = "0.7.0", path = "notify-debouncer-mini" }
4141
notify-types = { version = "2.0.0", path = "notify-types" }
4242
pretty_assertions = "1.3.0"
4343
rand = "0.8.5"
44-
rstest = "0.24.0"
44+
rstest = "0.26.0"
4545
serde = { version = "1.0.89", features = ["derive"] }
4646
serde_json = "1.0.39"
4747
tempfile = "3.10.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Originally created by [Félix Saparelli] and awesome [contributors].
7171
[deno]: https://github.com/denoland/deno
7272
[docket]: https://iwillspeak.github.io/docket/
7373
[notify-docs]: https://docs.rs/notify/latest/notify/
74-
[notify-types-docs]: https://docs.rs/notify-types/latest/notify-types/
74+
[notify-types-docs]: https://docs.rs/notify-types/
7575
[file-id-docs]: https://docs.rs/file-id/latest/file_id/
7676
[fsnotify]: https://github.com/fsnotify/fsnotify
7777
[handlebars-iron]: https://github.com/sunng87/handlebars-iron

file-id/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "file-id"
3-
version = "0.2.2"
3+
version = "0.2.3"
44
description = "Utility for reading inode numbers (Linux, MacOS) and file IDs (Windows)"
55
documentation = "https://docs.rs/notify"
66
readme = "README.md"

file-id/src/lib.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@
2727
//! let file_id = file_id::get_high_res_file_id(file.path()).unwrap();
2828
//! println!("{file_id:?}");
2929
//! ```
30+
//!
31+
//! ## Example (Not Following Symlinks/Reparse Points)
32+
//!
33+
//! ```
34+
//! let file = tempfile::NamedTempFile::new().unwrap();
35+
//!
36+
//! // Get file ID without following symlinks
37+
//! let file_id = file_id::get_file_id_no_follow(file.path()).unwrap();
38+
//! println!("{file_id:?}");
39+
//! ```
3040
use std::{fs, io, path::Path};
3141

3242
#[cfg(feature = "serde")]
@@ -122,6 +132,16 @@ pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
122132
Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
123133
}
124134

135+
/// Get the `FileId` for the file or directory at `path` without following symlinks
136+
#[cfg(target_family = "unix")]
137+
pub fn get_file_id_no_follow(path: impl AsRef<Path>) -> io::Result<FileId> {
138+
use std::os::unix::fs::MetadataExt;
139+
140+
let metadata = fs::symlink_metadata(path.as_ref())?;
141+
142+
Ok(FileId::new_inode(metadata.dev(), metadata.ino()))
143+
}
144+
125145
/// Get the `FileId` for the file or directory at `path`
126146
#[cfg(target_family = "windows")]
127147
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
@@ -130,6 +150,14 @@ pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
130150
unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
131151
}
132152

153+
/// Get the `FileId` for the file or directory at `path` without following symlinks/reparse points
154+
#[cfg(target_family = "windows")]
155+
pub fn get_file_id_no_follow(path: impl AsRef<Path>) -> io::Result<FileId> {
156+
let file = open_file_no_follow(path)?;
157+
158+
unsafe { get_file_info_ex(&file).or_else(|_| get_file_info(&file)) }
159+
}
160+
133161
/// Get the `FileId` with the low resolution variant for the file or directory at `path`
134162
#[cfg(target_family = "windows")]
135163
pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
@@ -138,6 +166,14 @@ pub fn get_low_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
138166
unsafe { get_file_info(&file) }
139167
}
140168

169+
/// Get the `FileId` with the low resolution variant for the file or directory at `path` without following symlinks/reparse points
170+
#[cfg(target_family = "windows")]
171+
pub fn get_low_res_file_id_no_follow(path: impl AsRef<Path>) -> io::Result<FileId> {
172+
let file = open_file_no_follow(path)?;
173+
174+
unsafe { get_file_info(&file) }
175+
}
176+
141177
/// Get the `FileId` with the high resolution variant for the file or directory at `path`
142178
#[cfg(target_family = "windows")]
143179
pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
@@ -146,6 +182,14 @@ pub fn get_high_res_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
146182
unsafe { get_file_info_ex(&file) }
147183
}
148184

185+
/// Get the `FileId` with the high resolution variant for the file or directory at `path` without following symlinks/reparse points
186+
#[cfg(target_family = "windows")]
187+
pub fn get_high_res_file_id_no_follow(path: impl AsRef<Path>) -> io::Result<FileId> {
188+
let file = open_file_no_follow(path)?;
189+
190+
unsafe { get_file_info_ex(&file) }
191+
}
192+
149193
#[cfg(target_family = "windows")]
150194
unsafe fn get_file_info_ex(file: &fs::File) -> Result<FileId, io::Error> {
151195
use std::{mem, os::windows::prelude::*};
@@ -202,3 +246,16 @@ fn open_file<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
202246
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
203247
.open(path)
204248
}
249+
250+
#[cfg(target_family = "windows")]
251+
fn open_file_no_follow<P: AsRef<Path>>(path: P) -> io::Result<fs::File> {
252+
use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt};
253+
use windows_sys::Win32::Storage::FileSystem::{
254+
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
255+
};
256+
257+
OpenOptions::new()
258+
.access_mode(0)
259+
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
260+
.open(path)
261+
}

file-id/tests/integration.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use file_id::{get_file_id, get_file_id_no_follow};
2+
use std::{fs, io};
3+
use tempfile::TempDir;
4+
5+
#[test]
6+
fn test_get_file_id_vs_no_follow() -> io::Result<()> {
7+
let temp_dir = TempDir::new()?;
8+
let file_path = temp_dir.path().join("test_file.txt");
9+
let symlink_path = temp_dir.path().join("test_symlink");
10+
11+
// Create a test file
12+
fs::write(&file_path, "test content")?;
13+
14+
// Create a symlink to the file
15+
#[cfg(target_family = "unix")]
16+
std::os::unix::fs::symlink(&file_path, &symlink_path)?;
17+
18+
#[cfg(target_family = "windows")]
19+
std::os::windows::fs::symlink_file(&file_path, &symlink_path)?;
20+
21+
// Get file IDs
22+
let original_file_id = get_file_id(&file_path)?;
23+
let symlink_follow_id = get_file_id(&symlink_path)?;
24+
let symlink_no_follow_id = get_file_id_no_follow(&symlink_path)?;
25+
26+
// Following the symlink should give us the same ID as the original file
27+
assert_eq!(original_file_id, symlink_follow_id);
28+
29+
// Not following the symlink should give us a different ID (the symlink's own ID)
30+
assert_ne!(original_file_id, symlink_no_follow_id);
31+
32+
Ok(())
33+
}

notify-debouncer-full/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "notify-debouncer-full"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
description = "notify event debouncer optimized for ease of use"
55
documentation = "https://docs.rs/notify-debouncer-full"
66
authors = ["Daniel Faust <hessijames@gmail.com>"]

notify-debouncer-full/src/lib.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,18 +226,18 @@ impl<T: FileIdCache> DebounceDataInner<T> {
226226
let mut kind_index = HashMap::new();
227227

228228
while let Some(event) = queue.events.pop_front() {
229-
if now.saturating_duration_since(event.time) >= self.timeout {
230-
// remove previous event of the same kind
231-
if let Some(idx) = kind_index.get(&event.kind).copied() {
232-
events_expired.remove(idx);
233-
234-
kind_index.values_mut().for_each(|i| {
235-
if *i > idx {
236-
*i -= 1
237-
}
238-
})
239-
}
229+
// remove previous event of the same kind
230+
if let Some(idx) = kind_index.get(&event.kind).copied() {
231+
events_expired.remove(idx);
232+
233+
kind_index.values_mut().for_each(|i| {
234+
if *i > idx {
235+
*i -= 1
236+
}
237+
})
238+
}
240239

240+
if now.saturating_duration_since(event.time) >= self.timeout {
241241
kind_index.insert(event.kind, events_expired.len());
242242

243243
events_expired.push(event);
@@ -812,6 +812,7 @@ mod tests {
812812
"add_remove_event_after_create_and_modify_event",
813813
"add_remove_parent_event_after_remove_child_event",
814814
"add_errors",
815+
"debounce_modify_events",
815816
"emit_continuous_modify_content_events",
816817
"emit_events_in_chronological_order",
817818
"emit_events_with_a_prepended_rename_event",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
state: {
3+
timeout: 3
4+
}
5+
events: [
6+
{ kind: "modify-any", paths: ["/watch/file"], time: 2 }
7+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
8+
]
9+
expected: {
10+
queues: {
11+
/watch/file: {
12+
events: [
13+
{ kind: "modify-any", paths: ["*"], time: 2 }
14+
{ kind: "modify-any", paths: ["*"], time: 4 }
15+
]
16+
}
17+
}
18+
events: {
19+
1: []
20+
2: []
21+
3: []
22+
4: []
23+
5: []
24+
6: []
25+
7: [
26+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
27+
]
28+
8: [
29+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
30+
]
31+
9: [
32+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
33+
]
34+
10: [
35+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
36+
]
37+
100: [
38+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
39+
]
40+
1000: [
41+
{ kind: "modify-any", paths: ["/watch/file"], time: 4 }
42+
]
43+
}
44+
}
45+
}

notify-debouncer-full/test_cases/emit_continuous_modify_content_events.hjson

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,8 @@
2323
3: []
2424
4: []
2525
5: []
26-
6: [
27-
{ kind: "modify-data-content", paths: ["/watch/file"], time: 1 }
28-
]
29-
7: [
30-
{ kind: "modify-data-content", paths: ["/watch/file"], time: 2 }
31-
]
26+
6: []
27+
7: []
3228
8: [
3329
{ kind: "modify-data-content", paths: ["/watch/file"], time: 3 }
3430
]

0 commit comments

Comments
 (0)