Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add refined debouncer #480

Merged
merged 15 commits into from
May 15, 2023
10 changes: 5 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
version:
- 1.56.0 # MSRV
- 1.60.0 # MSRV
- stable
- nightly
os:
Expand All @@ -38,19 +38,19 @@ jobs:
rustup override set ${{ matrix.version }}

- name: check build serde,macos_kqueue for examples
if: matrix.version != '1.56.0' && matrix.os == 'macos-latest'
if: matrix.version != '1.60.0' && matrix.os == 'macos-latest'
run: cargo check -p notify --features=serde,macos_kqueue --examples

- name: check build serde,macos_kqueue
if: matrix.version == '1.56.0' && matrix.os == 'macos-latest'
if: matrix.version == '1.60.0' && matrix.os == 'macos-latest'
run: cargo check -p notify --features=serde,macos_kqueue

- name: check build serde for examples
if: matrix.version != '1.56.0' && matrix.os != 'macos-latest'
if: matrix.version != '1.60.0' && matrix.os != 'macos-latest'
run: cargo check -p notify --features=serde --examples

- name: check build serde
if: matrix.version == '1.56.0' && matrix.os != 'macos-latest'
if: matrix.version == '1.60.0' && matrix.os != 'macos-latest'
run: cargo check --features=serde

- name: check build without crossbeam/default features
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

v4 commits split out to branch `v4_maintenance` starting with `4.0.16`

## notify 6.0.0

- CHANGE: files and directories moved into a watch folder on Linux will now be reported as `rename to` events instead of `create` events
dfaust marked this conversation as resolved.
Show resolved Hide resolved
- CHANGE: on Linux `rename from` events will be emitted immediately without starting a new thread
- CHANGE: raise MSRV to 1.60

## debouncer-full 0.1.0

- FEATURE: only emit a single `rename` event if the rename `From` and `To` events can be matched
- FEATURE: merge multiple `rename` events
- FEATURE: keep track of the file system IDs all files and stiches rename events together (FSevents, Windows)
- FEATURE: emit only one `remove` event when deleting a directory (inotify)
- FEATURE: don't emit duplicate create events
- FEATURE: don't emit `Modify` events after a `Create` event

## notify 5.2.0

