xvc_walker/notify.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
//! A libnotify based file system notification module that considers ignore rules.
//!
//! This module uses [notify] crate to watch file system events.
//! It filters relevant events, and also ignores the events from ignored paths.
//! It defines [PathEvent] as a simple version of [notify::EventKind].
//! It defines [PathEventHandler] that handles events from [notify::EventHandler].
use crate::{
error::{Error, Result},
IgnoreRules, MatchResult,
};
pub use notify::{
Config, Event, EventHandler, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher,
};
use std::fs::Metadata;
use std::path::PathBuf;
use std::time::Duration;
use xvc_logging::watch;
use crossbeam_channel::{bounded, Receiver, Sender};
use log::{debug, warn};
/// An walker-relevant event for changes in a directory.
/// It packs newer [std::fs::Metadata] if there is.
#[derive(Debug, Clone)]
pub enum PathEvent {
/// Emitted when a new `path` is created with `metadata`.
Create {
/// The created path
path: PathBuf,
/// The new metadata
metadata: Metadata,
},
/// Emitted after a new write to `path`.
Update {
/// Updated path
path: PathBuf,
/// New metadata
metadata: Metadata,
},
/// Emitted when [PathBuf] is deleted.
Delete {
/// Deleted path
path: PathBuf,
},
}
/// A struct that handles [notify::Event]s considering also [IgnoreRules]
struct PathEventHandler {
sender: Sender<Option<PathEvent>>,
ignore_rules: IgnoreRules,
}
impl EventHandler for PathEventHandler {
fn handle_event(&mut self, event: notify::Result<Event>) {
watch!(event);
if let Ok(event) = event {
match event.kind {
notify::EventKind::Create(_) => self.create_event(event.paths[0].clone()),
notify::EventKind::Modify(mk) => match mk {
notify::event::ModifyKind::Any => todo!(),
notify::event::ModifyKind::Data(_) => self.write_event(event.paths[0].clone()),
notify::event::ModifyKind::Metadata(_) => {
self.write_event(event.paths[0].clone())
}
notify::event::ModifyKind::Name(rk) => match rk {
notify::event::RenameMode::Any => {}
notify::event::RenameMode::To => self.create_event(event.paths[0].clone()),
notify::event::RenameMode::From => {
self.remove_event(event.paths[0].clone())
}
notify::event::RenameMode::Both => {
self.rename_event(event.paths[0].clone(), event.paths[1].clone())
}
notify::event::RenameMode::Other => {}
},
notify::event::ModifyKind::Other => {}
},
notify::EventKind::Remove(_) => self.remove_event(event.paths[0].clone()),
notify::EventKind::Any => {}
notify::EventKind::Access(_) => {}
notify::EventKind::Other => {}
}
} else {
debug!("{:?}", event);
}
}
}
impl PathEventHandler {
fn write_event(&mut self, path: PathBuf) {
match self.ignore_rules.check(&path) {
MatchResult::Whitelist | MatchResult::NoMatch => {
if let Ok(metadata) = path.metadata() {
self.sender
.send(Some(PathEvent::Create {
path: path.clone(),
metadata,
}))
.unwrap_or_else(|e| {
Error::from(e).warn();
});
} else {
debug!("Error in metadata for {}", path.to_string_lossy());
}
}
MatchResult::Ignore => {
debug!("FS Notification Ignored: {}", path.to_string_lossy());
}
}
}
fn create_event(&mut self, path: PathBuf) {
match self.ignore_rules.check(&path) {
MatchResult::Whitelist | MatchResult::NoMatch => {
if let Ok(metadata) = path.metadata() {
self.sender
.send(Some(PathEvent::Create {
path: path.clone(),
metadata,
}))
.unwrap_or_else(|e| {
Error::from(e).warn();
});
} else {
debug!("Error in metadata for {}", path.to_string_lossy());
}
}
MatchResult::Ignore => {
debug!("FS Notification Ignored: {}", path.to_string_lossy());
}
}
}
fn remove_event(&mut self, path: PathBuf) {
match self.ignore_rules.check(&path) {
MatchResult::Whitelist | MatchResult::NoMatch => {
self.sender
.send(Some(PathEvent::Delete { path }))
.unwrap_or_else(|e| warn!("{}", e));
}
MatchResult::Ignore => {
debug!("FS Notification Ignored: {}", path.to_string_lossy());
}
}
}
fn rename_event(&mut self, from: PathBuf, to: PathBuf) {
self.remove_event(from);
self.create_event(to);
}
}
/// Create a [notify::RecommendedWatcher] and a [crossbeam_channel::Receiver] to receive
/// [PathEvent]s. It creates the channel and [PathEventHandler] with its [Sender], then returns the
/// [Receiver] for consumption.
///
/// Paths ignored by `ignore_rules` do not emit any events.
pub fn make_watcher(
ignore_rules: IgnoreRules,
) -> Result<(RecommendedWatcher, Receiver<Option<PathEvent>>)> {
let (sender, receiver) = bounded(10000);
let root = ignore_rules.root.clone();
let mut watcher = notify::recommended_watcher(PathEventHandler {
ignore_rules,
sender,
})?;
watcher.watch(&root, RecursiveMode::Recursive)?;
watch!(watcher);
Ok((watcher, receiver))
}
/// Create a [notify::PollWatcher] and a [crossbeam_channel::Receiver] to receive
/// [PathEvent]s. It creates the channel and [PathEventHandler] with its [Sender], then returns the
/// [Receiver] for consumption.
///
/// Note that this is used when [`make_watcher`] doesn't work as expected. It uses
/// a polling watcher with 2 second polls to the filesystem. It has lower
/// performance than [`make_watcher`], but it works in all platforms.
pub fn make_polling_watcher(
ignore_rules: IgnoreRules,
) -> Result<(PollWatcher, Receiver<Option<PathEvent>>)> {
let (sender, receiver) = bounded(10000);
let root = ignore_rules.root.clone();
let mut watcher = notify::poll::PollWatcher::new(
PathEventHandler {
ignore_rules,
sender,
},
Config::default().with_poll_interval(Duration::from_secs(2)),
)?;
watcher.watch(&root, RecursiveMode::Recursive)?;
watch!(watcher);
Ok((watcher, receiver))
}