Skip to content

Commit

Permalink
add PollWatcher scan events
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpr03 committed Jul 14, 2023
1 parent 7da85a9 commit 06cfa26
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 8 deletions.
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ path = "poll_sysfs.rs"
name = "watcher_kind"
path = "watcher_kind.rs"

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

# specifically in its own sub folder
# to prevent cargo audit from complaining
#[[example]]
Expand Down
55 changes: 55 additions & 0 deletions examples/pollwatcher_scan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use notify::{Config, PollWatcher, RecursiveMode, Watcher};
use std::path::Path;

// Example for the pollwatcher scan callback feature.
// Returns the scanned paths
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();

let path = std::env::args()
.nth(1)
.expect("Argument 1 needs to be a path");

log::info!("Watching {path}");

if let Err(error) = watch(path) {
log::error!("Error: {error:?}");
}
}

fn watch<P: AsRef<Path>>(path: P) -> notify::Result<()> {
let (tx, rx) = std::sync::mpsc::channel();

// if you want to use the same channel for both events
// and you need to differentiate between scan and file change events,
// then you will have to use something like this
enum Message {
Event(notify::Result<notify::Event>),
Scan(notify::Result<notify::Event>),
}

let tx_c = tx.clone();
// use the pollwatcher and set a callback for the scanning events
let mut watcher = PollWatcher::with_initial_scan(
move |watch_event| {
tx_c.send(Message::Event(watch_event)).unwrap();
},
Config::default(),
Some(move |scan_event| {
tx.send(Message::Scan(scan_event)).unwrap();
}),
)?;

// Add a path to be watched. All files and directories at that path and
// below will be monitored for changes.
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;

for res in rx {
match res {
Message::Event(e) => println!("Watch event {e:?}"),
Message::Scan(e) => println!("Scan event {e:?}"),
}
}

Ok(())
}
6 changes: 6 additions & 0 deletions notify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
}
}

struct PhantomHandler;

impl EventHandler for PhantomHandler {
fn handle_event(&mut self, _event: Result<Event>) {}
}

/// Watcher kind enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
Expand Down
51 changes: 45 additions & 6 deletions notify/src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Checks the `watch`ed paths periodically to detect changes. This implementation only uses
//! Rust stdlib APIs and should work on all of the platforms it supports.
use crate::{Config, EventHandler, RecursiveMode, Watcher};
use crate::{Config, EventHandler, PhantomHandler, RecursiveMode, Watcher};
use std::{
collections::HashMap,
path::{Path, PathBuf},
Expand All @@ -18,7 +18,10 @@ use std::{
use data::{DataBuilder, WatchData};
mod data {
use crate::{
event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
event::{
CreateKind, DataChange, Event, EventAttributes, EventKind, MetadataKind, ModifyKind,
RemoveKind,
},
EventHandler,
};
use filetime::FileTime;
Expand All @@ -37,6 +40,7 @@ mod data {
/// Builder for [`WatchData`] & [`PathData`].
pub(super) struct DataBuilder {
emitter: EventEmitter,
scan_emitter: Option<EventEmitter>,

// TODO: May allow user setup their custom BuildHasher / BuildHasherDefault
// in future.
Expand All @@ -47,12 +51,18 @@ mod data {
}

impl DataBuilder {
pub(super) fn new<F>(event_handler: F, compare_content: bool) -> Self
pub(super) fn new<F, G>(
event_handler: F,
compare_content: bool,
scan_emitter: Option<G>,
) -> Self
where
F: EventHandler,
G: EventHandler,
{
Self {
emitter: EventEmitter::new(event_handler),
scan_emitter: scan_emitter.map(EventEmitter::new),
build_hasher: compare_content.then(RandomState::default),
now: Instant::now(),
}
Expand Down Expand Up @@ -192,6 +202,7 @@ mod data {
root: PathBuf,
is_recursive: bool,
) -> impl Iterator<Item = (PathBuf, PathData)> + '_ {
log::trace!("rescanning {root:?}");
// WalkDir return only one entry if root is a file (not a folder),
// so we can use single logic to do the both file & dir's jobs.
//
Expand All @@ -212,11 +223,27 @@ mod data {
// propagate to event handler. It may not consistent.
//
// FIXME: Should we emit all IO error events? Or ignore them all?
.filter_map(|entry| entry.ok())
.filter_map(|entry_res| match entry_res {
Ok(entry) => Some(entry),
Err(err) => {
log::warn!("walkdir error scanning {err:?}");
let crate_err =
crate::Error::new(crate::ErrorKind::Generic(err.to_string()));
data_builder.emitter.emit(Err(crate_err));
None
}
})
.filter_map(|entry| match entry.metadata() {
Ok(metadata) => {
let path = entry.into_path();

// emit initial scans
if let Some(emitter) = &data_builder.scan_emitter {
emitter.emit(Ok(Event {
kind: EventKind::Create(CreateKind::File),
paths: vec![path.clone()],
attrs: EventAttributes::new(),
}));
}
let meta_path = MetaPath::from_parts_unchecked(path, metadata);
let data_path = data_builder.build_path_data(&meta_path);

Expand Down Expand Up @@ -409,7 +436,19 @@ pub struct PollWatcher {
impl PollWatcher {
/// Create a new [PollWatcher], configured as needed.
pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
let data_builder = DataBuilder::new(event_handler, config.compare_contents());
Self::with_initial_scan::<_, PhantomHandler>(event_handler, config, None)
}

/// Crate a new [PollWatcher] with an event handler called on the initial scan with all files seen by the pollwatcher.
///
/// `scan_fallback` is called on the initial scan and rescans
pub fn with_initial_scan<F: EventHandler, G: EventHandler>(
event_handler: F,
config: Config,
scan_callback: Option<G>,
) -> crate::Result<PollWatcher> {
let data_builder =
DataBuilder::new(event_handler, config.compare_contents(), scan_callback);

let poll_watcher = PollWatcher {
watches: Default::default(),
Expand Down
8 changes: 6 additions & 2 deletions notify/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,11 @@ unsafe extern "system" fn handle_event(
};

if !skip {
log::trace!("Event: path = `{}`, action = {:?}", path.display(), (*cur_entry).Action);
log::trace!(
"Event: path = `{}`, action = {:?}",
path.display(),
(*cur_entry).Action
);

let newe = Event::new(EventKind::Any).add_path(path);

Expand Down Expand Up @@ -504,7 +508,7 @@ impl ReadDirectoryChangesWatcher {
}

impl Watcher for ReadDirectoryChangesWatcher {
fn new<F: EventHandler>(event_handler: F, config: Config) -> Result<Self> {
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
// create dummy channel for meta event
// TODO: determine the original purpose of this - can we remove it?
let (meta_tx, _) = unbounded();
Expand Down

0 comments on commit 06cfa26

Please sign in to comment.