- CHANGE: implement `Copy` for `EventKind` and `ModifyKind` [#458]
Expand Down
7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
members = [
"notify",
"notify-debouncer-mini",

"notify-debouncer-full",
"file-id",
# internal
"examples"
#"examples/hot_reload_tide" until https://github.com/rustsec/rustsec/issues/501 is resolved
]

exclude = ["examples/hot_reload_tide"]

[patch.crates-io]
notify = { path = "notify/" }
0xpr03 marked this conversation as resolved.
Show resolved Hide resolved
notify-debouncer-mini = { path = "notify-debouncer-mini/" }
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ _Cross-platform filesystem notification library for Rust._
- [Crate page][crate]
- [Changelog][changelog]
- [Upgrading from v4](UPGRADING_V4_TO_V5.md)
- Earliest supported Rust version: **1.56**
- Earliest supported Rust version: **1.60**
- **incomplete [Guides and in-depth docs][wiki]**

As used by: [alacritty], [cargo watch], [cobalt], [docket], [mdBook], [pax],
Expand Down Expand Up @@ -76,7 +76,7 @@ Originally created by [Félix Saparelli] and awesome [contributors].
[contributors]: https://github.com/notify-rs/notify/graphs/contributors
[crate]: https://crates.io/crates/notify
[docket]: https://iwillspeak.github.io/docket/
[docs]: https://docs.rs/notify/5.1.0/notify/
[docs]: https://docs.rs/notify/6.0.0/notify/
[fsnotify]: https://github.com/go-fsnotify/fsnotify
[handlebars-iron]: https://github.com/sunng87/handlebars-iron
[hotwatch]: https://github.com/francesca64/hotwatch
Expand Down
18 changes: 12 additions & 6 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ publish = false
edition = "2021"

[dev-dependencies]
notify = { version = "5.1.0" }
notify-debouncer-mini = { version = "0.2.0" }
notify = { version = "6.0.0", path = "../notify" }
notify-debouncer-mini = { version = "0.2.0", path = "../notify-debouncer-mini" }
0xpr03 marked this conversation as resolved.
Show resolved Hide resolved
notify-debouncer-full = { version = "0.1.0", path = "../notify-debouncer-full" }
futures = "0.3"
tempfile = "3.5.0"

[[example]]
name = "async_monitor"
Expand All @@ -18,12 +20,16 @@ name = "monitor_raw"
path = "monitor_raw.rs"

[[example]]
name = "debounced"
path = "debounced.rs"
name = "debouncer_mini"
path = "debouncer_mini.rs"

[[example]]
name = "debounced_custom"
path = "debounced_full_custom.rs"
name = "debouncer_mini_custom"
path = "debouncer_mini_custom.rs"

[[example]]
name = "debouncer_full"
path = "debouncer_full.rs"

[[example]]
name = "poll_sysfs"
Expand Down
51 changes: 51 additions & 0 deletions examples/debouncer_full.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::{fs, thread, time::Duration};

use notify::{RecursiveMode, Watcher};
use notify_debouncer_full::new_debouncer;
use tempfile::tempdir;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let dir_path = dir.path().to_path_buf();

// emit some events by changing a file
thread::spawn(move || {
let mut n = 1;
let mut file_path = dir_path.join(format!("file-{n:03}.txt"));
loop {
for _ in 0..5 {
fs::write(&file_path, b"Lorem ipsum").unwrap();
thread::sleep(Duration::from_millis(500));
}
n += 1;
let target_path = dir_path.join(format!("file-{n:03}.txt"));
fs::rename(&file_path, &target_path).unwrap();
file_path = target_path;
}
});

// setup debouncer
let (tx, rx) = std::sync::mpsc::channel();

// no specific tickrate, max debounce time 2 seconds
let mut debouncer = new_debouncer(Duration::from_secs(2), None, tx)?;

debouncer
.watcher()
.watch(dir.path(), RecursiveMode::Recursive)?;

debouncer
.cache()
.add_root(dir.path(), RecursiveMode::Recursive);

// print all events and errors
for result in rx {
match result {
Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
}
println!();
}

Ok(())
}
9 changes: 5 additions & 4 deletions examples/debounced.rs → examples/debouncer_mini.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{path::Path, time::Duration};

use notify::{RecursiveMode};
use notify::RecursiveMode;
use notify_debouncer_mini::new_debouncer;

/// Example for debouncer
Expand All @@ -27,9 +27,10 @@ fn main() {
.unwrap();

// print all events, non returning
for events in rx {
for e in events {
println!("{:?}", e);
for result in rx {
match result {
Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ fn main() {
.watch(Path::new("."), RecursiveMode::Recursive)
.unwrap();
// print all events, non returning
for events in rx {
for e in events {
println!("{:?}", e);
for result in rx {
match result {
Ok(events) => events.iter().for_each(|event| println!("{event:?}")),
Err(errors) => errors.iter().for_each(|error| println!("{error:?}")),
}
}
}
2 changes: 1 addition & 1 deletion examples/hot_reload_tide/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tide = "0.16.0"
async-std = { version = "1.6.0", features = ["attributes"] }
serde_json = "1.0"
serde = "1.0.115"
notify = { version = "5.1.0", features = ["serde"], path = "../../notify" }
notify = { version = "6.0.0", features = ["serde"], path = "../../notify" }

# required to prevent mixing with workspace
# hack to prevent cargo audit from catching this
Expand Down
24 changes: 24 additions & 0 deletions file-id/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
0xpr03 marked this conversation as resolved.
Show resolved Hide resolved
name = "file-id"
version = "0.1.0"
rust-version = "1.60"
description = "Platform independent file id library"
documentation = "https://docs.rs/notify"
homepage = "https://github.com/notify-rs/notify"
repository = "https://github.com/notify-rs/notify.git"
readme = "../README.md"
license = "CC0-1.0 OR Artistic-2.0"
keywords = ["filesystem", "inode", "file", "index"]
categories = ["filesystem"]
authors = ["Daniel Faust <hessijames@gmail.com>"]

edition = "2021"

[dependencies]
serde = { version = "1.0.89", features = ["derive"], optional = true }

[target.'cfg(windows)'.dependencies.winapi-util]
version = "0.1.5"

[dev-dependencies]
tempfile = "3.2.0"
1 change: 1 addition & 0 deletions file-id/LICENSE
1 change: 1 addition & 0 deletions file-id/LICENSE.ARTISTIC
24 changes: 24 additions & 0 deletions file-id/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# File Id

[![» Docs](https://flat.badgen.net/badge/api/docs.rs/df3600)][docs]

A utility to read file IDs.

Modern file systems assign a unique ID to each file. On Linux and MacOS it is called an `inode number`, on Windows it is called `file index`.
Together with the `device id`, a file can be identified uniquely on a device at a given time.

Keep in mind though, that IDs may be re-used at some point.

## Example

```rust
let file_id = file_id::get_file_id(path).unwrap();

println!("{file_id:?}");
```

## Features

- `serde` for serde support, off by default

[docs]: https://docs.rs/file-id
64 changes: 64 additions & 0 deletions file-id/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! A utility to read file IDs that are unique on a given device.
//!
//! Modern file systems assign a unique ID to each file. On Linux and MacOS it is called an `inode number`, on Windows it is called a `file index`.
//! Together with the `device id`, a file can be identified uniquely on a device at a given time.
//!
//! Keep in mind though, that IDs may be re-used at some point.
//!
//! ## Example
//!
//! ```rust
//! let file = tempfile::NamedTempFile::new().unwrap();
//!
//! let file_id = file_id::get_file_id(file.path()).unwrap();
//!
//! println!("{file_id:?}");
//! ```
use std::{fs, io, path::Path};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Unique identifier of a file
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FileId {
/// Device ID or volume serial number
pub device: u64,

/// Inode number or file index
pub file: u64,
}

impl FileId {
pub fn new(device: u64, file: u64) -> Self {
Self { device, file }
}
}

/// Get the `FileId` for the file at `path`
#[cfg(target_family = "unix")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
use std::os::unix::fs::MetadataExt;

let metadata = fs::metadata(path.as_ref())?;

Ok(FileId {
device: metadata.dev(),
file: metadata.ino(),
})
}

/// Get the `FileId` for the file at `path`
#[cfg(target_family = "windows")]
pub fn get_file_id(path: impl AsRef<Path>) -> io::Result<FileId> {
use winapi_util::{file::information, Handle};

let handle = Handle::from_path_any(path.as_ref())?;
let info = information(&handle)?;

Ok(FileId {
device: info.volume_serial_number(),
file: info.file_index(),
})
}
40 changes: 40 additions & 0 deletions notify-debouncer-full/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "notify-debouncer-full"
version = "0.1.0"
edition = "2021"
rust-version = "1.60"
description = "notify event debouncer optimized for ease of use"
documentation = "https://docs.rs/notify-debouncer-full"
homepage = "https://github.com/notify-rs/notify"
repository = "https://github.com/notify-rs/notify.git"
authors = ["Daniel Faust <hessijames@gmail.com>"]
keywords = ["events", "filesystem", "notify", "watch"]
license = "CC0-1.0 OR Artistic-2.0"
readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "notify_debouncer_full"
path = "src/lib.rs"

[features]
default = ["crossbeam"]
# can't use dep:crossbeam-channel and feature name crossbeam-channel below rust 1.60
crossbeam = ["crossbeam-channel","notify/crossbeam-channel"]

[dependencies]
notify = { version = "6.0.0", path = "../notify" }
crossbeam-channel = { version = "0.5", optional = true }
serde = { version = "1.0.89", features = ["derive"], optional = true }
file-id = { version = "0.1.0", path = "../file-id" }
walkdir = "2.2.2"
parking_lot = "0.12.1"

[dev-dependencies]
pretty_assertions = "1.3.0"
mock_instant = "0.3.0"
rstest = "0.17.0"
serde = { version = "1.0.89", features = ["derive"] }
deser-hjson = "1.1.1"
rand = "0.8.5"
1 change: 1 addition & 0 deletions notify-debouncer-full/LICENSE
1 change: 1 addition & 0 deletions notify-debouncer-full/LICENSE.ARTISTIC
Loading