Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
116
third-party/vendor/notify/src/config.rs
vendored
Normal file
116
third-party/vendor/notify/src/config.rs
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
//! Configuration types
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
/// Indicates whether only the provided directory or its sub-directories as well should be watched
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||
pub enum RecursiveMode {
|
||||
/// Watch all sub-directories as well, including directories created after installing the watch
|
||||
Recursive,
|
||||
|
||||
/// Watch only the provided directory
|
||||
NonRecursive,
|
||||
}
|
||||
|
||||
impl RecursiveMode {
|
||||
pub(crate) fn is_recursive(&self) -> bool {
|
||||
match *self {
|
||||
RecursiveMode::Recursive => true,
|
||||
RecursiveMode::NonRecursive => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Watcher Backend configuration
|
||||
///
|
||||
/// This contains multiple settings that may relate to only one specific backend,
|
||||
/// such as to correctly configure each backend regardless of what is selected during runtime.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use std::time::Duration;
|
||||
/// # use notify::Config;
|
||||
/// let config = Config::default()
|
||||
/// .with_poll_interval(Duration::from_secs(2))
|
||||
/// .with_compare_contents(true);
|
||||
/// ```
|
||||
///
|
||||
/// Some options can be changed during runtime, others have to be set when creating the watcher backend.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Config {
|
||||
/// See [BackendConfig::with_poll_interval]
|
||||
poll_interval: Option<Duration>,
|
||||
|
||||
/// See [BackendConfig::with_compare_contents]
|
||||
compare_contents: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// For the [PollWatcher](crate::PollWatcher) backend.
|
||||
///
|
||||
/// Interval between each re-scan attempt. This can be extremely expensive for large
|
||||
/// file trees so it is recommended to measure and tune accordingly.
|
||||
///
|
||||
/// The default poll frequency is 30 seconds.
|
||||
///
|
||||
/// This will enable automatic polling, overwriting [with_manual_polling](Config::with_manual_polling).
|
||||
pub fn with_poll_interval(mut self, dur: Duration) -> Self {
|
||||
// TODO: v7.0 break signature to option
|
||||
self.poll_interval = Some(dur);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns current setting
|
||||
#[deprecated(
|
||||
since = "6.1.0",
|
||||
note = "use poll_interval_v2 to account for disabled automatic polling"
|
||||
)]
|
||||
pub fn poll_interval(&self) -> Duration {
|
||||
// TODO: v7.0 break signature to option
|
||||
self.poll_interval.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns current setting
|
||||
pub fn poll_interval_v2(&self) -> Option<Duration> {
|
||||
// TODO: v7.0 break signature to option
|
||||
self.poll_interval
|
||||
}
|
||||
|
||||
/// For the [PollWatcher](crate::PollWatcher) backend.
|
||||
///
|
||||
/// Disable automatic polling. Requires calling [crate::PollWatcher::poll] manually.
|
||||
///
|
||||
/// This will disable automatic polling, overwriting [with_poll_interval](Config::with_poll_interval).
|
||||
pub fn with_manual_polling(mut self) -> Self {
|
||||
self.poll_interval = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// For the [PollWatcher](crate::PollWatcher) backend.
|
||||
///
|
||||
/// Optional feature that will evaluate the contents of changed files to determine if
|
||||
/// they have indeed changed using a fast hashing algorithm. This is especially important
|
||||
/// for pseudo filesystems like those on Linux under /sys and /proc which are not obligated
|
||||
/// to respect any other filesystem norms such as modification timestamps, file sizes, etc.
|
||||
/// By enabling this feature, performance will be significantly impacted as all files will
|
||||
/// need to be read and hashed at each `poll_interval`.
|
||||
///
|
||||
/// This can't be changed during runtime. Off by default.
|
||||
pub fn with_compare_contents(mut self, compare_contents: bool) -> Self {
|
||||
self.compare_contents = compare_contents;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns current setting
|
||||
pub fn compare_contents(&self) -> bool {
|
||||
self.compare_contents
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
poll_interval: Some(Duration::from_secs(30)),
|
||||
compare_contents: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
178
third-party/vendor/notify/src/error.rs
vendored
Normal file
178
third-party/vendor/notify/src/error.rs
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
//! Error types
|
||||
|
||||
use crate::Config;
|
||||
use std::error::Error as StdError;
|
||||
use std::path::PathBuf;
|
||||
use std::result::Result as StdResult;
|
||||
use std::{self, fmt, io};
|
||||
|
||||
/// Type alias to use this library's `Error` type in a Result
|
||||
pub type Result<T> = StdResult<T, Error>;
|
||||
|
||||
/// Error kinds
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Generic error
|
||||
///
|
||||
/// May be used in cases where a platform specific error is mapped to this type, or for opaque
|
||||
/// internal errors.
|
||||
Generic(String),
|
||||
|
||||
/// I/O errors.
|
||||
Io(io::Error),
|
||||
|
||||
/// A path does not exist.
|
||||
PathNotFound,
|
||||
|
||||
/// Attempted to remove a watch that does not exist.
|
||||
WatchNotFound,
|
||||
|
||||
/// An invalid value was passed as runtime configuration.
|
||||
InvalidConfig(Config),
|
||||
|
||||
/// Can't watch (more) files, limit on the total number of inotify watches reached
|
||||
MaxFilesWatch,
|
||||
}
|
||||
|
||||
/// Notify error type.
|
||||
///
|
||||
/// Errors are emitted either at creation time of a `Watcher`, or during the event stream. They
|
||||
/// range from kernel errors to filesystem errors to argument errors.
|
||||
///
|
||||
/// Errors can be general, or they can be about specific paths or subtrees. In that later case, the
|
||||
/// error's `paths` field will be populated.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// Kind of the error.
|
||||
pub kind: ErrorKind,
|
||||
|
||||
/// Relevant paths to the error, if any.
|
||||
pub paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Adds a path to the error.
|
||||
pub fn add_path(mut self, path: PathBuf) -> Self {
|
||||
self.paths.push(path);
|
||||
self
|
||||
}
|
||||
|
||||
/// Replaces the paths for the error.
|
||||
pub fn set_paths(mut self, paths: Vec<PathBuf>) -> Self {
|
||||
self.paths = paths;
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new Error with empty paths given its kind.
|
||||
pub fn new(kind: ErrorKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
paths: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new generic Error from a message.
|
||||
pub fn generic(msg: &str) -> Self {
|
||||
Self::new(ErrorKind::Generic(msg.into()))
|
||||
}
|
||||
|
||||
/// Creates a new i/o Error from a stdlib `io::Error`.
|
||||
pub fn io(err: io::Error) -> Self {
|
||||
Self::new(ErrorKind::Io(err))
|
||||
}
|
||||
|
||||
/// Creates a new "path not found" error.
|
||||
pub fn path_not_found() -> Self {
|
||||
Self::new(ErrorKind::PathNotFound)
|
||||
}
|
||||
|
||||
/// Creates a new "watch not found" error.
|
||||
pub fn watch_not_found() -> Self {
|
||||
Self::new(ErrorKind::WatchNotFound)
|
||||
}
|
||||
|
||||
/// Creates a new "invalid config" error from the given `Config`.
|
||||
pub fn invalid_config(config: &Config) -> Self {
|
||||
Self::new(ErrorKind::InvalidConfig(config.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let error = match self.kind {
|
||||
ErrorKind::PathNotFound => "No path was found.".into(),
|
||||
ErrorKind::WatchNotFound => "No watch was found.".into(),
|
||||
ErrorKind::InvalidConfig(ref config) => format!("Invalid configuration: {:?}", config),
|
||||
ErrorKind::Generic(ref err) => err.clone(),
|
||||
ErrorKind::Io(ref err) => err.to_string(),
|
||||
ErrorKind::MaxFilesWatch => "OS file watch limit reached.".into(),
|
||||
};
|
||||
|
||||
if self.paths.is_empty() {
|
||||
write!(f, "{}", error)
|
||||
} else {
|
||||
write!(f, "{} about {:?}", error, self.paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn cause(&self) -> Option<&dyn StdError> {
|
||||
match self.kind {
|
||||
ErrorKind::Io(ref cause) => Some(cause),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Self {
|
||||
Error::io(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
impl<T> From<crossbeam_channel::SendError<T>> for Error {
|
||||
fn from(err: crossbeam_channel::SendError<T>) -> Self {
|
||||
Error::generic(&format!("internal channel disconnect: {:?}", err))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
impl<T> From<std::sync::mpsc::SendError<T>> for Error {
|
||||
fn from(err: std::sync::mpsc::SendError<T>) -> Self {
|
||||
Error::generic(&format!("internal channel disconnect: {:?}", err))
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
impl From<crossbeam_channel::RecvError> for Error {
|
||||
fn from(err: crossbeam_channel::RecvError) -> Self {
|
||||
Error::generic(&format!("internal channel disconnect: {:?}", err))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
impl From<std::sync::mpsc::RecvError> for Error {
|
||||
fn from(err: std::sync::mpsc::RecvError) -> Self {
|
||||
Error::generic(&format!("internal channel disconnect: {:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::PoisonError<T>> for Error {
|
||||
fn from(err: std::sync::PoisonError<T>) -> Self {
|
||||
Error::generic(&format!("internal mutex poisoned: {:?}", err))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_formatted_errors() {
|
||||
let expected = "Some error";
|
||||
|
||||
assert_eq!(expected, format!("{}", Error::generic(expected)));
|
||||
|
||||
assert_eq!(
|
||||
expected,
|
||||
format!(
|
||||
"{}",
|
||||
Error::io(io::Error::new(io::ErrorKind::Other, expected))
|
||||
)
|
||||
);
|
||||
}
|
||||
623
third-party/vendor/notify/src/event.rs
vendored
Normal file
623
third-party/vendor/notify/src/event.rs
vendored
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
// This file is dual-licensed under the Artistic License 2.0 as per the
|
||||
// LICENSE.ARTISTIC file, and the Creative Commons Zero 1.0 license.
|
||||
//! The `Event` type and the hierarchical `EventKind` descriptor.
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An event describing open or close operations on files.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum AccessMode {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when the file is executed, or the folder opened.
|
||||
Execute,
|
||||
|
||||
/// An event emitted when the file is opened for reading.
|
||||
Read,
|
||||
|
||||
/// An event emitted when the file is opened for writing.
|
||||
Write,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event describing non-mutating access operations on files.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum AccessKind {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when the file is read.
|
||||
Read,
|
||||
|
||||
/// An event emitted when the file, or a handle to the file, is opened.
|
||||
Open(AccessMode),
|
||||
|
||||
/// An event emitted when the file, or a handle to the file, is closed.
|
||||
Close(AccessMode),
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event describing creation operations on files.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum CreateKind {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event which results in the creation of a file.
|
||||
File,
|
||||
|
||||
/// An event which results in the creation of a folder.
|
||||
Folder,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event emitted when the data content of a file is changed.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum DataChange {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when the size of the data is changed.
|
||||
Size,
|
||||
|
||||
/// An event emitted when the content of the data is changed.
|
||||
Content,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event emitted when the metadata of a file or folder is changed.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum MetadataKind {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when the access time of the file or folder is changed.
|
||||
AccessTime,
|
||||
|
||||
/// An event emitted when the write or modify time of the file or folder is changed.
|
||||
WriteTime,
|
||||
|
||||
/// An event emitted when the permissions of the file or folder are changed.
|
||||
Permissions,
|
||||
|
||||
/// An event emitted when the ownership of the file or folder is changed.
|
||||
Ownership,
|
||||
|
||||
/// An event emitted when an extended attribute of the file or folder is changed.
|
||||
///
|
||||
/// If the extended attribute's name or type is known, it should be provided in the
|
||||
/// `Info` event attribute.
|
||||
Extended,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event emitted when the name of a file or folder is changed.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum RenameMode {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted on the file or folder resulting from a rename.
|
||||
To,
|
||||
|
||||
/// An event emitted on the file or folder that was renamed.
|
||||
From,
|
||||
|
||||
/// A single event emitted with both the `From` and `To` paths.
|
||||
///
|
||||
/// This event should be emitted when both source and target are known. The paths should be
|
||||
/// provided in this exact order (from, to).
|
||||
Both,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event describing mutation of content, name, or metadata.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "kind", content = "mode"))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum ModifyKind {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when the data content of a file is changed.
|
||||
Data(DataChange),
|
||||
|
||||
/// An event emitted when the metadata of a file or folder is changed.
|
||||
Metadata(MetadataKind),
|
||||
|
||||
/// An event emitted when the name of a file or folder is changed.
|
||||
#[cfg_attr(feature = "serde", serde(rename = "rename"))]
|
||||
Name(RenameMode),
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// An event describing removal operations on files.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "kind"))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum RemoveKind {
|
||||
/// The catch-all case, to be used when the specific kind of event is unknown.
|
||||
Any,
|
||||
|
||||
/// An event emitted when a file is removed.
|
||||
File,
|
||||
|
||||
/// An event emitted when a folder is removed.
|
||||
Folder,
|
||||
|
||||
/// An event which specific kind is known but cannot be represented otherwise.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Top-level event kind.
|
||||
///
|
||||
/// This is arguably the most important classification for events. All subkinds below this one
|
||||
/// represent details that may or may not be available for any particular backend, but most tools
|
||||
/// and Notify systems will only care about which of these four general kinds an event is about.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
|
||||
pub enum EventKind {
|
||||
/// The catch-all event kind, for unsupported/unknown events.
|
||||
///
|
||||
/// This variant should be used as the "else" case when mapping native kernel bitmasks or
|
||||
/// bitmaps, such that if the mask is ever extended with new event types the backend will not
|
||||
/// gain bugs due to not matching new unknown event types.
|
||||
///
|
||||
/// This variant is also the default variant used when Notify is in "imprecise" mode.
|
||||
Any,
|
||||
|
||||
/// An event describing non-mutating access operations on files.
|
||||
///
|
||||
/// This event is about opening and closing file handles, as well as executing files, and any
|
||||
/// other such event that is about accessing files, folders, or other structures rather than
|
||||
/// mutating them.
|
||||
///
|
||||
/// Only some platforms are capable of generating these.
|
||||
Access(AccessKind),
|
||||
|
||||
/// An event describing creation operations on files.
|
||||
///
|
||||
/// This event is about the creation of files, folders, or other structures but not about e.g.
|
||||
/// writing new content into them.
|
||||
Create(CreateKind),
|
||||
|
||||
/// An event describing mutation of content, name, or metadata.
|
||||
///
|
||||
/// This event is about the mutation of files', folders', or other structures' content, name
|
||||
/// (path), or associated metadata (attributes).
|
||||
Modify(ModifyKind),
|
||||
|
||||
/// An event describing removal operations on files.
|
||||
///
|
||||
/// This event is about the removal of files, folders, or other structures but not e.g. erasing
|
||||
/// content from them. This may also be triggered for renames/moves that move files _out of the
|
||||
/// watched subpath_.
|
||||
///
|
||||
/// Some editors also trigger Remove events when saving files as they may opt for removing (or
|
||||
/// renaming) the original then creating a new file in-place.
|
||||
Remove(RemoveKind),
|
||||
|
||||
/// An event not fitting in any of the above four categories.
|
||||
///
|
||||
/// This may be used for meta-events about the watch itself.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl EventKind {
|
||||
/// Indicates whether an event is an Access variant.
|
||||
pub fn is_access(&self) -> bool {
|
||||
matches!(self, EventKind::Access(_))
|
||||
}
|
||||
|
||||
/// Indicates whether an event is a Create variant.
|
||||
pub fn is_create(&self) -> bool {
|
||||
matches!(self, EventKind::Create(_))
|
||||
}
|
||||
|
||||
/// Indicates whether an event is a Modify variant.
|
||||
pub fn is_modify(&self) -> bool {
|
||||
matches!(self, EventKind::Modify(_))
|
||||
}
|
||||
|
||||
/// Indicates whether an event is a Remove variant.
|
||||
pub fn is_remove(&self) -> bool {
|
||||
matches!(self, EventKind::Remove(_))
|
||||
}
|
||||
|
||||
/// Indicates whether an event is an Other variant.
|
||||
pub fn is_other(&self) -> bool {
|
||||
matches!(self, EventKind::Other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventKind {
|
||||
fn default() -> Self {
|
||||
EventKind::Any
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify event.
|
||||
///
|
||||
/// You might want to check [`Event::need_rescan`] to make sure no event was missed before you
|
||||
/// received this one.
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Event {
|
||||
/// Kind or type of the event.
|
||||
///
|
||||
/// This is a hierarchy of enums describing the event as precisely as possible. All enums in
|
||||
/// the hierarchy have two variants always present, `Any` and `Other`, accompanied by one or
|
||||
/// more specific variants.
|
||||
///
|
||||
/// `Any` should be used when more detail about the event is not known beyond the variant
|
||||
/// already selected. For example, `AccessMode::Any` means a file has been accessed, but that's
|
||||
/// all we know.
|
||||
///
|
||||
/// `Other` should be used when more detail _is_ available, but cannot be encoded as one of the
|
||||
/// defined variants. When specifying `Other`, the event attributes should contain an `Info`
|
||||
/// entry with a short string identifying this detail. That string is to be considered part of
|
||||
/// the interface of the backend (i.e. a change should probably be breaking).
|
||||
///
|
||||
/// For example, `CreateKind::Other` with an `Info("mount")` may indicate the binding of a
|
||||
/// mount. The documentation of the particular backend should indicate if any `Other` events
|
||||
/// are generated, and what their description means.
|
||||
///
|
||||
/// The `EventKind::Any` variant should be used as the "else" case when mapping native kernel
|
||||
/// bitmasks or bitmaps, such that if the mask is ever extended with new event types the
|
||||
/// backend will not gain bugs due to not matching new unknown event types.
|
||||
#[cfg_attr(feature = "serde", serde(rename = "type"))]
|
||||
pub kind: EventKind,
|
||||
|
||||
/// Paths the event is about, if known.
|
||||
///
|
||||
/// If an event concerns two or more paths, and the paths are known at the time of event
|
||||
/// creation, they should all go in this `Vec`. Otherwise, using the `Tracker` attr may be more
|
||||
/// appropriate.
|
||||
///
|
||||
/// The order of the paths is likely to be significant! For example, renames where both ends of
|
||||
/// the name change are known will have the "source" path first, and the "target" path last.
|
||||
pub paths: Vec<PathBuf>,
|
||||
|
||||
// "What should be in the struct" and "what can go in the attrs" is an interesting question.
|
||||
//
|
||||
// Technically, the paths could go in the attrs. That would reduce the type size to 4 pointer
|
||||
// widths, instead of 7 like it is now. Anything 8 and below is probably good — on x64 that's
|
||||
// the size of an L1 cache line. The entire kind classification fits in 3 bytes, and an AnyMap
|
||||
// is 3 pointers. A Vec<PathBuf> is another 3 pointers.
|
||||
//
|
||||
// Type size aside, what's behind these structures? A Vec and a PathBuf is stored on the heap.
|
||||
// An AnyMap is stored on the heap. But a Vec is directly there, requiring about one access to
|
||||
// get, while retrieving anything in the AnyMap requires some accesses as overhead.
|
||||
//
|
||||
// So things that are used often should be on the struct, and things that are used more rarely
|
||||
// should go in the attrs. Additionally, arbitrary data can _only_ go in the attrs.
|
||||
//
|
||||
// The kind and the paths vie for first place on this scale, depending on how downstream wishes
|
||||
// to use the information. Everything else is secondary. So far, that's why paths live here.
|
||||
//
|
||||
// In the future, it might be possible to have more data and to benchmark things properly, so
|
||||
// the performance can be actually quantified. Also, it might turn out that I have no idea what
|
||||
// I was talking about, so the above may be discarded or reviewed. We'll see!
|
||||
//
|
||||
/// Additional attributes of the event.
|
||||
///
|
||||
/// Arbitrary data may be added to this field, without restriction beyond the `Sync` and
|
||||
/// `Clone` properties. Some data added here is considered for comparing and hashing, but not
|
||||
/// all: at this writing this is `Tracker`, `Flag`, `Info`, and `Source`.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub attrs: EventAttributes,
|
||||
}
|
||||
|
||||
/// Additional attributes of the event.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct EventAttributes {
|
||||
#[cfg_attr(feature = "serde", serde(flatten))]
|
||||
inner: Option<Box<EventAttributesInner>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
struct EventAttributesInner {
|
||||
/// Tracking ID for events that are related.
|
||||
///
|
||||
/// For events generated by backends with the `TrackRelated` capability. Those backends _may_
|
||||
/// emit events that are related to each other, and tag those with an identical "tracking id"
|
||||
/// or "cookie". The value is normalised to `usize`.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
tracker: Option<usize>,
|
||||
|
||||
/// Special Notify flag on the event.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
flag: Option<Flag>,
|
||||
|
||||
/// Additional information on the event.
|
||||
///
|
||||
/// This is to be used for all `Other` variants of the event kind hierarchy. The variant
|
||||
/// indicates that a consumer should look into the `attrs` for an `Info` value; if that value
|
||||
/// is missing it should be considered a backend bug.
|
||||
///
|
||||
/// This attribute may also be present for non-`Other` variants of the event kind, if doing so
|
||||
/// provides useful precision. For example, the `Modify(Metadata(Extended))` kind suggests
|
||||
/// using this attribute when information about _what_ extended metadata changed is available.
|
||||
///
|
||||
/// This should be a short string, and changes may be considered breaking.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
info: Option<String>,
|
||||
|
||||
/// The source of the event.
|
||||
///
|
||||
/// In most cases this should be a short string, identifying the backend unambiguously. In some
|
||||
/// cases this may be dynamically generated, but should contain a prefix to make it unambiguous
|
||||
/// between backends.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
source: Option<String>,
|
||||
|
||||
/// The process ID of the originator of the event.
|
||||
///
|
||||
/// This attribute is experimental and, while included in Notify itself, is not considered
|
||||
/// stable or standard enough to be part of the serde, eq, hash, and debug representations.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing, skip_deserializing)
|
||||
)]
|
||||
process_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl EventAttributes {
|
||||
/// Creates a new `EventAttributes`.
|
||||
pub fn new() -> Self {
|
||||
Self { inner: None }
|
||||
}
|
||||
|
||||
/// Retrieves the tracker ID for an event directly, if present.
|
||||
pub fn tracker(&self) -> Option<usize> {
|
||||
self.inner.as_ref().and_then(|inner| inner.tracker)
|
||||
}
|
||||
|
||||
/// Retrieves the Notify flag for an event directly, if present.
|
||||
pub fn flag(&self) -> Option<Flag> {
|
||||
self.inner.as_ref().and_then(|inner| inner.flag)
|
||||
}
|
||||
|
||||
/// Retrieves the additional info for an event directly, if present.
|
||||
pub fn info(&self) -> Option<&str> {
|
||||
self.inner.as_ref().and_then(|inner| inner.info.as_deref())
|
||||
}
|
||||
|
||||
/// Retrieves the source for an event directly, if present.
|
||||
pub fn source(&self) -> Option<&str> {
|
||||
self.inner
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.source.as_deref())
|
||||
}
|
||||
|
||||
/// The process ID of the originator of the event.
|
||||
///
|
||||
/// This attribute is experimental and, while included in Notify itself, is not considered
|
||||
/// stable or standard enough to be part of the serde, eq, hash, and debug representations.
|
||||
pub fn process_id(&self) -> Option<u32> {
|
||||
self.inner.as_ref().and_then(|inner| inner.process_id)
|
||||
}
|
||||
|
||||
/// Sets the tracker.
|
||||
pub fn set_tracker(&mut self, tracker: usize) {
|
||||
self.inner_mut().tracker = Some(tracker);
|
||||
}
|
||||
|
||||
/// Sets the Notify flag onto the event.
|
||||
pub fn set_flag(&mut self, flag: Flag) {
|
||||
self.inner_mut().flag = Some(flag);
|
||||
}
|
||||
|
||||
/// Sets additional info onto the event.
|
||||
pub fn set_info(&mut self, info: &str) {
|
||||
self.inner_mut().info = Some(info.to_string());
|
||||
}
|
||||
|
||||
/// Sets the process id onto the event.
|
||||
pub fn set_process_id(&mut self, process_id: u32) {
|
||||
self.inner_mut().process_id = Some(process_id)
|
||||
}
|
||||
|
||||
fn inner_mut(&mut self) -> &mut EventAttributesInner {
|
||||
self.inner
|
||||
.get_or_insert_with(|| Box::new(Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Special Notify flag on the event.
|
||||
///
|
||||
/// This attribute is used to flag certain kinds of events that Notify either marks or generates in
|
||||
/// particular ways.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum Flag {
|
||||
/// Rescan notices are emitted by some platforms (and may also be emitted by Notify itself).
|
||||
/// They indicate either a lapse in the events or a change in the filesystem such that events
|
||||
/// received so far can no longer be relied on to represent the state of the filesystem now.
|
||||
///
|
||||
/// An application that simply reacts to file changes may not care about this. An application
|
||||
/// that keeps an in-memory representation of the filesystem will need to care, and will need
|
||||
/// to refresh that representation directly from the filesystem.
|
||||
Rescan,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Returns whether some events may have been missed. If true, you should assume any file or
|
||||
/// folder might have been modified.
|
||||
///
|
||||
/// See [`Flag::Rescan`] for more information.
|
||||
pub fn need_rescan(&self) -> bool {
|
||||
matches!(self.flag(), Some(Flag::Rescan))
|
||||
}
|
||||
/// Retrieves the tracker ID for an event directly, if present.
|
||||
pub fn tracker(&self) -> Option<usize> {
|
||||
self.attrs.tracker()
|
||||
}
|
||||
|
||||
/// Retrieves the Notify flag for an event directly, if present.
|
||||
pub fn flag(&self) -> Option<Flag> {
|
||||
self.attrs.flag()
|
||||
}
|
||||
|
||||
/// Retrieves the additional info for an event directly, if present.
|
||||
pub fn info(&self) -> Option<&str> {
|
||||
self.attrs.info()
|
||||
}
|
||||
|
||||
/// Retrieves the source for an event directly, if present.
|
||||
pub fn source(&self) -> Option<&str> {
|
||||
self.attrs.source()
|
||||
}
|
||||
|
||||
/// Creates a new `Event` given a kind.
|
||||
pub fn new(kind: EventKind) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
paths: Vec::new(),
|
||||
attrs: EventAttributes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the kind.
|
||||
pub fn set_kind(mut self, kind: EventKind) -> Self {
|
||||
self.kind = kind;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a path to the event.
|
||||
pub fn add_path(mut self, path: PathBuf) -> Self {
|
||||
self.paths.push(path);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a path to the event if the argument is Some.
|
||||
pub fn add_some_path(self, path: Option<PathBuf>) -> Self {
|
||||
if let Some(path) = path {
|
||||
self.add_path(path)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the tracker.
|
||||
pub fn set_tracker(mut self, tracker: usize) -> Self {
|
||||
self.attrs.set_tracker(tracker);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets additional info onto the event.
|
||||
pub fn set_info(mut self, info: &str) -> Self {
|
||||
self.attrs.set_info(info);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the Notify flag onto the event.
|
||||
pub fn set_flag(mut self, flag: Flag) -> Self {
|
||||
self.attrs.set_flag(flag);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the process id onto the event.
|
||||
pub fn set_process_id(mut self, process_id: u32) -> Self {
|
||||
self.attrs.set_process_id(process_id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Event {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Event")
|
||||
.field("kind", &self.kind)
|
||||
.field("paths", &self.paths)
|
||||
.field("attr:tracker", &self.tracker())
|
||||
.field("attr:flag", &self.flag())
|
||||
.field("attr:info", &self.info())
|
||||
.field("attr:source", &self.source())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl Default for Event {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kind: EventKind::default(),
|
||||
paths: Vec::new(),
|
||||
attrs: EventAttributes::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Event {}
|
||||
impl PartialEq for Event {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.kind.eq(&other.kind)
|
||||
&& self.paths.eq(&other.paths)
|
||||
&& self.tracker().eq(&other.tracker())
|
||||
&& self.flag().eq(&other.flag())
|
||||
&& self.info().eq(&other.info())
|
||||
&& self.source().eq(&other.source())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Event {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.hash(state);
|
||||
self.paths.hash(state);
|
||||
self.tracker().hash(state);
|
||||
self.flag().hash(state);
|
||||
self.info().hash(state);
|
||||
self.source().hash(state);
|
||||
}
|
||||
}
|
||||
615
third-party/vendor/notify/src/fsevent.rs
vendored
Normal file
615
third-party/vendor/notify/src/fsevent.rs
vendored
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
//! Watcher implementation for Darwin's FSEvents API
|
||||
//!
|
||||
//! The FSEvents API provides a mechanism to notify clients about directories they ought to re-scan
|
||||
//! in order to keep their internal data structures up-to-date with respect to the true state of
|
||||
//! the file system. (For example, when files or directories are created, modified, or removed.) It
|
||||
//! sends these notifications "in bulk", possibly notifying the client of changes to several
|
||||
//! directories in a single callback.
|
||||
//!
|
||||
//! For more information see the [FSEvents API reference][ref].
|
||||
//!
|
||||
//! TODO: document event translation
|
||||
//!
|
||||
//! [ref]: https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/
|
||||
|
||||
#![allow(non_upper_case_globals, dead_code)]
|
||||
|
||||
use crate::event::*;
|
||||
use crate::{unbounded, Config, Error, EventHandler, RecursiveMode, Result, Sender, Watcher};
|
||||
use fsevent_sys as fs;
|
||||
use fsevent_sys::core_foundation as cf;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CStr;
|
||||
use std::fmt;
|
||||
use std::os::raw;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct StreamFlags: u32 {
|
||||
const NONE = fs::kFSEventStreamEventFlagNone;
|
||||
const MUST_SCAN_SUBDIRS = fs::kFSEventStreamEventFlagMustScanSubDirs;
|
||||
const USER_DROPPED = fs::kFSEventStreamEventFlagUserDropped;
|
||||
const KERNEL_DROPPED = fs::kFSEventStreamEventFlagKernelDropped;
|
||||
const IDS_WRAPPED = fs::kFSEventStreamEventFlagEventIdsWrapped;
|
||||
const HISTORY_DONE = fs::kFSEventStreamEventFlagHistoryDone;
|
||||
const ROOT_CHANGED = fs::kFSEventStreamEventFlagRootChanged;
|
||||
const MOUNT = fs::kFSEventStreamEventFlagMount;
|
||||
const UNMOUNT = fs::kFSEventStreamEventFlagUnmount;
|
||||
const ITEM_CREATED = fs::kFSEventStreamEventFlagItemCreated;
|
||||
const ITEM_REMOVED = fs::kFSEventStreamEventFlagItemRemoved;
|
||||
const INODE_META_MOD = fs::kFSEventStreamEventFlagItemInodeMetaMod;
|
||||
const ITEM_RENAMED = fs::kFSEventStreamEventFlagItemRenamed;
|
||||
const ITEM_MODIFIED = fs::kFSEventStreamEventFlagItemModified;
|
||||
const FINDER_INFO_MOD = fs::kFSEventStreamEventFlagItemFinderInfoMod;
|
||||
const ITEM_CHANGE_OWNER = fs::kFSEventStreamEventFlagItemChangeOwner;
|
||||
const ITEM_XATTR_MOD = fs::kFSEventStreamEventFlagItemXattrMod;
|
||||
const IS_FILE = fs::kFSEventStreamEventFlagItemIsFile;
|
||||
const IS_DIR = fs::kFSEventStreamEventFlagItemIsDir;
|
||||
const IS_SYMLINK = fs::kFSEventStreamEventFlagItemIsSymlink;
|
||||
const OWN_EVENT = fs::kFSEventStreamEventFlagOwnEvent;
|
||||
const IS_HARDLINK = fs::kFSEventStreamEventFlagItemIsHardlink;
|
||||
const IS_LAST_HARDLINK = fs::kFSEventStreamEventFlagItemIsLastHardlink;
|
||||
const ITEM_CLONED = fs::kFSEventStreamEventFlagItemCloned;
|
||||
}
|
||||
}
|
||||
|
||||
/// FSEvents-based `Watcher` implementation
|
||||
pub struct FsEventWatcher {
|
||||
paths: cf::CFMutableArrayRef,
|
||||
since_when: fs::FSEventStreamEventId,
|
||||
latency: cf::CFTimeInterval,
|
||||
flags: fs::FSEventStreamCreateFlags,
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
runloop: Option<(cf::CFRunLoopRef, thread::JoinHandle<()>)>,
|
||||
recursive_info: HashMap<PathBuf, bool>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for FsEventWatcher {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("FsEventWatcher")
|
||||
.field("paths", &self.paths)
|
||||
.field("since_when", &self.since_when)
|
||||
.field("latency", &self.latency)
|
||||
.field("flags", &self.flags)
|
||||
.field("event_handler", &Arc::as_ptr(&self.event_handler))
|
||||
.field("runloop", &self.runloop)
|
||||
.field("recursive_info", &self.recursive_info)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// CFMutableArrayRef is a type alias to *mut libc::c_void, so FsEventWatcher is not Send/Sync
|
||||
// automatically. It's Send because the pointer is not used in other threads.
|
||||
unsafe impl Send for FsEventWatcher {}
|
||||
|
||||
// It's Sync because all methods that change the mutable state use `&mut self`.
|
||||
unsafe impl Sync for FsEventWatcher {}
|
||||
|
||||
fn translate_flags(flags: StreamFlags, precise: bool) -> Vec<Event> {
|
||||
let mut evs = Vec::new();
|
||||
|
||||
// «Denotes a sentinel event sent to mark the end of the "historical" events
|
||||
// sent as a result of specifying a `sinceWhen` value in the FSEvents.Create
|
||||
// call that created this event stream. After invoking the client's callback
|
||||
// with all the "historical" events that occurred before now, the client's
|
||||
// callback will be invoked with an event where the HistoryDone flag is set.
|
||||
// The client should ignore the path supplied in this callback.»
|
||||
// — https://www.mbsplugins.eu/FSEventsNextEvent.shtml
|
||||
//
|
||||
// As a result, we just stop processing here and return an empty vec, which
|
||||
// will ignore this completely and not emit any Events whatsoever.
|
||||
if flags.contains(StreamFlags::HISTORY_DONE) {
|
||||
return evs;
|
||||
}
|
||||
|
||||
// FSEvents provides two possible hints as to why events were dropped,
|
||||
// however documentation on what those mean is scant, so we just pass them
|
||||
// through in the info attr field. The intent is clear enough, and the
|
||||
// additional information is provided if the user wants it.
|
||||
if flags.contains(StreamFlags::MUST_SCAN_SUBDIRS) {
|
||||
let e = Event::new(EventKind::Other).set_flag(Flag::Rescan);
|
||||
evs.push(if flags.contains(StreamFlags::USER_DROPPED) {
|
||||
e.set_info("rescan: user dropped")
|
||||
} else if flags.contains(StreamFlags::KERNEL_DROPPED) {
|
||||
e.set_info("rescan: kernel dropped")
|
||||
} else {
|
||||
e
|
||||
});
|
||||
}
|
||||
|
||||
// In imprecise mode, let's not even bother parsing the kind of the event
|
||||
// except for the above very special events.
|
||||
if !precise {
|
||||
evs.push(Event::new(EventKind::Any));
|
||||
return evs;
|
||||
}
|
||||
|
||||
// This is most likely a rename or a removal. We assume rename but may want
|
||||
// to figure out if it was a removal some way later (TODO). To denote the
|
||||
// special nature of the event, we add an info string.
|
||||
if flags.contains(StreamFlags::ROOT_CHANGED) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::From)))
|
||||
.set_info("root changed"),
|
||||
);
|
||||
}
|
||||
|
||||
// A path was mounted at the event path; we treat that as a create.
|
||||
if flags.contains(StreamFlags::MOUNT) {
|
||||
evs.push(Event::new(EventKind::Create(CreateKind::Other)).set_info("mount"));
|
||||
}
|
||||
|
||||
// A path was unmounted at the event path; we treat that as a remove.
|
||||
if flags.contains(StreamFlags::UNMOUNT) {
|
||||
evs.push(Event::new(EventKind::Remove(RemoveKind::Other)).set_info("mount"));
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::ITEM_CREATED) {
|
||||
evs.push(if flags.contains(StreamFlags::IS_DIR) {
|
||||
Event::new(EventKind::Create(CreateKind::Folder))
|
||||
} else if flags.contains(StreamFlags::IS_FILE) {
|
||||
Event::new(EventKind::Create(CreateKind::File))
|
||||
} else {
|
||||
let e = Event::new(EventKind::Create(CreateKind::Other));
|
||||
if flags.contains(StreamFlags::IS_SYMLINK) {
|
||||
e.set_info("is: symlink")
|
||||
} else if flags.contains(StreamFlags::IS_HARDLINK) {
|
||||
e.set_info("is: hardlink")
|
||||
} else if flags.contains(StreamFlags::ITEM_CLONED) {
|
||||
e.set_info("is: clone")
|
||||
} else {
|
||||
Event::new(EventKind::Create(CreateKind::Any))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::ITEM_REMOVED) {
|
||||
evs.push(if flags.contains(StreamFlags::IS_DIR) {
|
||||
Event::new(EventKind::Remove(RemoveKind::Folder))
|
||||
} else if flags.contains(StreamFlags::IS_FILE) {
|
||||
Event::new(EventKind::Remove(RemoveKind::File))
|
||||
} else {
|
||||
let e = Event::new(EventKind::Remove(RemoveKind::Other));
|
||||
if flags.contains(StreamFlags::IS_SYMLINK) {
|
||||
e.set_info("is: symlink")
|
||||
} else if flags.contains(StreamFlags::IS_HARDLINK) {
|
||||
e.set_info("is: hardlink")
|
||||
} else if flags.contains(StreamFlags::ITEM_CLONED) {
|
||||
e.set_info("is: clone")
|
||||
} else {
|
||||
Event::new(EventKind::Remove(RemoveKind::Any))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// FSEvents provides no mechanism to associate the old and new sides of a
|
||||
// rename event.
|
||||
if flags.contains(StreamFlags::ITEM_RENAMED) {
|
||||
evs.push(Event::new(EventKind::Modify(ModifyKind::Name(
|
||||
RenameMode::Any,
|
||||
))));
|
||||
}
|
||||
|
||||
// This is only described as "metadata changed", but it may be that it's
|
||||
// only emitted for some more precise subset of events... if so, will need
|
||||
// amending, but for now we have an Any-shaped bucket to put it in.
|
||||
if flags.contains(StreamFlags::INODE_META_MOD) {
|
||||
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
|
||||
MetadataKind::Any,
|
||||
))));
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::FINDER_INFO_MOD) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Metadata(MetadataKind::Other)))
|
||||
.set_info("meta: finder info"),
|
||||
);
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::ITEM_CHANGE_OWNER) {
|
||||
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
|
||||
MetadataKind::Ownership,
|
||||
))));
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::ITEM_XATTR_MOD) {
|
||||
evs.push(Event::new(EventKind::Modify(ModifyKind::Metadata(
|
||||
MetadataKind::Extended,
|
||||
))));
|
||||
}
|
||||
|
||||
// This is specifically described as a data change, which we take to mean
|
||||
// is a content change.
|
||||
if flags.contains(StreamFlags::ITEM_MODIFIED) {
|
||||
evs.push(Event::new(EventKind::Modify(ModifyKind::Data(
|
||||
DataChange::Content,
|
||||
))));
|
||||
}
|
||||
|
||||
if flags.contains(StreamFlags::OWN_EVENT) {
|
||||
for ev in &mut evs {
|
||||
*ev = std::mem::take(ev).set_process_id(std::process::id());
|
||||
}
|
||||
}
|
||||
|
||||
evs
|
||||
}
|
||||
|
||||
struct StreamContextInfo {
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
recursive_info: HashMap<PathBuf, bool>,
|
||||
}
|
||||
|
||||
// Free the context when the stream created by `FSEventStreamCreate` is released.
|
||||
extern "C" fn release_context(info: *const libc::c_void) {
|
||||
// Safety:
|
||||
// - The [documentation] for `FSEventStreamContext` states that `release` is only
|
||||
// called when the stream is deallocated, so it is safe to convert `info` back into a
|
||||
// box and drop it.
|
||||
//
|
||||
// [docs]: https://developer.apple.com/documentation/coreservices/fseventstreamcontext?language=objc
|
||||
unsafe {
|
||||
drop(Box::from_raw(
|
||||
info as *const StreamContextInfo as *mut StreamContextInfo,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
/// Indicates whether the run loop is waiting for an event.
|
||||
fn CFRunLoopIsWaiting(runloop: cf::CFRunLoopRef) -> cf::Boolean;
|
||||
}
|
||||
|
||||
impl FsEventWatcher {
|
||||
fn from_event_handler(event_handler: Arc<Mutex<dyn EventHandler>>) -> Result<Self> {
|
||||
Ok(FsEventWatcher {
|
||||
paths: unsafe {
|
||||
cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks)
|
||||
},
|
||||
since_when: fs::kFSEventStreamEventIdSinceNow,
|
||||
latency: 0.0,
|
||||
flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer,
|
||||
event_handler,
|
||||
runloop: None,
|
||||
recursive_info: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
self.stop();
|
||||
let result = self.append_path(path, recursive_mode);
|
||||
// ignore return error: may be empty path list
|
||||
let _ = self.run();
|
||||
result
|
||||
}
|
||||
|
||||
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
|
||||
self.stop();
|
||||
let result = self.remove_path(path);
|
||||
// ignore return error: may be empty path list
|
||||
let _ = self.run();
|
||||
result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_running(&self) -> bool {
|
||||
self.runloop.is_some()
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
if !self.is_running() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((runloop, thread_handle)) = self.runloop.take() {
|
||||
unsafe {
|
||||
let runloop = runloop as *mut raw::c_void;
|
||||
|
||||
while CFRunLoopIsWaiting(runloop) == 0 {
|
||||
thread::yield_now();
|
||||
}
|
||||
|
||||
cf::CFRunLoopStop(runloop);
|
||||
}
|
||||
|
||||
// Wait for the thread to shut down.
|
||||
thread_handle.join().expect("thread to shut down");
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_path(&mut self, path: &Path) -> Result<()> {
|
||||
let str_path = path.to_str().unwrap();
|
||||
unsafe {
|
||||
let mut err: cf::CFErrorRef = ptr::null_mut();
|
||||
let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err);
|
||||
if cf_path.is_null() {
|
||||
cf::CFRelease(err as cf::CFRef);
|
||||
return Err(Error::watch_not_found().add_path(path.into()));
|
||||
}
|
||||
|
||||
let mut to_remove = Vec::new();
|
||||
for idx in 0..cf::CFArrayGetCount(self.paths) {
|
||||
let item = cf::CFArrayGetValueAtIndex(self.paths, idx);
|
||||
if cf::CFStringCompare(item, cf_path, cf::kCFCompareCaseInsensitive)
|
||||
== cf::kCFCompareEqualTo
|
||||
{
|
||||
to_remove.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
cf::CFRelease(cf_path);
|
||||
|
||||
for idx in to_remove.iter().rev() {
|
||||
cf::CFArrayRemoveValueAtIndex(self.paths, *idx);
|
||||
}
|
||||
}
|
||||
let p = if let Ok(canonicalized_path) = path.canonicalize() {
|
||||
canonicalized_path
|
||||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
match self.recursive_info.remove(&p) {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(Error::watch_not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c
|
||||
fn append_path(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
if !path.exists() {
|
||||
return Err(Error::path_not_found().add_path(path.into()));
|
||||
}
|
||||
let canonical_path = path.to_path_buf().canonicalize()?;
|
||||
let str_path = path.to_str().unwrap();
|
||||
unsafe {
|
||||
let mut err: cf::CFErrorRef = ptr::null_mut();
|
||||
let cf_path = cf::str_path_to_cfstring_ref(str_path, &mut err);
|
||||
if cf_path.is_null() {
|
||||
// Most likely the directory was deleted, or permissions changed,
|
||||
// while the above code was running.
|
||||
cf::CFRelease(err as cf::CFRef);
|
||||
return Err(Error::path_not_found().add_path(path.into()));
|
||||
}
|
||||
cf::CFArrayAppendValue(self.paths, cf_path);
|
||||
cf::CFRelease(cf_path);
|
||||
}
|
||||
self.recursive_info
|
||||
.insert(canonical_path, recursive_mode.is_recursive());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self) -> Result<()> {
|
||||
if unsafe { cf::CFArrayGetCount(self.paths) } == 0 {
|
||||
// TODO: Reconstruct and add paths to error
|
||||
return Err(Error::path_not_found());
|
||||
}
|
||||
|
||||
// We need to associate the stream context with our callback in order to propagate events
|
||||
// to the rest of the system. This will be owned by the stream, and will be freed when the
|
||||
// stream is closed. This means we will leak the context if we panic before reacing
|
||||
// `FSEventStreamRelease`.
|
||||
let context = Box::into_raw(Box::new(StreamContextInfo {
|
||||
event_handler: self.event_handler.clone(),
|
||||
recursive_info: self.recursive_info.clone(),
|
||||
}));
|
||||
|
||||
let stream_context = fs::FSEventStreamContext {
|
||||
version: 0,
|
||||
info: context as *mut libc::c_void,
|
||||
retain: None,
|
||||
release: Some(release_context),
|
||||
copy_description: None,
|
||||
};
|
||||
|
||||
let stream = unsafe {
|
||||
fs::FSEventStreamCreate(
|
||||
cf::kCFAllocatorDefault,
|
||||
callback,
|
||||
&stream_context,
|
||||
self.paths,
|
||||
self.since_when,
|
||||
self.latency,
|
||||
self.flags,
|
||||
)
|
||||
};
|
||||
|
||||
// Wrapper to help send CFRef types across threads.
|
||||
struct CFSendWrapper(cf::CFRef);
|
||||
|
||||
// Safety:
|
||||
// - According to the Apple documentation, it's safe to move `CFRef`s across threads.
|
||||
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
|
||||
unsafe impl Send for CFSendWrapper {}
|
||||
|
||||
// move into thread
|
||||
let stream = CFSendWrapper(stream);
|
||||
|
||||
// channel to pass runloop around
|
||||
let (rl_tx, rl_rx) = unbounded();
|
||||
|
||||
let thread_handle = thread::Builder::new()
|
||||
.name("notify-rs fsevents loop".to_string())
|
||||
.spawn(move || {
|
||||
let _ = &stream;
|
||||
let stream = stream.0;
|
||||
|
||||
unsafe {
|
||||
let cur_runloop = cf::CFRunLoopGetCurrent();
|
||||
|
||||
fs::FSEventStreamScheduleWithRunLoop(
|
||||
stream,
|
||||
cur_runloop,
|
||||
cf::kCFRunLoopDefaultMode,
|
||||
);
|
||||
fs::FSEventStreamStart(stream);
|
||||
|
||||
// the calling to CFRunLoopRun will be terminated by CFRunLoopStop call in drop()
|
||||
rl_tx
|
||||
.send(CFSendWrapper(cur_runloop))
|
||||
.expect("Unable to send runloop to watcher");
|
||||
|
||||
cf::CFRunLoopRun();
|
||||
fs::FSEventStreamStop(stream);
|
||||
fs::FSEventStreamInvalidate(stream);
|
||||
fs::FSEventStreamRelease(stream);
|
||||
}
|
||||
})?;
|
||||
// block until runloop has been sent
|
||||
self.runloop = Some((rl_rx.recv().unwrap().0, thread_handle));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_raw_mode(&mut self, _config: Config, tx: Sender<Result<bool>>) {
|
||||
tx.send(Ok(false))
|
||||
.expect("configuration channel disconnect");
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn callback(
|
||||
stream_ref: fs::FSEventStreamRef,
|
||||
info: *mut libc::c_void,
|
||||
num_events: libc::size_t, // size_t numEvents
|
||||
event_paths: *mut libc::c_void, // void *eventPaths
|
||||
event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[]
|
||||
event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[]
|
||||
) {
|
||||
unsafe {
|
||||
callback_impl(
|
||||
stream_ref,
|
||||
info,
|
||||
num_events,
|
||||
event_paths,
|
||||
event_flags,
|
||||
event_ids,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn callback_impl(
|
||||
_stream_ref: fs::FSEventStreamRef,
|
||||
info: *mut libc::c_void,
|
||||
num_events: libc::size_t, // size_t numEvents
|
||||
event_paths: *mut libc::c_void, // void *eventPaths
|
||||
event_flags: *const fs::FSEventStreamEventFlags, // const FSEventStreamEventFlags eventFlags[]
|
||||
_event_ids: *const fs::FSEventStreamEventId, // const FSEventStreamEventId eventIds[]
|
||||
) {
|
||||
let event_paths = event_paths as *const *const libc::c_char;
|
||||
let info = info as *const StreamContextInfo;
|
||||
let event_handler = &(*info).event_handler;
|
||||
|
||||
for p in 0..num_events {
|
||||
let path = CStr::from_ptr(*event_paths.add(p))
|
||||
.to_str()
|
||||
.expect("Invalid UTF8 string.");
|
||||
let path = PathBuf::from(path);
|
||||
|
||||
let flag = *event_flags.add(p);
|
||||
let flag = StreamFlags::from_bits(flag).unwrap_or_else(|| {
|
||||
panic!("Unable to decode StreamFlags: {}", flag);
|
||||
});
|
||||
|
||||
let mut handle_event = false;
|
||||
for (p, r) in &(*info).recursive_info {
|
||||
if path.starts_with(p) {
|
||||
if *r || &path == p {
|
||||
handle_event = true;
|
||||
break;
|
||||
} else if let Some(parent_path) = path.parent() {
|
||||
if parent_path == p {
|
||||
handle_event = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !handle_event {
|
||||
continue;
|
||||
}
|
||||
|
||||
log::trace!("FSEvent: path = `{}`, flag = {:?}", path.display(), flag);
|
||||
|
||||
for ev in translate_flags(flag, true).into_iter() {
|
||||
// TODO: precise
|
||||
let ev = ev.add_path(path.clone());
|
||||
let mut event_handler = event_handler.lock().expect("lock not to be poisoned");
|
||||
event_handler.handle_event(Ok(ev));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for FsEventWatcher {
|
||||
/// Create a new watcher.
|
||||
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
|
||||
Self::from_event_handler(Arc::new(Mutex::new(event_handler)))
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
self.watch_inner(path, recursive_mode)
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()> {
|
||||
self.unwatch_inner(path)
|
||||
}
|
||||
|
||||
fn configure(&mut self, config: Config) -> Result<bool> {
|
||||
let (tx, rx) = unbounded();
|
||||
self.configure_raw_mode(config, tx);
|
||||
rx.recv()?
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
crate::WatcherKind::Fsevent
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FsEventWatcher {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
unsafe {
|
||||
cf::CFRelease(self.paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fsevent_watcher_drop() {
|
||||
use super::*;
|
||||
use std::time::Duration;
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
{
|
||||
let mut watcher = FsEventWatcher::new(tx, Default::default()).unwrap();
|
||||
watcher.watch(dir.path(), RecursiveMode::Recursive).unwrap();
|
||||
thread::sleep(Duration::from_millis(2000));
|
||||
println!("is running -> {}", watcher.is_running());
|
||||
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
watcher.unwatch(dir.path()).unwrap();
|
||||
println!("is running -> {}", watcher.is_running());
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
|
||||
for res in rx {
|
||||
let e = res.unwrap();
|
||||
println!("debug => {:?} {:?}", e.kind, e.paths);
|
||||
}
|
||||
|
||||
println!("in test: {} works", file!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_steam_context_info_send_and_sync() {
|
||||
fn check_send<T: Send + Sync>() {}
|
||||
check_send::<StreamContextInfo>();
|
||||
}
|
||||
577
third-party/vendor/notify/src/inotify.rs
vendored
Normal file
577
third-party/vendor/notify/src/inotify.rs
vendored
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
//! Watcher implementation for the inotify Linux API
|
||||
//!
|
||||
//! The inotify API provides a mechanism for monitoring filesystem events. Inotify can be used to
|
||||
//! monitor individual files, or to monitor directories. When a directory is monitored, inotify
|
||||
//! will return events for the directory itself, and for files inside the directory.
|
||||
|
||||
use super::event::*;
|
||||
use super::{Config, Error, ErrorKind, EventHandler, RecursiveMode, Result, Watcher};
|
||||
use crate::{bounded, unbounded, BoundSender, Receiver, Sender};
|
||||
use inotify as inotify_sys;
|
||||
use inotify_sys::{EventMask, Inotify, WatchDescriptor, WatchMask};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::metadata;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const INOTIFY: mio::Token = mio::Token(0);
|
||||
const MESSAGE: mio::Token = mio::Token(1);
|
||||
|
||||
// The EventLoop will set up a mio::Poll and use it to wait for the following:
|
||||
//
|
||||
// - messages telling it what to do
|
||||
//
|
||||
// - events telling it that something has happened on one of the watched files.
|
||||
struct EventLoop {
|
||||
running: bool,
|
||||
poll: mio::Poll,
|
||||
event_loop_waker: Arc<mio::Waker>,
|
||||
event_loop_tx: Sender<EventLoopMsg>,
|
||||
event_loop_rx: Receiver<EventLoopMsg>,
|
||||
inotify: Option<Inotify>,
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
watches: HashMap<PathBuf, (WatchDescriptor, WatchMask, bool)>,
|
||||
paths: HashMap<WatchDescriptor, PathBuf>,
|
||||
rename_event: Option<Event>,
|
||||
}
|
||||
|
||||
/// Watcher implementation based on inotify
|
||||
#[derive(Debug)]
|
||||
pub struct INotifyWatcher {
|
||||
channel: Sender<EventLoopMsg>,
|
||||
waker: Arc<mio::Waker>,
|
||||
}
|
||||
|
||||
enum EventLoopMsg {
|
||||
AddWatch(PathBuf, RecursiveMode, Sender<Result<()>>),
|
||||
RemoveWatch(PathBuf, Sender<Result<()>>),
|
||||
Shutdown,
|
||||
Configure(Config, BoundSender<Result<bool>>),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_watch_by_event(
|
||||
path: &Option<PathBuf>,
|
||||
event: &inotify_sys::Event<&OsStr>,
|
||||
watches: &HashMap<PathBuf, (WatchDescriptor, WatchMask, bool)>,
|
||||
add_watches: &mut Vec<PathBuf>,
|
||||
) {
|
||||
if let Some(ref path) = *path {
|
||||
if event.mask.contains(EventMask::ISDIR) {
|
||||
if let Some(parent_path) = path.parent() {
|
||||
if let Some(&(_, _, is_recursive)) = watches.get(parent_path) {
|
||||
if is_recursive {
|
||||
add_watches.push(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn remove_watch_by_event(
|
||||
path: &Option<PathBuf>,
|
||||
watches: &HashMap<PathBuf, (WatchDescriptor, WatchMask, bool)>,
|
||||
remove_watches: &mut Vec<PathBuf>,
|
||||
) {
|
||||
if let Some(ref path) = *path {
|
||||
if watches.contains_key(path) {
|
||||
remove_watches.push(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(inotify: Inotify, event_handler: Box<dyn EventHandler>) -> Result<Self> {
|
||||
let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
|
||||
let poll = mio::Poll::new()?;
|
||||
|
||||
let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
|
||||
|
||||
let inotify_fd = inotify.as_raw_fd();
|
||||
let mut evented_inotify = mio::unix::SourceFd(&inotify_fd);
|
||||
poll.registry()
|
||||
.register(&mut evented_inotify, INOTIFY, mio::Interest::READABLE)?;
|
||||
|
||||
let event_loop = EventLoop {
|
||||
running: true,
|
||||
poll,
|
||||
event_loop_waker,
|
||||
event_loop_tx,
|
||||
event_loop_rx,
|
||||
inotify: Some(inotify),
|
||||
event_handler,
|
||||
watches: HashMap::new(),
|
||||
paths: HashMap::new(),
|
||||
rename_event: None,
|
||||
};
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
// Run the event loop.
|
||||
pub fn run(self) {
|
||||
let _ = thread::Builder::new()
|
||||
.name("notify-rs inotify loop".to_string())
|
||||
.spawn(|| self.event_loop_thread());
|
||||
}
|
||||
|
||||
fn event_loop_thread(mut self) {
|
||||
let mut events = mio::Events::with_capacity(16);
|
||||
loop {
|
||||
// Wait for something to happen.
|
||||
match self.poll.poll(&mut events, None) {
|
||||
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
|
||||
// System call was interrupted, we will retry
|
||||
// TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
|
||||
}
|
||||
Err(e) => panic!("poll failed: {}", e),
|
||||
Ok(()) => {}
|
||||
}
|
||||
|
||||
// Process whatever happened.
|
||||
for event in &events {
|
||||
self.handle_event(event);
|
||||
}
|
||||
|
||||
// Stop, if we're done.
|
||||
if !self.running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a single event.
|
||||
fn handle_event(&mut self, event: &mio::event::Event) {
|
||||
match event.token() {
|
||||
MESSAGE => {
|
||||
// The channel is readable - handle messages.
|
||||
self.handle_messages()
|
||||
}
|
||||
INOTIFY => {
|
||||
// inotify has something to tell us.
|
||||
self.handle_inotify()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_messages(&mut self) {
|
||||
while let Ok(msg) = self.event_loop_rx.try_recv() {
|
||||
match msg {
|
||||
EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
|
||||
let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive(), true));
|
||||
}
|
||||
EventLoopMsg::RemoveWatch(path, tx) => {
|
||||
let _ = tx.send(self.remove_watch(path, false));
|
||||
}
|
||||
EventLoopMsg::Shutdown => {
|
||||
let _ = self.remove_all_watches();
|
||||
if let Some(inotify) = self.inotify.take() {
|
||||
let _ = inotify.close();
|
||||
}
|
||||
self.running = false;
|
||||
break;
|
||||
}
|
||||
EventLoopMsg::Configure(config, tx) => {
|
||||
self.configure_raw_mode(config, tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
|
||||
tx.send(Ok(false))
|
||||
.expect("configuration channel disconnected");
|
||||
}
|
||||
|
||||
fn handle_inotify(&mut self) {
|
||||
let mut add_watches = Vec::new();
|
||||
let mut remove_watches = Vec::new();
|
||||
|
||||
if let Some(ref mut inotify) = self.inotify {
|
||||
let mut buffer = [0; 1024];
|
||||
// Read all buffers available.
|
||||
loop {
|
||||
match inotify.read_events(&mut buffer) {
|
||||
Ok(events) => {
|
||||
let mut num_events = 0;
|
||||
for event in events {
|
||||
log::trace!("inotify event: {event:?}");
|
||||
|
||||
num_events += 1;
|
||||
if event.mask.contains(EventMask::Q_OVERFLOW) {
|
||||
let ev = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan));
|
||||
self.event_handler.handle_event(ev);
|
||||
}
|
||||
|
||||
let path = match event.name {
|
||||
Some(name) => {
|
||||
self.paths.get(&event.wd).map(|root| root.join(&name))
|
||||
}
|
||||
None => self.paths.get(&event.wd).cloned(),
|
||||
};
|
||||
|
||||
let mut evs = Vec::new();
|
||||
|
||||
if event.mask.contains(EventMask::MOVED_FROM) {
|
||||
remove_watch_by_event(&path, &self.watches, &mut remove_watches);
|
||||
|
||||
let event = Event::new(EventKind::Modify(ModifyKind::Name(
|
||||
RenameMode::From,
|
||||
)))
|
||||
.add_some_path(path.clone())
|
||||
.set_tracker(event.cookie as usize);
|
||||
|
||||
self.rename_event = Some(event.clone());
|
||||
|
||||
evs.push(event);
|
||||
} else if event.mask.contains(EventMask::MOVED_TO) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::To)))
|
||||
.set_tracker(event.cookie as usize)
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
|
||||
let trackers_match = self
|
||||
.rename_event
|
||||
.as_ref()
|
||||
.and_then(|e| e.tracker())
|
||||
.map_or(false, |from_tracker| {
|
||||
from_tracker == event.cookie as usize
|
||||
});
|
||||
|
||||
if trackers_match {
|
||||
let rename_event = self.rename_event.take().unwrap(); // unwrap is safe because `rename_event` must be set at this point
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Name(
|
||||
RenameMode::Both,
|
||||
)))
|
||||
.set_tracker(event.cookie as usize)
|
||||
.add_some_path(rename_event.paths.first().cloned())
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
|
||||
}
|
||||
if event.mask.contains(EventMask::MOVE_SELF) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Name(
|
||||
RenameMode::From,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
// TODO stat the path and get to new path
|
||||
// - emit To and Both events
|
||||
// - change prefix for further events
|
||||
}
|
||||
if event.mask.contains(EventMask::CREATE) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Create(
|
||||
if event.mask.contains(EventMask::ISDIR) {
|
||||
CreateKind::Folder
|
||||
} else {
|
||||
CreateKind::File
|
||||
},
|
||||
))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
add_watch_by_event(&path, &event, &self.watches, &mut add_watches);
|
||||
}
|
||||
if event.mask.contains(EventMask::DELETE_SELF)
|
||||
|| event.mask.contains(EventMask::DELETE)
|
||||
{
|
||||
evs.push(
|
||||
Event::new(EventKind::Remove(
|
||||
if event.mask.contains(EventMask::ISDIR) {
|
||||
RemoveKind::Folder
|
||||
} else {
|
||||
RemoveKind::File
|
||||
},
|
||||
))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
remove_watch_by_event(&path, &self.watches, &mut remove_watches);
|
||||
}
|
||||
if event.mask.contains(EventMask::MODIFY) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Data(
|
||||
DataChange::Any,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
if event.mask.contains(EventMask::CLOSE_WRITE) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Access(AccessKind::Close(
|
||||
AccessMode::Write,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
if event.mask.contains(EventMask::CLOSE_NOWRITE) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Access(AccessKind::Close(
|
||||
AccessMode::Read,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
if event.mask.contains(EventMask::ATTRIB) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Modify(ModifyKind::Metadata(
|
||||
MetadataKind::Any,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
if event.mask.contains(EventMask::OPEN) {
|
||||
evs.push(
|
||||
Event::new(EventKind::Access(AccessKind::Open(
|
||||
AccessMode::Any,
|
||||
)))
|
||||
.add_some_path(path.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
for ev in evs {
|
||||
self.event_handler.handle_event(Ok(ev));
|
||||
}
|
||||
}
|
||||
|
||||
// All events read. Break out.
|
||||
if num_events == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
self.event_handler.handle_event(Err(Error::io(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for path in remove_watches {
|
||||
self.remove_watch(path, true).ok();
|
||||
}
|
||||
|
||||
for path in add_watches {
|
||||
self.add_watch(path, true, false).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_watch(&mut self, path: PathBuf, is_recursive: bool, mut watch_self: bool) -> Result<()> {
|
||||
// If the watch is not recursive, or if we determine (by stat'ing the path to get its
|
||||
// metadata) that the watched path is not a directory, add a single path watch.
|
||||
if !is_recursive || !metadata(&path).map_err(Error::io)?.is_dir() {
|
||||
return self.add_single_watch(path, false, true);
|
||||
}
|
||||
|
||||
for entry in WalkDir::new(path)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(filter_dir)
|
||||
{
|
||||
self.add_single_watch(entry.path().to_path_buf(), is_recursive, watch_self)?;
|
||||
watch_self = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_single_watch(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
is_recursive: bool,
|
||||
watch_self: bool,
|
||||
) -> Result<()> {
|
||||
let mut watchmask = WatchMask::ATTRIB
|
||||
| WatchMask::CREATE
|
||||
| WatchMask::DELETE
|
||||
| WatchMask::CLOSE_WRITE
|
||||
| WatchMask::MODIFY
|
||||
| WatchMask::MOVED_FROM
|
||||
| WatchMask::MOVED_TO;
|
||||
|
||||
if watch_self {
|
||||
watchmask.insert(WatchMask::DELETE_SELF);
|
||||
watchmask.insert(WatchMask::MOVE_SELF);
|
||||
}
|
||||
|
||||
if let Some(&(_, old_watchmask, _)) = self.watches.get(&path) {
|
||||
watchmask.insert(old_watchmask);
|
||||
watchmask.insert(WatchMask::MASK_ADD);
|
||||
}
|
||||
|
||||
if let Some(ref mut inotify) = self.inotify {
|
||||
log::trace!("adding inotify watch: {}", path.display());
|
||||
|
||||
match inotify.add_watch(&path, watchmask) {
|
||||
Err(e) => {
|
||||
Err(if e.raw_os_error() == Some(libc::ENOSPC) {
|
||||
// do not report inotify limits as "no more space" on linux #266
|
||||
Error::new(ErrorKind::MaxFilesWatch)
|
||||
} else {
|
||||
Error::io(e)
|
||||
}
|
||||
.add_path(path))
|
||||
}
|
||||
Ok(w) => {
|
||||
watchmask.remove(WatchMask::MASK_ADD);
|
||||
self.watches
|
||||
.insert(path.clone(), (w.clone(), watchmask, is_recursive));
|
||||
self.paths.insert(w, path);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
|
||||
match self.watches.remove(&path) {
|
||||
None => return Err(Error::watch_not_found().add_path(path)),
|
||||
Some((w, _, is_recursive)) => {
|
||||
if let Some(ref mut inotify) = self.inotify {
|
||||
log::trace!("removing inotify watch: {}", path.display());
|
||||
|
||||
inotify
|
||||
.rm_watch(w.clone())
|
||||
.map_err(|e| Error::io(e).add_path(path.clone()))?;
|
||||
self.paths.remove(&w);
|
||||
|
||||
if is_recursive || remove_recursive {
|
||||
let mut remove_list = Vec::new();
|
||||
for (w, p) in &self.paths {
|
||||
if p.starts_with(&path) {
|
||||
inotify
|
||||
.rm_watch(w.clone())
|
||||
.map_err(|e| Error::io(e).add_path(p.into()))?;
|
||||
self.watches.remove(p);
|
||||
remove_list.push(w.clone());
|
||||
}
|
||||
}
|
||||
for w in remove_list {
|
||||
self.paths.remove(&w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_all_watches(&mut self) -> Result<()> {
|
||||
if let Some(ref mut inotify) = self.inotify {
|
||||
for (w, p) in &self.paths {
|
||||
inotify
|
||||
.rm_watch(w.clone())
|
||||
.map_err(|e| Error::io(e).add_path(p.into()))?;
|
||||
}
|
||||
self.watches.clear();
|
||||
self.paths.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// return `DirEntry` when it is a directory
|
||||
fn filter_dir(e: walkdir::Result<walkdir::DirEntry>) -> Option<walkdir::DirEntry> {
|
||||
if let Ok(e) = e {
|
||||
if let Ok(metadata) = e.metadata() {
|
||||
if metadata.is_dir() {
|
||||
return Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl INotifyWatcher {
|
||||
fn from_event_handler(event_handler: Box<dyn EventHandler>) -> Result<Self> {
|
||||
let inotify = Inotify::init()?;
|
||||
let event_loop = EventLoop::new(inotify, event_handler)?;
|
||||
let channel = event_loop.event_loop_tx.clone();
|
||||
let waker = event_loop.event_loop_waker.clone();
|
||||
event_loop.run();
|
||||
Ok(INotifyWatcher { channel, waker })
|
||||
}
|
||||
|
||||
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
let (tx, rx) = unbounded();
|
||||
let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
|
||||
|
||||
// we expect the event loop to live and reply => unwraps must not panic
|
||||
self.channel.send(msg).unwrap();
|
||||
self.waker.wake().unwrap();
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
|
||||
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
let (tx, rx) = unbounded();
|
||||
let msg = EventLoopMsg::RemoveWatch(pb, tx);
|
||||
|
||||
// we expect the event loop to live and reply => unwraps must not panic
|
||||
self.channel.send(msg).unwrap();
|
||||
self.waker.wake().unwrap();
|
||||
rx.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for INotifyWatcher {
|
||||
/// Create a new watcher.
|
||||
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
|
||||
Self::from_event_handler(Box::new(event_handler))
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
self.watch_inner(path, recursive_mode)
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()> {
|
||||
self.unwatch_inner(path)
|
||||
}
|
||||
|
||||
fn configure(&mut self, config: Config) -> Result<bool> {
|
||||
let (tx, rx) = bounded(1);
|
||||
self.channel.send(EventLoopMsg::Configure(config, tx))?;
|
||||
self.waker.wake()?;
|
||||
rx.recv()?
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
crate::WatcherKind::Inotify
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for INotifyWatcher {
|
||||
fn drop(&mut self) {
|
||||
// we expect the event loop to live => unwrap must not panic
|
||||
self.channel.send(EventLoopMsg::Shutdown).unwrap();
|
||||
self.waker.wake().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inotify_watcher_is_send_and_sync() {
|
||||
fn check<T: Send + Sync>() {}
|
||||
check::<INotifyWatcher>();
|
||||
}
|
||||
442
third-party/vendor/notify/src/kqueue.rs
vendored
Normal file
442
third-party/vendor/notify/src/kqueue.rs
vendored
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
//! Watcher implementation for the kqueue API
|
||||
//!
|
||||
//! The kqueue() system call provides a generic method of notifying the user
|
||||
//! when an event happens or a condition holds, based on the results of small
|
||||
//! pieces of kernel code termed filters.
|
||||
|
||||
use super::event::*;
|
||||
use super::{Config, Error, EventHandler, RecursiveMode, Result, Watcher};
|
||||
use crate::{unbounded, Receiver, Sender};
|
||||
use kqueue::{EventData, EventFilter, FilterFlag, Ident};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::metadata;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const KQUEUE: mio::Token = mio::Token(0);
|
||||
const MESSAGE: mio::Token = mio::Token(1);
|
||||
|
||||
// The EventLoop will set up a mio::Poll and use it to wait for the following:
|
||||
//
|
||||
// - messages telling it what to do
|
||||
//
|
||||
// - events telling it that something has happened on one of the watched files.
|
||||
struct EventLoop {
|
||||
running: bool,
|
||||
poll: mio::Poll,
|
||||
event_loop_waker: Arc<mio::Waker>,
|
||||
event_loop_tx: Sender<EventLoopMsg>,
|
||||
event_loop_rx: Receiver<EventLoopMsg>,
|
||||
kqueue: kqueue::Watcher,
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
watches: HashMap<PathBuf, bool>,
|
||||
}
|
||||
|
||||
/// Watcher implementation based on inotify
|
||||
#[derive(Debug)]
|
||||
pub struct KqueueWatcher {
|
||||
channel: Sender<EventLoopMsg>,
|
||||
waker: Arc<mio::Waker>,
|
||||
}
|
||||
|
||||
enum EventLoopMsg {
|
||||
AddWatch(PathBuf, RecursiveMode, Sender<Result<()>>),
|
||||
RemoveWatch(PathBuf, Sender<Result<()>>),
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(kqueue: kqueue::Watcher, event_handler: Box<dyn EventHandler>) -> Result<Self> {
|
||||
let (event_loop_tx, event_loop_rx) = unbounded::<EventLoopMsg>();
|
||||
let poll = mio::Poll::new()?;
|
||||
|
||||
let event_loop_waker = Arc::new(mio::Waker::new(poll.registry(), MESSAGE)?);
|
||||
|
||||
let kqueue_fd = kqueue.as_raw_fd();
|
||||
let mut evented_kqueue = mio::unix::SourceFd(&kqueue_fd);
|
||||
poll.registry()
|
||||
.register(&mut evented_kqueue, KQUEUE, mio::Interest::READABLE)?;
|
||||
|
||||
let event_loop = EventLoop {
|
||||
running: true,
|
||||
poll,
|
||||
event_loop_waker,
|
||||
event_loop_tx,
|
||||
event_loop_rx,
|
||||
kqueue,
|
||||
event_handler,
|
||||
watches: HashMap::new(),
|
||||
};
|
||||
Ok(event_loop)
|
||||
}
|
||||
|
||||
// Run the event loop.
|
||||
pub fn run(self) {
|
||||
let _ = thread::Builder::new()
|
||||
.name("notify-rs kqueue loop".to_string())
|
||||
.spawn(|| self.event_loop_thread());
|
||||
}
|
||||
|
||||
fn event_loop_thread(mut self) {
|
||||
let mut events = mio::Events::with_capacity(16);
|
||||
loop {
|
||||
// Wait for something to happen.
|
||||
match self.poll.poll(&mut events, None) {
|
||||
Err(ref e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => {
|
||||
// System call was interrupted, we will retry
|
||||
// TODO: Not covered by tests (to reproduce likely need to setup signal handlers)
|
||||
}
|
||||
Err(e) => panic!("poll failed: {}", e),
|
||||
Ok(()) => {}
|
||||
}
|
||||
|
||||
// Process whatever happened.
|
||||
for event in &events {
|
||||
self.handle_event(event);
|
||||
}
|
||||
|
||||
// Stop, if we're done.
|
||||
if !self.running {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a single event.
|
||||
fn handle_event(&mut self, event: &mio::event::Event) {
|
||||
match event.token() {
|
||||
MESSAGE => {
|
||||
// The channel is readable - handle messages.
|
||||
self.handle_messages()
|
||||
}
|
||||
KQUEUE => {
|
||||
// inotify has something to tell us.
|
||||
self.handle_kqueue()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_messages(&mut self) {
|
||||
while let Ok(msg) = self.event_loop_rx.try_recv() {
|
||||
match msg {
|
||||
EventLoopMsg::AddWatch(path, recursive_mode, tx) => {
|
||||
let _ = tx.send(self.add_watch(path, recursive_mode.is_recursive()));
|
||||
}
|
||||
EventLoopMsg::RemoveWatch(path, tx) => {
|
||||
let _ = tx.send(self.remove_watch(path, false));
|
||||
}
|
||||
EventLoopMsg::Shutdown => {
|
||||
self.running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_kqueue(&mut self) {
|
||||
let mut add_watches = Vec::new();
|
||||
let mut remove_watches = Vec::new();
|
||||
|
||||
while let Some(event) = self.kqueue.poll(None) {
|
||||
log::trace!("kqueue event: {event:?}");
|
||||
|
||||
match event {
|
||||
kqueue::Event {
|
||||
data: EventData::Vnode(data),
|
||||
ident: Ident::Filename(_, path),
|
||||
} => {
|
||||
let path = PathBuf::from(path);
|
||||
let event = match data {
|
||||
/*
|
||||
TODO: Differenciate folders and files
|
||||
kqueue dosen't tell us if this was a file or a dir, so we
|
||||
could only emulate this inotify behavior if we keep track of
|
||||
all files and directories internally and then perform a
|
||||
lookup.
|
||||
*/
|
||||
kqueue::Vnode::Delete => {
|
||||
remove_watches.push(path.clone());
|
||||
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path))
|
||||
}
|
||||
|
||||
// a write to a directory means that a new file was created in it, let's
|
||||
// figure out which file this was
|
||||
kqueue::Vnode::Write if path.is_dir() => {
|
||||
// find which file is new in the directory by comparing it with our
|
||||
// list of known watches
|
||||
std::fs::read_dir(&path)
|
||||
.map(|dir| {
|
||||
dir.filter_map(std::result::Result::ok)
|
||||
.map(|f| f.path())
|
||||
.find(|f| !self.watches.contains_key(f))
|
||||
})
|
||||
.map(|file| {
|
||||
if let Some(file) = file {
|
||||
// watch this new file
|
||||
add_watches.push(file.clone());
|
||||
|
||||
Event::new(EventKind::Create(if file.is_dir() {
|
||||
CreateKind::Folder
|
||||
} else if file.is_file() {
|
||||
CreateKind::File
|
||||
} else {
|
||||
CreateKind::Other
|
||||
}))
|
||||
.add_path(file)
|
||||
} else {
|
||||
Event::new(EventKind::Modify(ModifyKind::Data(
|
||||
DataChange::Any,
|
||||
)))
|
||||
.add_path(path)
|
||||
}
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// data was written to this file
|
||||
kqueue::Vnode::Write => Ok(Event::new(EventKind::Modify(
|
||||
ModifyKind::Data(DataChange::Any),
|
||||
))
|
||||
.add_path(path)),
|
||||
|
||||
/*
|
||||
Extend and Truncate are just different names for the same
|
||||
operation, extend is only used on FreeBSD, truncate everwhere
|
||||
else
|
||||
*/
|
||||
kqueue::Vnode::Extend | kqueue::Vnode::Truncate => Ok(Event::new(
|
||||
EventKind::Modify(ModifyKind::Data(DataChange::Size)),
|
||||
)
|
||||
.add_path(path)),
|
||||
|
||||
/*
|
||||
this kevent has the same problem as the delete kevent. The
|
||||
only way i can think of providing "better" event with more
|
||||
information is to do the diff our self, while this maybe do
|
||||
able of delete. In this case it would somewhat expensive to
|
||||
keep track and compare ever peace of metadata for every file
|
||||
*/
|
||||
kqueue::Vnode::Attrib => Ok(Event::new(EventKind::Modify(
|
||||
ModifyKind::Metadata(MetadataKind::Any),
|
||||
))
|
||||
.add_path(path)),
|
||||
|
||||
/*
|
||||
The link count on a file changed => subdirectory created or
|
||||
delete.
|
||||
*/
|
||||
kqueue::Vnode::Link => {
|
||||
// As we currently don't have a solution that whould allow us
|
||||
// to only add/remove the new/delete directory and that dosn't include a
|
||||
// possible race condition. On possible solution would be to
|
||||
// create a `HashMap<PathBuf, Vec<PathBuf>>` which would
|
||||
// include every directory and this content add the time of
|
||||
// adding it to kqueue. While this sould allow us to do the
|
||||
// diff and only add/remove the files nessesary. This whould
|
||||
// also introduce a race condition, where multiple files could
|
||||
// all ready be remove from the directory, and we could get out
|
||||
// of sync.
|
||||
// So for now, until we find a better solution, let remove and
|
||||
// readd the whole directory.
|
||||
// This is a expensive operation, as we recursive through all
|
||||
// subdirectories.
|
||||
remove_watches.push(path.clone());
|
||||
add_watches.push(path.clone());
|
||||
Ok(Event::new(EventKind::Modify(ModifyKind::Any)).add_path(path))
|
||||
}
|
||||
|
||||
// Kqueue not provide us with the infomation nessesary to provide
|
||||
// the new file name to the event.
|
||||
kqueue::Vnode::Rename => {
|
||||
remove_watches.push(path.clone());
|
||||
Ok(
|
||||
Event::new(EventKind::Modify(ModifyKind::Name(RenameMode::Any)))
|
||||
.add_path(path),
|
||||
)
|
||||
}
|
||||
|
||||
// Access to the file was revoked via revoke(2) or the underlying file system was unmounted.
|
||||
kqueue::Vnode::Revoke => {
|
||||
remove_watches.push(path.clone());
|
||||
Ok(Event::new(EventKind::Remove(RemoveKind::Any)).add_path(path))
|
||||
}
|
||||
|
||||
// On different BSD variants, different extra events may be present
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => Ok(Event::new(EventKind::Other)),
|
||||
};
|
||||
self.event_handler.handle_event(event);
|
||||
}
|
||||
// as we don't add any other EVFILTER to kqueue we should never get here
|
||||
kqueue::Event { ident: _, data: _ } => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
for path in remove_watches {
|
||||
self.remove_watch(path, true).ok();
|
||||
}
|
||||
|
||||
for path in add_watches {
|
||||
self.add_watch(path, true).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> {
|
||||
// If the watch is not recursive, or if we determine (by stat'ing the path to get its
|
||||
// metadata) that the watched path is not a directory, add a single path watch.
|
||||
if !is_recursive || !metadata(&path).map_err(Error::io)?.is_dir() {
|
||||
self.add_single_watch(path, false)?;
|
||||
} else {
|
||||
for entry in WalkDir::new(path).follow_links(true).into_iter() {
|
||||
let entry = entry.map_err(map_walkdir_error)?;
|
||||
self.add_single_watch(entry.path().to_path_buf(), is_recursive)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Only make a single `kevent` syscall to add all the watches.
|
||||
self.kqueue.watch()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a single watch to the kqueue.
|
||||
///
|
||||
/// The caller of this function must call `self.kqueue.watch()` afterwards to register the new watch.
|
||||
fn add_single_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<()> {
|
||||
let event_filter = EventFilter::EVFILT_VNODE;
|
||||
let filter_flags = FilterFlag::NOTE_DELETE
|
||||
| FilterFlag::NOTE_WRITE
|
||||
| FilterFlag::NOTE_EXTEND
|
||||
| FilterFlag::NOTE_ATTRIB
|
||||
| FilterFlag::NOTE_LINK
|
||||
| FilterFlag::NOTE_RENAME
|
||||
| FilterFlag::NOTE_REVOKE;
|
||||
|
||||
log::trace!("adding kqueue watch: {}", path.display());
|
||||
|
||||
self.kqueue
|
||||
.add_filename(&path, event_filter, filter_flags)
|
||||
.map_err(|e| Error::io(e).add_path(path.clone()))?;
|
||||
self.watches.insert(path, is_recursive);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_watch(&mut self, path: PathBuf, remove_recursive: bool) -> Result<()> {
|
||||
log::trace!("removing kqueue watch: {}", path.display());
|
||||
|
||||
match self.watches.remove(&path) {
|
||||
None => return Err(Error::watch_not_found()),
|
||||
Some(is_recursive) => {
|
||||
self.kqueue
|
||||
.remove_filename(&path, EventFilter::EVFILT_VNODE)
|
||||
.map_err(|e| Error::io(e).add_path(path.clone()))?;
|
||||
|
||||
if is_recursive || remove_recursive {
|
||||
for entry in WalkDir::new(path).follow_links(true).into_iter() {
|
||||
let p = entry.map_err(map_walkdir_error)?.path().to_path_buf();
|
||||
self.kqueue
|
||||
.remove_filename(&p, EventFilter::EVFILT_VNODE)
|
||||
.map_err(|e| Error::io(e).add_path(p))?;
|
||||
}
|
||||
}
|
||||
self.kqueue.watch()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_walkdir_error(e: walkdir::Error) -> Error {
|
||||
if e.io_error().is_some() {
|
||||
// save to unwrap otherwise we whouldn't be in this branch
|
||||
Error::io(e.into_io_error().unwrap())
|
||||
} else {
|
||||
Error::generic(&e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl KqueueWatcher {
|
||||
fn from_event_handler(event_handler: Box<dyn EventHandler>) -> Result<Self> {
|
||||
let kqueue = kqueue::Watcher::new()?;
|
||||
let event_loop = EventLoop::new(kqueue, event_handler)?;
|
||||
let channel = event_loop.event_loop_tx.clone();
|
||||
let waker = event_loop.event_loop_waker.clone();
|
||||
event_loop.run();
|
||||
Ok(KqueueWatcher { channel, waker })
|
||||
}
|
||||
|
||||
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
let (tx, rx) = unbounded();
|
||||
let msg = EventLoopMsg::AddWatch(pb, recursive_mode, tx);
|
||||
|
||||
self.channel
|
||||
.send(msg)
|
||||
.map_err(|e| Error::generic(&e.to_string()))?;
|
||||
self.waker
|
||||
.wake()
|
||||
.map_err(|e| Error::generic(&e.to_string()))?;
|
||||
rx.recv()
|
||||
.unwrap()
|
||||
.map_err(|e| Error::generic(&e.to_string()))
|
||||
}
|
||||
|
||||
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
let (tx, rx) = unbounded();
|
||||
let msg = EventLoopMsg::RemoveWatch(pb, tx);
|
||||
|
||||
self.channel
|
||||
.send(msg)
|
||||
.map_err(|e| Error::generic(&e.to_string()))?;
|
||||
self.waker
|
||||
.wake()
|
||||
.map_err(|e| Error::generic(&e.to_string()))?;
|
||||
rx.recv()
|
||||
.unwrap()
|
||||
.map_err(|e| Error::generic(&e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for KqueueWatcher {
|
||||
/// Create a new watcher.
|
||||
fn new<F: EventHandler>(event_handler: F, _config: Config) -> Result<Self> {
|
||||
Self::from_event_handler(Box::new(event_handler))
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
self.watch_inner(path, recursive_mode)
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()> {
|
||||
self.unwatch_inner(path)
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
crate::WatcherKind::Kqueue
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KqueueWatcher {
|
||||
fn drop(&mut self) {
|
||||
// we expect the event loop to live => unwrap must not panic
|
||||
self.channel.send(EventLoopMsg::Shutdown).unwrap();
|
||||
self.waker.wake().unwrap();
|
||||
}
|
||||
}
|
||||
451
third-party/vendor/notify/src/lib.rs
vendored
Normal file
451
third-party/vendor/notify/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
//! Cross-platform file system notification library
|
||||
//!
|
||||
//! # Installation
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! notify = "6.1.1"
|
||||
//! ```
|
||||
//!
|
||||
//! If you want debounced events (or don't need them in-order), see [notify-debouncer-mini](https://docs.rs/notify-debouncer-mini/latest/notify_debouncer_mini/)
|
||||
//! or [notify-debouncer-full](https://docs.rs/notify-debouncer-full/latest/notify_debouncer_full/).
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! List of compilation features, see below for details
|
||||
//!
|
||||
//! - `serde` for serialization of events
|
||||
//! - `macos_fsevent` enabled by default, for fsevent backend on macos
|
||||
//! - `macos_kqueue` for kqueue backend on macos
|
||||
//! - `crossbeam-channel` enabled by default, see below
|
||||
//!
|
||||
//! ### Serde
|
||||
//!
|
||||
//! Events are serializable via [serde](https://serde.rs) if the `serde` feature is enabled:
|
||||
//!
|
||||
//! ```toml
|
||||
//! notify = { version = "6.1.1", features = ["serde"] }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Crossbeam-Channel & Tokio
|
||||
//!
|
||||
//! By default crossbeam-channel is used internally by notify. Which also allows the [Watcher] to be sync.
|
||||
//! This can [cause issues](https://github.com/notify-rs/notify/issues/380) when used inside tokio.
|
||||
//!
|
||||
//! You can disable crossbeam-channel, letting notify fallback to std channels via
|
||||
//!
|
||||
//! ```toml
|
||||
//! notify = { version = "6.1.1", default-features = false, features = ["macos_kqueue"] }
|
||||
//! // Alternatively macos_fsevent instead of macos_kqueue
|
||||
//! ```
|
||||
//! Note the `macos_kqueue` requirement here, otherwise no native backend is available on macos.
|
||||
//!
|
||||
//! # Known Problems
|
||||
//!
|
||||
//! ### Network filesystems
|
||||
//!
|
||||
//! Network mounted filesystems like NFS may not emit any events for notify to listen to.
|
||||
//! This applies especially to WSL programs watching windows paths ([issue #254](https://github.com/notify-rs/notify/issues/254)).
|
||||
//!
|
||||
//! A workaround is the [PollWatcher] backend.
|
||||
//!
|
||||
//! ### Docker with Linux on MacOS M1
|
||||
//!
|
||||
//! Docker on macos M1 [throws](https://github.com/notify-rs/notify/issues/423) `Function not implemented (os error 38)`.
|
||||
//! You have to manually use the [PollWatcher], as the native backend isn't available inside the emulation.
|
||||
//!
|
||||
//! ### MacOS, FSEvents and unowned files
|
||||
//!
|
||||
//! Due to the inner security model of FSEvents (see [FileSystemEventSecurity](https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/FSEvents_ProgGuide/FileSystemEventSecurity/FileSystemEventSecurity.html)),
|
||||
//! some events cannot be observed easily when trying to follow files that do not
|
||||
//! belong to you. In this case, reverting to the pollwatcher can fix the issue,
|
||||
//! with a slight performance cost.
|
||||
//!
|
||||
//! ### Editor Behaviour
|
||||
//!
|
||||
//! If you rely on precise events (Write/Delete/Create..), you will notice that the actual events
|
||||
//! can differ a lot between file editors. Some truncate the file on save, some create a new one and replace the old one.
|
||||
//! See also [this](https://github.com/notify-rs/notify/issues/247) and [this](https://github.com/notify-rs/notify/issues/113#issuecomment-281836995) issues for example.
|
||||
//!
|
||||
//! ### Parent folder deletion
|
||||
//!
|
||||
//! If you want to receive an event for a deletion of folder `b` for the path `/a/b/..`, you will have to watch its parent `/a`.
|
||||
//! See [here](https://github.com/notify-rs/notify/issues/403) for more details.
|
||||
//!
|
||||
//! ### Pseudo Filesystems like /proc, /sys
|
||||
//!
|
||||
//! Some filesystems like `/proc` and `/sys` on *nix do not emit change events or use correct file change dates.
|
||||
//! To circumvent that problem you can use the [PollWatcher] with the `compare_contents` option.
|
||||
//!
|
||||
//! ### Linux: Bad File Descriptor / No space left on device
|
||||
//!
|
||||
//! This may be the case of running into the max-files watched limits of your user or system.
|
||||
//! (Files also includes folders.) Note that for recursive watched folders each file and folder inside counts towards the limit.
|
||||
//!
|
||||
//! You may increase this limit in linux via
|
||||
//! ```sh
|
||||
//! sudo sysctl fs.inotify.max_user_instances=8192 # example number
|
||||
//! sudo sysctl fs.inotify.max_user_watches=524288 # example number
|
||||
//! sudo sysctl -p
|
||||
//! ```
|
||||
//!
|
||||
//! Note that the [PollWatcher] is not restricted by this limitation, so it may be an alternative if your users can't increase the limit.
|
||||
//!
|
||||
//! ### Watching large directories
|
||||
//!
|
||||
//! When watching a very large amount of files, notify may fail to receive all events.
|
||||
//! For example the linux backend is documented to not be a 100% reliable source. See also issue [#412](https://github.com/notify-rs/notify/issues/412).
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! For more examples visit the [examples folder](https://github.com/notify-rs/notify/tree/main/examples) in the repository.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use std::path::Path;
|
||||
//! use notify::{Watcher, RecommendedWatcher, RecursiveMode, Result};
|
||||
//!
|
||||
//! fn main() -> Result<()> {
|
||||
//! // Automatically select the best implementation for your platform.
|
||||
//! let mut watcher = notify::recommended_watcher(|res| {
|
||||
//! match res {
|
||||
//! Ok(event) => println!("event: {:?}", event),
|
||||
//! Err(e) => println!("watch error: {:?}", e),
|
||||
//! }
|
||||
//! })?;
|
||||
//!
|
||||
//! // Add a path to be watched. All files and directories at that path and
|
||||
//! // below will be monitored for changes.
|
||||
//! # #[cfg(not(any(
|
||||
//! # target_os = "freebsd",
|
||||
//! # target_os = "openbsd",
|
||||
//! # target_os = "dragonflybsd",
|
||||
//! # target_os = "netbsd")))]
|
||||
//! # { // "." doesn't exist on BSD for some reason in CI
|
||||
//! watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
|
||||
//! # }
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## With different configurations
|
||||
//!
|
||||
//! It is possible to create several watchers with different configurations or implementations that
|
||||
//! all call the same event function. This can accommodate advanced behaviour or work around limits.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use notify::{RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||
//! # use std::path::Path;
|
||||
//! #
|
||||
//! # fn main() -> Result<()> {
|
||||
//! fn event_fn(res: Result<notify::Event>) {
|
||||
//! match res {
|
||||
//! Ok(event) => println!("event: {:?}", event),
|
||||
//! Err(e) => println!("watch error: {:?}", e),
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let mut watcher1 = notify::recommended_watcher(event_fn)?;
|
||||
//! // we will just use the same watcher kind again here
|
||||
//! let mut watcher2 = notify::recommended_watcher(event_fn)?;
|
||||
//! # #[cfg(not(any(
|
||||
//! # target_os = "freebsd",
|
||||
//! # target_os = "openbsd",
|
||||
//! # target_os = "dragonflybsd",
|
||||
//! # target_os = "netbsd")))]
|
||||
//! # { // "." doesn't exist on BSD for some reason in CI
|
||||
//! # watcher1.watch(Path::new("."), RecursiveMode::Recursive)?;
|
||||
//! # watcher2.watch(Path::new("."), RecursiveMode::Recursive)?;
|
||||
//! # }
|
||||
//! // dropping the watcher1/2 here (no loop etc) will end the program
|
||||
//! #
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub use config::{Config, RecursiveMode};
|
||||
pub use error::{Error, ErrorKind, Result};
|
||||
pub use event::{Event, EventKind};
|
||||
use std::path::Path;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
pub(crate) type Receiver<T> = crossbeam_channel::Receiver<T>;
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
pub(crate) type Receiver<T> = std::sync::mpsc::Receiver<T>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
pub(crate) type Sender<T> = crossbeam_channel::Sender<T>;
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
pub(crate) type Sender<T> = std::sync::mpsc::Sender<T>;
|
||||
|
||||
// std limitation
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
pub(crate) type BoundSender<T> = crossbeam_channel::Sender<T>;
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
pub(crate) type BoundSender<T> = std::sync::mpsc::SyncSender<T>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn unbounded<T>() -> (Sender<T>, Receiver<T>) {
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
return crossbeam_channel::unbounded();
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
return std::sync::mpsc::channel();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub(crate) fn bounded<T>(cap: usize) -> (BoundSender<T>, Receiver<T>) {
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
return crossbeam_channel::bounded(cap);
|
||||
#[cfg(not(feature = "crossbeam-channel"))]
|
||||
return std::sync::mpsc::sync_channel(cap);
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
|
||||
pub use crate::fsevent::FsEventWatcher;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub use crate::inotify::INotifyWatcher;
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonflybsd",
|
||||
all(target_os = "macos", feature = "macos_kqueue")
|
||||
))]
|
||||
pub use crate::kqueue::KqueueWatcher;
|
||||
pub use null::NullWatcher;
|
||||
pub use poll::PollWatcher;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::ReadDirectoryChangesWatcher;
|
||||
|
||||
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
|
||||
pub mod fsevent;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub mod inotify;
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "dragonflybsd",
|
||||
target_os = "netbsd",
|
||||
all(target_os = "macos", feature = "macos_kqueue")
|
||||
))]
|
||||
pub mod kqueue;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
pub mod event;
|
||||
pub mod null;
|
||||
pub mod poll;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
/// The set of requirements for watcher event handling functions.
|
||||
///
|
||||
/// # Example implementation
|
||||
///
|
||||
/// ```no_run
|
||||
/// use notify::{Event, Result, EventHandler};
|
||||
///
|
||||
/// /// Prints received events
|
||||
/// struct EventPrinter;
|
||||
///
|
||||
/// impl EventHandler for EventPrinter {
|
||||
/// fn handle_event(&mut self, event: Result<Event>) {
|
||||
/// if let Ok(event) = event {
|
||||
/// println!("Event: {:?}", event);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait EventHandler: Send + 'static {
|
||||
/// Handles an event.
|
||||
fn handle_event(&mut self, event: Result<Event>);
|
||||
}
|
||||
|
||||
impl<F> EventHandler for F
|
||||
where
|
||||
F: FnMut(Result<Event>) + Send + 'static,
|
||||
{
|
||||
fn handle_event(&mut self, event: Result<Event>) {
|
||||
(self)(event);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
impl EventHandler for crossbeam_channel::Sender<Result<Event>> {
|
||||
fn handle_event(&mut self, event: Result<Event>) {
|
||||
let _ = self.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventHandler for std::sync::mpsc::Sender<Result<Event>> {
|
||||
fn handle_event(&mut self, event: Result<Event>) {
|
||||
let _ = self.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Watcher kind enumeration
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum WatcherKind {
|
||||
/// inotify backend (linux)
|
||||
Inotify,
|
||||
/// FS-Event backend (mac)
|
||||
Fsevent,
|
||||
/// KQueue backend (bsd,optionally mac)
|
||||
Kqueue,
|
||||
/// Polling based backend (fallback)
|
||||
PollWatcher,
|
||||
/// Windows backend
|
||||
ReadDirectoryChangesWatcher,
|
||||
/// Fake watcher for testing
|
||||
NullWatcher,
|
||||
}
|
||||
|
||||
/// Type that can deliver file activity notifications
|
||||
///
|
||||
/// Watcher is implemented per platform using the best implementation available on that platform.
|
||||
/// In addition to such event driven implementations, a polling implementation is also provided
|
||||
/// that should work on any platform.
|
||||
pub trait Watcher {
|
||||
/// Create a new watcher with an initial Config.
|
||||
fn new<F: EventHandler>(event_handler: F, config: config::Config) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Begin watching a new path.
|
||||
///
|
||||
/// If the `path` is a directory, `recursive_mode` will be evaluated. If `recursive_mode` is
|
||||
/// `RecursiveMode::Recursive` events will be delivered for all files in that tree. Otherwise
|
||||
/// only the directory and its immediate children will be watched.
|
||||
///
|
||||
/// If the `path` is a file, `recursive_mode` will be ignored and events will be delivered only
|
||||
/// for the file.
|
||||
///
|
||||
/// On some platforms, if the `path` is renamed or removed while being watched, behaviour may
|
||||
/// be unexpected. See discussions in [#165] and [#166]. If less surprising behaviour is wanted
|
||||
/// one may non-recursively watch the _parent_ directory as well and manage related events.
|
||||
///
|
||||
/// [#165]: https://github.com/notify-rs/notify/issues/165
|
||||
/// [#166]: https://github.com/notify-rs/notify/issues/166
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()>;
|
||||
|
||||
/// Stop watching a path.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error in the case that `path` has not been watched or if removing the watch
|
||||
/// fails.
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()>;
|
||||
|
||||
/// Configure the watcher at runtime.
|
||||
///
|
||||
/// See the [`Config`](config/enum.Config.html) enum for all configuration options.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Ok(true)` on success.
|
||||
/// - `Ok(false)` if the watcher does not support or implement the option.
|
||||
/// - `Err(notify::Error)` on failure.
|
||||
fn configure(&mut self, _option: Config) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Returns the watcher kind, allowing to perform backend-specific tasks
|
||||
fn kind() -> WatcherKind
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// The recommended `Watcher` implementation for the current platform
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
pub type RecommendedWatcher = INotifyWatcher;
|
||||
/// The recommended `Watcher` implementation for the current platform
|
||||
#[cfg(all(target_os = "macos", not(feature = "macos_kqueue")))]
|
||||
pub type RecommendedWatcher = FsEventWatcher;
|
||||
/// The recommended `Watcher` implementation for the current platform
|
||||
#[cfg(target_os = "windows")]
|
||||
pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
|
||||
/// The recommended `Watcher` implementation for the current platform
|
||||
#[cfg(any(
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonflybsd",
|
||||
all(target_os = "macos", feature = "macos_kqueue")
|
||||
))]
|
||||
pub type RecommendedWatcher = KqueueWatcher;
|
||||
/// The recommended `Watcher` implementation for the current platform
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "android",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "freebsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "dragonflybsd"
|
||||
)))]
|
||||
pub type RecommendedWatcher = PollWatcher;
|
||||
|
||||
/// Convenience method for creating the `RecommendedWatcher` for the current platform in
|
||||
/// _immediate_ mode.
|
||||
///
|
||||
/// See [`Watcher::new_immediate`](trait.Watcher.html#tymethod.new_immediate).
|
||||
pub fn recommended_watcher<F>(event_handler: F) -> Result<RecommendedWatcher>
|
||||
where
|
||||
F: EventHandler,
|
||||
{
|
||||
// All recommended watchers currently implement `new`, so just call that.
|
||||
RecommendedWatcher::new(event_handler, Config::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_object_safe() {
|
||||
let _watcher: &dyn Watcher = &NullWatcher;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_impl() {
|
||||
macro_rules! assert_debug_impl {
|
||||
($t:ty) => {{
|
||||
trait NeedsDebug: std::fmt::Debug {}
|
||||
impl NeedsDebug for $t {}
|
||||
}};
|
||||
}
|
||||
|
||||
assert_debug_impl!(Config);
|
||||
assert_debug_impl!(Error);
|
||||
assert_debug_impl!(ErrorKind);
|
||||
assert_debug_impl!(event::AccessKind);
|
||||
assert_debug_impl!(event::AccessMode);
|
||||
assert_debug_impl!(event::CreateKind);
|
||||
assert_debug_impl!(event::DataChange);
|
||||
assert_debug_impl!(event::EventAttributes);
|
||||
assert_debug_impl!(event::Flag);
|
||||
assert_debug_impl!(event::MetadataKind);
|
||||
assert_debug_impl!(event::ModifyKind);
|
||||
assert_debug_impl!(event::RemoveKind);
|
||||
assert_debug_impl!(event::RenameMode);
|
||||
assert_debug_impl!(Event);
|
||||
assert_debug_impl!(EventKind);
|
||||
assert_debug_impl!(NullWatcher);
|
||||
assert_debug_impl!(PollWatcher);
|
||||
assert_debug_impl!(RecommendedWatcher);
|
||||
assert_debug_impl!(RecursiveMode);
|
||||
assert_debug_impl!(WatcherKind);
|
||||
}
|
||||
}
|
||||
39
third-party/vendor/notify/src/null.rs
vendored
Normal file
39
third-party/vendor/notify/src/null.rs
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
//! Stub Watcher implementation
|
||||
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use crate::Config;
|
||||
|
||||
use super::{RecursiveMode, Result, Watcher};
|
||||
use std::path::Path;
|
||||
|
||||
/// Stub `Watcher` implementation
|
||||
///
|
||||
/// Events are never delivered from this watcher.
|
||||
#[derive(Debug)]
|
||||
pub struct NullWatcher;
|
||||
|
||||
impl Watcher for NullWatcher {
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new<F: crate::EventHandler>(event_handler: F, config: Config) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(NullWatcher)
|
||||
}
|
||||
|
||||
fn configure(&mut self, config: Config) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
crate::WatcherKind::NullWatcher
|
||||
}
|
||||
}
|
||||
640
third-party/vendor/notify/src/poll.rs
vendored
Normal file
640
third-party/vendor/notify/src/poll.rs
vendored
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
//! Generic Watcher implementation based on polling
|
||||
//!
|
||||
//! 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::{unbounded, Config, Error, EventHandler, Receiver, RecursiveMode, Sender, Watcher};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// Event send for registered handler on initial directory scans
|
||||
pub type ScanEvent = crate::Result<PathBuf>;
|
||||
|
||||
/// Handler trait for receivers of ScanEvent.
|
||||
/// Very much the same as [EventHandler], but including the Result.
|
||||
///
|
||||
/// See the full example for more information.
|
||||
pub trait ScanEventHandler: Send + 'static {
|
||||
/// Handles an event.
|
||||
fn handle_event(&mut self, event: ScanEvent);
|
||||
}
|
||||
|
||||
impl<F> ScanEventHandler for F
|
||||
where
|
||||
F: FnMut(ScanEvent) + Send + 'static,
|
||||
{
|
||||
fn handle_event(&mut self, event: ScanEvent) {
|
||||
(self)(event);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crossbeam-channel")]
|
||||
impl ScanEventHandler for crossbeam_channel::Sender<ScanEvent> {
|
||||
fn handle_event(&mut self, event: ScanEvent) {
|
||||
let _ = self.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl ScanEventHandler for std::sync::mpsc::Sender<ScanEvent> {
|
||||
fn handle_event(&mut self, event: ScanEvent) {
|
||||
let _ = self.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl ScanEventHandler for () {
|
||||
fn handle_event(&mut self, _event: ScanEvent) {}
|
||||
}
|
||||
|
||||
use data::{DataBuilder, WatchData};
|
||||
mod data {
|
||||
use crate::{
|
||||
event::{CreateKind, DataChange, Event, EventKind, MetadataKind, ModifyKind, RemoveKind},
|
||||
EventHandler,
|
||||
};
|
||||
use filetime::FileTime;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{hash_map::RandomState, HashMap},
|
||||
fmt::{self, Debug},
|
||||
fs::{self, File, Metadata},
|
||||
hash::{BuildHasher, Hasher},
|
||||
io::{self, Read},
|
||||
path::{Path, PathBuf},
|
||||
time::Instant,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::ScanEventHandler;
|
||||
|
||||
/// Builder for [`WatchData`] & [`PathData`].
|
||||
pub(super) struct DataBuilder {
|
||||
emitter: EventEmitter,
|
||||
scan_emitter: Option<Box<RefCell<dyn ScanEventHandler>>>,
|
||||
|
||||
// TODO: May allow user setup their custom BuildHasher / BuildHasherDefault
|
||||
// in future.
|
||||
build_hasher: Option<RandomState>,
|
||||
|
||||
// current timestamp for building Data.
|
||||
now: Instant,
|
||||
}
|
||||
|
||||
impl DataBuilder {
|
||||
pub(super) fn new<F, G>(
|
||||
event_handler: F,
|
||||
compare_content: bool,
|
||||
scan_emitter: Option<G>,
|
||||
) -> Self
|
||||
where
|
||||
F: EventHandler,
|
||||
G: ScanEventHandler,
|
||||
{
|
||||
let scan_emitter = match scan_emitter {
|
||||
None => None,
|
||||
Some(v) => {
|
||||
// workaround for a weird type resolution bug when directly going to dyn Trait
|
||||
let intermediate: Box<RefCell<dyn ScanEventHandler>> =
|
||||
Box::new(RefCell::new(v));
|
||||
Some(intermediate)
|
||||
}
|
||||
};
|
||||
Self {
|
||||
emitter: EventEmitter::new(event_handler),
|
||||
scan_emitter,
|
||||
build_hasher: compare_content.then(RandomState::default),
|
||||
now: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update internal timestamp.
|
||||
pub(super) fn update_timestamp(&mut self) {
|
||||
self.now = Instant::now();
|
||||
}
|
||||
|
||||
/// Create [`WatchData`].
|
||||
///
|
||||
/// This function will return `Err(_)` if can not retrieve metadata from
|
||||
/// the path location. (e.g., not found).
|
||||
pub(super) fn build_watch_data(
|
||||
&self,
|
||||
root: PathBuf,
|
||||
is_recursive: bool,
|
||||
) -> Option<WatchData> {
|
||||
WatchData::new(self, root, is_recursive)
|
||||
}
|
||||
|
||||
/// Create [`PathData`].
|
||||
fn build_path_data(&self, meta_path: &MetaPath) -> PathData {
|
||||
PathData::new(self, meta_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for DataBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("DataBuilder")
|
||||
.field("build_hasher", &self.build_hasher)
|
||||
.field("now", &self.now)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct WatchData {
|
||||
// config part, won't change.
|
||||
root: PathBuf,
|
||||
is_recursive: bool,
|
||||
|
||||
// current status part.
|
||||
all_path_data: HashMap<PathBuf, PathData>,
|
||||
}
|
||||
|
||||
impl WatchData {
|
||||
/// Scan filesystem and create a new `WatchData`.
|
||||
///
|
||||
/// # Side effect
|
||||
///
|
||||
/// This function may send event by `data_builder.emitter`.
|
||||
fn new(data_builder: &DataBuilder, root: PathBuf, is_recursive: bool) -> Option<Self> {
|
||||
// If metadata read error at `root` path, it will emit
|
||||
// a error event and stop to create the whole `WatchData`.
|
||||
//
|
||||
// QUESTION: inconsistent?
|
||||
//
|
||||
// When user try to *CREATE* a watch by `poll_watcher.watch(root, ..)`,
|
||||
// if `root` path hit an io error, then watcher will reject to
|
||||
// create this new watch.
|
||||
//
|
||||
// This may inconsistent with *POLLING* a watch. When watcher
|
||||
// continue polling, io error at root path will not delete
|
||||
// a existing watch. polling still working.
|
||||
//
|
||||
// So, consider a config file may not exists at first time but may
|
||||
// create after a while, developer cannot watch it.
|
||||
//
|
||||
// FIXME: Can we always allow to watch a path, even file not
|
||||
// found at this path?
|
||||
if let Err(e) = fs::metadata(&root) {
|
||||
data_builder.emitter.emit_io_err(e, &root);
|
||||
return None;
|
||||
}
|
||||
|
||||
let all_path_data =
|
||||
Self::scan_all_path_data(data_builder, root.clone(), is_recursive, true).collect();
|
||||
|
||||
Some(Self {
|
||||
root,
|
||||
is_recursive,
|
||||
all_path_data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Rescan filesystem and update this `WatchData`.
|
||||
///
|
||||
/// # Side effect
|
||||
///
|
||||
/// This function may emit event by `data_builder.emitter`.
|
||||
pub(super) fn rescan(&mut self, data_builder: &mut DataBuilder) {
|
||||
// scan current filesystem.
|
||||
for (path, new_path_data) in
|
||||
Self::scan_all_path_data(data_builder, self.root.clone(), self.is_recursive, false)
|
||||
{
|
||||
let old_path_data = self
|
||||
.all_path_data
|
||||
.insert(path.clone(), new_path_data.clone());
|
||||
|
||||
// emit event
|
||||
let event =
|
||||
PathData::compare_to_event(path, old_path_data.as_ref(), Some(&new_path_data));
|
||||
if let Some(event) = event {
|
||||
data_builder.emitter.emit_ok(event);
|
||||
}
|
||||
}
|
||||
|
||||
// scan for disappeared paths.
|
||||
let mut disappeared_paths = Vec::new();
|
||||
for (path, path_data) in self.all_path_data.iter() {
|
||||
if path_data.last_check < data_builder.now {
|
||||
disappeared_paths.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// remove disappeared paths
|
||||
for path in disappeared_paths {
|
||||
let old_path_data = self.all_path_data.remove(&path);
|
||||
|
||||
// emit event
|
||||
let event = PathData::compare_to_event(path, old_path_data.as_ref(), None);
|
||||
if let Some(event) = event {
|
||||
data_builder.emitter.emit_ok(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all `PathData` by given configuration.
|
||||
///
|
||||
/// # Side Effect
|
||||
///
|
||||
/// This function may emit some IO Error events by `data_builder.emitter`.
|
||||
fn scan_all_path_data(
|
||||
data_builder: &'_ DataBuilder,
|
||||
root: PathBuf,
|
||||
is_recursive: bool,
|
||||
// whether this is an initial scan, used only for events
|
||||
is_initial: 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.
|
||||
//
|
||||
// See: https://docs.rs/walkdir/2.0.1/walkdir/struct.WalkDir.html#method.new
|
||||
WalkDir::new(root)
|
||||
.follow_links(true)
|
||||
.max_depth(Self::dir_scan_depth(is_recursive))
|
||||
.into_iter()
|
||||
//
|
||||
// QUESTION: should we ignore IO Error?
|
||||
//
|
||||
// current implementation ignore some IO error, e.g.,
|
||||
//
|
||||
// - `.filter_map(|entry| entry.ok())`
|
||||
// - all read error when hashing
|
||||
//
|
||||
// but the code also interest with `fs::metadata()` error and
|
||||
// propagate to event handler. It may not consistent.
|
||||
//
|
||||
// FIXME: Should we emit all IO error events? Or ignore them all?
|
||||
.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(move |entry| match entry.metadata() {
|
||||
Ok(metadata) => {
|
||||
let path = entry.into_path();
|
||||
if is_initial {
|
||||
// emit initial scans
|
||||
if let Some(ref emitter) = data_builder.scan_emitter {
|
||||
emitter.borrow_mut().handle_event(Ok(path.clone()));
|
||||
}
|
||||
}
|
||||
let meta_path = MetaPath::from_parts_unchecked(path, metadata);
|
||||
let data_path = data_builder.build_path_data(&meta_path);
|
||||
|
||||
Some((meta_path.into_path(), data_path))
|
||||
}
|
||||
Err(e) => {
|
||||
// emit event.
|
||||
let path = entry.into_path();
|
||||
data_builder.emitter.emit_io_err(e, path);
|
||||
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn dir_scan_depth(is_recursive: bool) -> usize {
|
||||
if is_recursive {
|
||||
usize::max_value()
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stored data for a one path locations.
|
||||
///
|
||||
/// See [`WatchData`] for more detail.
|
||||
#[derive(Debug, Clone)]
|
||||
struct PathData {
|
||||
/// File updated time.
|
||||
mtime: i64,
|
||||
|
||||
/// Content's hash value, only available if user request compare file
|
||||
/// contents and read successful.
|
||||
hash: Option<u64>,
|
||||
|
||||
/// Checked time.
|
||||
last_check: Instant,
|
||||
}
|
||||
|
||||
impl PathData {
|
||||
/// Create a new `PathData`.
|
||||
fn new(data_builder: &DataBuilder, meta_path: &MetaPath) -> PathData {
|
||||
let metadata = meta_path.metadata();
|
||||
|
||||
PathData {
|
||||
mtime: FileTime::from_last_modification_time(metadata).seconds(),
|
||||
hash: data_builder
|
||||
.build_hasher
|
||||
.as_ref()
|
||||
.filter(|_| metadata.is_file())
|
||||
.and_then(|build_hasher| {
|
||||
Self::get_content_hash(build_hasher, meta_path.path()).ok()
|
||||
}),
|
||||
|
||||
last_check: data_builder.now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get hash value for the data content in given file `path`.
|
||||
fn get_content_hash(build_hasher: &RandomState, path: &Path) -> io::Result<u64> {
|
||||
let mut hasher = build_hasher.build_hasher();
|
||||
let mut file = File::open(path)?;
|
||||
let mut buf = [0; 512];
|
||||
|
||||
loop {
|
||||
let n = match file.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(len) => len,
|
||||
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
hasher.write(&buf[..n]);
|
||||
}
|
||||
|
||||
Ok(hasher.finish())
|
||||
}
|
||||
|
||||
/// Get [`Event`] by compare two optional [`PathData`].
|
||||
fn compare_to_event<P>(
|
||||
path: P,
|
||||
old: Option<&PathData>,
|
||||
new: Option<&PathData>,
|
||||
) -> Option<Event>
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
match (old, new) {
|
||||
(Some(old), Some(new)) => {
|
||||
if new.mtime > old.mtime {
|
||||
Some(EventKind::Modify(ModifyKind::Metadata(
|
||||
MetadataKind::WriteTime,
|
||||
)))
|
||||
} else if new.hash != old.hash {
|
||||
Some(EventKind::Modify(ModifyKind::Data(DataChange::Any)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(None, Some(_new)) => Some(EventKind::Create(CreateKind::Any)),
|
||||
(Some(_old), None) => Some(EventKind::Remove(RemoveKind::Any)),
|
||||
(None, None) => None,
|
||||
}
|
||||
.map(|event_kind| Event::new(event_kind).add_path(path.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compose path and its metadata.
|
||||
///
|
||||
/// This data structure designed for make sure path and its metadata can be
|
||||
/// transferred in consistent way, and may avoid some duplicated
|
||||
/// `fs::metadata()` function call in some situations.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct MetaPath {
|
||||
path: PathBuf,
|
||||
metadata: Metadata,
|
||||
}
|
||||
|
||||
impl MetaPath {
|
||||
/// Create `MetaPath` by given parts.
|
||||
///
|
||||
/// # Invariant
|
||||
///
|
||||
/// User must make sure the input `metadata` are associated with `path`.
|
||||
fn from_parts_unchecked(path: PathBuf, metadata: Metadata) -> Self {
|
||||
Self { path, metadata }
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &Metadata {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
fn into_path(self) -> PathBuf {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin wrapper for outer event handler, for easy to use.
|
||||
struct EventEmitter(
|
||||
// Use `RefCell` to make sure `emit()` only need shared borrow of self (&self).
|
||||
// Use `Box` to make sure EventEmitter is Sized.
|
||||
Box<RefCell<dyn EventHandler>>,
|
||||
);
|
||||
|
||||
impl EventEmitter {
|
||||
fn new<F: EventHandler>(event_handler: F) -> Self {
|
||||
Self(Box::new(RefCell::new(event_handler)))
|
||||
}
|
||||
|
||||
/// Emit single event.
|
||||
fn emit(&self, event: crate::Result<Event>) {
|
||||
self.0.borrow_mut().handle_event(event);
|
||||
}
|
||||
|
||||
/// Emit event.
|
||||
fn emit_ok(&self, event: Event) {
|
||||
self.emit(Ok(event))
|
||||
}
|
||||
|
||||
/// Emit io error event.
|
||||
fn emit_io_err<E, P>(&self, err: E, path: P)
|
||||
where
|
||||
E: Into<io::Error>,
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
self.emit(Err(crate::Error::io(err.into()).add_path(path.into())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Polling based `Watcher` implementation.
|
||||
///
|
||||
/// By default scans through all files and checks for changed entries based on their change date.
|
||||
/// Can also be changed to perform file content change checks.
|
||||
///
|
||||
/// See [Config] for more details.
|
||||
#[derive(Debug)]
|
||||
pub struct PollWatcher {
|
||||
watches: Arc<Mutex<HashMap<PathBuf, WatchData>>>,
|
||||
data_builder: Arc<Mutex<DataBuilder>>,
|
||||
want_to_stop: Arc<AtomicBool>,
|
||||
/// channel to the poll loop
|
||||
/// currently used only for manual polling
|
||||
message_channel: Sender<()>,
|
||||
delay: Option<Duration>,
|
||||
}
|
||||
|
||||
impl PollWatcher {
|
||||
/// Create a new [PollWatcher], configured as needed.
|
||||
pub fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<PollWatcher> {
|
||||
Self::with_opt::<_, ()>(event_handler, config, None)
|
||||
}
|
||||
|
||||
/// Actively poll for changes. Can be combined with a timeout of 0 to perform only manual polling.
|
||||
pub fn poll(&self) -> crate::Result<()> {
|
||||
self.message_channel
|
||||
.send(())
|
||||
.map_err(|_| Error::generic("failed to send poll message"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new [PollWatcher] with an scan event handler.
|
||||
///
|
||||
/// `scan_fallback` is called on the initial scan with all files seen by the pollwatcher.
|
||||
pub fn with_initial_scan<F: EventHandler, G: ScanEventHandler>(
|
||||
event_handler: F,
|
||||
config: Config,
|
||||
scan_callback: G,
|
||||
) -> crate::Result<PollWatcher> {
|
||||
Self::with_opt(event_handler, config, Some(scan_callback))
|
||||
}
|
||||
|
||||
/// create a new PollWatcher with all options
|
||||
fn with_opt<F: EventHandler, G: ScanEventHandler>(
|
||||
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 (tx, rx) = unbounded();
|
||||
|
||||
let poll_watcher = PollWatcher {
|
||||
watches: Default::default(),
|
||||
data_builder: Arc::new(Mutex::new(data_builder)),
|
||||
want_to_stop: Arc::new(AtomicBool::new(false)),
|
||||
delay: config.poll_interval_v2(),
|
||||
message_channel: tx,
|
||||
};
|
||||
|
||||
poll_watcher.run(rx);
|
||||
|
||||
Ok(poll_watcher)
|
||||
}
|
||||
|
||||
fn run(&self, rx: Receiver<()>) {
|
||||
let watches = Arc::clone(&self.watches);
|
||||
let data_builder = Arc::clone(&self.data_builder);
|
||||
let want_to_stop = Arc::clone(&self.want_to_stop);
|
||||
let delay = self.delay;
|
||||
|
||||
let _ = thread::Builder::new()
|
||||
.name("notify-rs poll loop".to_string())
|
||||
.spawn(move || {
|
||||
loop {
|
||||
if want_to_stop.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
|
||||
// HINT: Make sure always lock in the same order to avoid deadlock.
|
||||
//
|
||||
// FIXME: inconsistent: some place mutex poison cause panic,
|
||||
// some place just ignore.
|
||||
if let (Ok(mut watches), Ok(mut data_builder)) =
|
||||
(watches.lock(), data_builder.lock())
|
||||
{
|
||||
data_builder.update_timestamp();
|
||||
|
||||
let vals = watches.values_mut();
|
||||
for watch_data in vals {
|
||||
watch_data.rescan(&mut data_builder);
|
||||
}
|
||||
}
|
||||
// TODO: v7.0 use delay - (Instant::now().saturating_duration_since(start))
|
||||
if let Some(delay) = delay {
|
||||
let _ = rx.recv_timeout(delay);
|
||||
} else {
|
||||
let _ = rx.recv();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Watch a path location.
|
||||
///
|
||||
/// QUESTION: this function never return an Error, is it as intend?
|
||||
/// Please also consider the IO Error event problem.
|
||||
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) {
|
||||
// HINT: Make sure always lock in the same order to avoid deadlock.
|
||||
//
|
||||
// FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
|
||||
if let (Ok(mut watches), Ok(mut data_builder)) =
|
||||
(self.watches.lock(), self.data_builder.lock())
|
||||
{
|
||||
data_builder.update_timestamp();
|
||||
|
||||
let watch_data =
|
||||
data_builder.build_watch_data(path.to_path_buf(), recursive_mode.is_recursive());
|
||||
|
||||
// if create watch_data successful, add it to watching list.
|
||||
if let Some(watch_data) = watch_data {
|
||||
watches.insert(path.to_path_buf(), watch_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwatch a path.
|
||||
///
|
||||
/// Return `Err(_)` if given path has't be monitored.
|
||||
fn unwatch_inner(&mut self, path: &Path) -> crate::Result<()> {
|
||||
// FIXME: inconsistent: some place mutex poison cause panic, some place just ignore.
|
||||
self.watches
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove(path)
|
||||
.map(|_| ())
|
||||
.ok_or_else(crate::Error::watch_not_found)
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for PollWatcher {
|
||||
/// Create a new [PollWatcher].
|
||||
fn new<F: EventHandler>(event_handler: F, config: Config) -> crate::Result<Self> {
|
||||
Self::new(event_handler, config)
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> crate::Result<()> {
|
||||
self.watch_inner(path, recursive_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> crate::Result<()> {
|
||||
self.unwatch_inner(path)
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
crate::WatcherKind::PollWatcher
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PollWatcher {
|
||||
fn drop(&mut self) {
|
||||
self.want_to_stop.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poll_watcher_is_send_and_sync() {
|
||||
fn check<T: Send + Sync>() {}
|
||||
check::<PollWatcher>();
|
||||
}
|
||||
548
third-party/vendor/notify/src/windows.rs
vendored
Normal file
548
third-party/vendor/notify/src/windows.rs
vendored
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
#![allow(missing_docs)]
|
||||
//! Watcher implementation for Windows' directory management APIs
|
||||
//!
|
||||
//! For more information see the [ReadDirectoryChangesW reference][ref].
|
||||
//!
|
||||
//! [ref]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363950(v=vs.85).aspx
|
||||
|
||||
use crate::{bounded, unbounded, BoundSender, Config, Receiver, Sender};
|
||||
use crate::{event::*, WatcherKind};
|
||||
use crate::{Error, EventHandler, RecursiveMode, Result, Watcher};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use windows_sys::Win32::Foundation::{
|
||||
CloseHandle, ERROR_OPERATION_ABORTED, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{
|
||||
CreateFileW, ReadDirectoryChangesW, FILE_ACTION_ADDED, FILE_ACTION_MODIFIED,
|
||||
FILE_ACTION_REMOVED, FILE_ACTION_RENAMED_NEW_NAME, FILE_ACTION_RENAMED_OLD_NAME,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OVERLAPPED, FILE_LIST_DIRECTORY,
|
||||
FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_CREATION, FILE_NOTIFY_CHANGE_DIR_NAME,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY,
|
||||
FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_INFORMATION, FILE_SHARE_DELETE, FILE_SHARE_READ,
|
||||
FILE_SHARE_WRITE, OPEN_EXISTING,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
CreateSemaphoreW, ReleaseSemaphore, WaitForSingleObjectEx, INFINITE,
|
||||
};
|
||||
use windows_sys::Win32::System::IO::{CancelIo, OVERLAPPED};
|
||||
|
||||
const BUF_SIZE: u32 = 16384;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ReadData {
|
||||
dir: PathBuf, // directory that is being watched
|
||||
file: Option<PathBuf>, // if a file is being watched, this is its full path
|
||||
complete_sem: HANDLE,
|
||||
is_recursive: bool,
|
||||
}
|
||||
|
||||
struct ReadDirectoryRequest {
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
buffer: [u8; BUF_SIZE as usize],
|
||||
handle: HANDLE,
|
||||
data: ReadData,
|
||||
}
|
||||
|
||||
enum Action {
|
||||
Watch(PathBuf, RecursiveMode),
|
||||
Unwatch(PathBuf),
|
||||
Stop,
|
||||
Configure(Config, BoundSender<Result<bool>>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum MetaEvent {
|
||||
SingleWatchComplete,
|
||||
WatcherAwakened,
|
||||
}
|
||||
|
||||
struct WatchState {
|
||||
dir_handle: HANDLE,
|
||||
complete_sem: HANDLE,
|
||||
}
|
||||
|
||||
struct ReadDirectoryChangesServer {
|
||||
rx: Receiver<Action>,
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
meta_tx: Sender<MetaEvent>,
|
||||
cmd_tx: Sender<Result<PathBuf>>,
|
||||
watches: HashMap<PathBuf, WatchState>,
|
||||
wakeup_sem: HANDLE,
|
||||
}
|
||||
|
||||
impl ReadDirectoryChangesServer {
|
||||
fn start(
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
meta_tx: Sender<MetaEvent>,
|
||||
cmd_tx: Sender<Result<PathBuf>>,
|
||||
wakeup_sem: HANDLE,
|
||||
) -> Sender<Action> {
|
||||
let (action_tx, action_rx) = unbounded();
|
||||
// it is, in fact, ok to send the semaphore across threads
|
||||
let sem_temp = wakeup_sem as u64;
|
||||
let _ = thread::Builder::new()
|
||||
.name("notify-rs windows loop".to_string())
|
||||
.spawn(move || {
|
||||
let wakeup_sem = sem_temp as HANDLE;
|
||||
let server = ReadDirectoryChangesServer {
|
||||
rx: action_rx,
|
||||
event_handler,
|
||||
meta_tx,
|
||||
cmd_tx,
|
||||
watches: HashMap::new(),
|
||||
wakeup_sem,
|
||||
};
|
||||
server.run();
|
||||
});
|
||||
action_tx
|
||||
}
|
||||
|
||||
fn run(mut self) {
|
||||
loop {
|
||||
// process all available actions first
|
||||
let mut stopped = false;
|
||||
|
||||
while let Ok(action) = self.rx.try_recv() {
|
||||
match action {
|
||||
Action::Watch(path, recursive_mode) => {
|
||||
let res = self.add_watch(path, recursive_mode.is_recursive());
|
||||
let _ = self.cmd_tx.send(res);
|
||||
}
|
||||
Action::Unwatch(path) => self.remove_watch(path),
|
||||
Action::Stop => {
|
||||
stopped = true;
|
||||
for ws in self.watches.values() {
|
||||
stop_watch(ws, &self.meta_tx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Action::Configure(config, tx) => {
|
||||
self.configure_raw_mode(config, tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if stopped {
|
||||
break;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// wait with alertable flag so that the completion routine fires
|
||||
let waitres = WaitForSingleObjectEx(self.wakeup_sem, 100, 1);
|
||||
if waitres == WAIT_OBJECT_0 {
|
||||
let _ = self.meta_tx.send(MetaEvent::WatcherAwakened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have to clean this up, since the watcher may be long gone
|
||||
unsafe {
|
||||
CloseHandle(self.wakeup_sem);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_watch(&mut self, path: PathBuf, is_recursive: bool) -> Result<PathBuf> {
|
||||
// path must exist and be either a file or directory
|
||||
if !path.is_dir() && !path.is_file() {
|
||||
return Err(
|
||||
Error::generic("Input watch path is neither a file nor a directory.")
|
||||
.add_path(path),
|
||||
);
|
||||
}
|
||||
|
||||
let (watching_file, dir_target) = {
|
||||
if path.is_dir() {
|
||||
(false, path.clone())
|
||||
} else {
|
||||
// emulate file watching by watching the parent directory
|
||||
(true, path.parent().unwrap().to_path_buf())
|
||||
}
|
||||
};
|
||||
|
||||
let encoded_path: Vec<u16> = dir_target
|
||||
.as_os_str()
|
||||
.encode_wide()
|
||||
.chain(Some(0))
|
||||
.collect();
|
||||
let handle;
|
||||
unsafe {
|
||||
handle = CreateFileW(
|
||||
encoded_path.as_ptr(),
|
||||
FILE_LIST_DIRECTORY,
|
||||
FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
|
||||
ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||
0,
|
||||
);
|
||||
|
||||
if handle == INVALID_HANDLE_VALUE {
|
||||
return Err(if watching_file {
|
||||
Error::generic(
|
||||
"You attempted to watch a single file, but parent \
|
||||
directory could not be opened.",
|
||||
)
|
||||
.add_path(path)
|
||||
} else {
|
||||
// TODO: Call GetLastError for better error info?
|
||||
Error::path_not_found().add_path(path)
|
||||
});
|
||||
}
|
||||
}
|
||||
let wf = if watching_file {
|
||||
Some(path.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// every watcher gets its own semaphore to signal completion
|
||||
let semaphore = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) };
|
||||
if semaphore == 0 || semaphore == INVALID_HANDLE_VALUE {
|
||||
unsafe {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
return Err(Error::generic("Failed to create semaphore for watch.").add_path(path));
|
||||
}
|
||||
let rd = ReadData {
|
||||
dir: dir_target,
|
||||
file: wf,
|
||||
complete_sem: semaphore,
|
||||
is_recursive,
|
||||
};
|
||||
let ws = WatchState {
|
||||
dir_handle: handle,
|
||||
complete_sem: semaphore,
|
||||
};
|
||||
self.watches.insert(path.clone(), ws);
|
||||
start_read(&rd, self.event_handler.clone(), handle);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn remove_watch(&mut self, path: PathBuf) {
|
||||
if let Some(ws) = self.watches.remove(&path) {
|
||||
stop_watch(&ws, &self.meta_tx);
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_raw_mode(&mut self, _config: Config, tx: BoundSender<Result<bool>>) {
|
||||
tx.send(Ok(false))
|
||||
.expect("configuration channel disconnect");
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_watch(ws: &WatchState, meta_tx: &Sender<MetaEvent>) {
|
||||
unsafe {
|
||||
let cio = CancelIo(ws.dir_handle);
|
||||
let ch = CloseHandle(ws.dir_handle);
|
||||
// have to wait for it, otherwise we leak the memory allocated for there read request
|
||||
if cio != 0 && ch != 0 {
|
||||
while WaitForSingleObjectEx(ws.complete_sem, INFINITE, 1) != WAIT_OBJECT_0 {
|
||||
// drain the apc queue, fix for https://github.com/notify-rs/notify/issues/287#issuecomment-801465550
|
||||
}
|
||||
}
|
||||
CloseHandle(ws.complete_sem);
|
||||
}
|
||||
let _ = meta_tx.send(MetaEvent::SingleWatchComplete);
|
||||
}
|
||||
|
||||
fn start_read(rd: &ReadData, event_handler: Arc<Mutex<dyn EventHandler>>, handle: HANDLE) {
|
||||
let mut request = Box::new(ReadDirectoryRequest {
|
||||
event_handler,
|
||||
handle,
|
||||
buffer: [0u8; BUF_SIZE as usize],
|
||||
data: rd.clone(),
|
||||
});
|
||||
|
||||
let flags = FILE_NOTIFY_CHANGE_FILE_NAME
|
||||
| FILE_NOTIFY_CHANGE_DIR_NAME
|
||||
| FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||
| FILE_NOTIFY_CHANGE_SIZE
|
||||
| FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||
| FILE_NOTIFY_CHANGE_CREATION
|
||||
| FILE_NOTIFY_CHANGE_SECURITY;
|
||||
|
||||
let monitor_subdir = if (&request.data.file).is_none() && request.data.is_recursive {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mut overlapped = std::mem::ManuallyDrop::new(Box::new(mem::zeroed::<OVERLAPPED>()));
|
||||
// When using callback based async requests, we are allowed to use the hEvent member
|
||||
// for our own purposes
|
||||
|
||||
let req_buf = request.buffer.as_mut_ptr() as *mut c_void;
|
||||
let request_p = Box::into_raw(request) as isize;
|
||||
overlapped.hEvent = request_p;
|
||||
|
||||
// This is using an asynchronous call with a completion routine for receiving notifications
|
||||
// An I/O completion port would probably be more performant
|
||||
let ret = ReadDirectoryChangesW(
|
||||
handle,
|
||||
req_buf,
|
||||
BUF_SIZE,
|
||||
monitor_subdir,
|
||||
flags,
|
||||
&mut 0u32 as *mut u32, // not used for async reqs
|
||||
(&mut **overlapped) as *mut OVERLAPPED,
|
||||
Some(handle_event),
|
||||
);
|
||||
|
||||
if ret == 0 {
|
||||
// error reading. retransmute request memory to allow drop.
|
||||
// Because of the error, ownership of the `overlapped` alloc was not passed
|
||||
// over to `ReadDirectoryChangesW`.
|
||||
// So we can claim ownership back.
|
||||
let _overlapped_alloc = std::mem::ManuallyDrop::into_inner(overlapped);
|
||||
let request: Box<ReadDirectoryRequest> = mem::transmute(request_p);
|
||||
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn handle_event(
|
||||
error_code: u32,
|
||||
_bytes_written: u32,
|
||||
overlapped: *mut OVERLAPPED,
|
||||
) {
|
||||
let overlapped: Box<OVERLAPPED> = Box::from_raw(overlapped);
|
||||
let request: Box<ReadDirectoryRequest> = Box::from_raw(overlapped.hEvent as *mut _);
|
||||
|
||||
if error_code == ERROR_OPERATION_ABORTED {
|
||||
// received when dir is unwatched or watcher is shutdown; return and let overlapped/request
|
||||
// get drop-cleaned
|
||||
ReleaseSemaphore(request.data.complete_sem, 1, ptr::null_mut());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next request queued up as soon as possible
|
||||
start_read(&request.data, request.event_handler.clone(), request.handle);
|
||||
|
||||
// The FILE_NOTIFY_INFORMATION struct has a variable length due to the variable length
|
||||
// string as its last member. Each struct contains an offset for getting the next entry in
|
||||
// the buffer.
|
||||
let mut cur_offset: *const u8 = request.buffer.as_ptr();
|
||||
let mut cur_entry = cur_offset as *const FILE_NOTIFY_INFORMATION;
|
||||
loop {
|
||||
// filename length is size in bytes, so / 2
|
||||
let len = (*cur_entry).FileNameLength as usize / 2;
|
||||
let encoded_path: &[u16] = slice::from_raw_parts((*cur_entry).FileName.as_ptr(), len);
|
||||
// prepend root to get a full path
|
||||
let path = request
|
||||
.data
|
||||
.dir
|
||||
.join(PathBuf::from(OsString::from_wide(encoded_path)));
|
||||
|
||||
// if we are watching a single file, ignore the event unless the path is exactly
|
||||
// the watched file
|
||||
let skip = match request.data.file {
|
||||
None => false,
|
||||
Some(ref watch_path) => *watch_path != path,
|
||||
};
|
||||
|
||||
if !skip {
|
||||
log::trace!(
|
||||
"Event: path = `{}`, action = {:?}",
|
||||
path.display(),
|
||||
(*cur_entry).Action
|
||||
);
|
||||
|
||||
let newe = Event::new(EventKind::Any).add_path(path);
|
||||
|
||||
fn emit_event(event_handler: &Mutex<dyn EventHandler>, res: Result<Event>) {
|
||||
if let Ok(mut guard) = event_handler.lock() {
|
||||
let f: &mut dyn EventHandler = &mut *guard;
|
||||
f.handle_event(res);
|
||||
}
|
||||
}
|
||||
|
||||
let event_handler = |res| emit_event(&request.event_handler, res);
|
||||
|
||||
if (*cur_entry).Action == FILE_ACTION_RENAMED_OLD_NAME {
|
||||
let mode = RenameMode::From;
|
||||
let kind = ModifyKind::Name(mode);
|
||||
let kind = EventKind::Modify(kind);
|
||||
let ev = newe.set_kind(kind);
|
||||
event_handler(Ok(ev))
|
||||
} else {
|
||||
match (*cur_entry).Action {
|
||||
FILE_ACTION_RENAMED_NEW_NAME => {
|
||||
let kind = EventKind::Modify(ModifyKind::Name(RenameMode::To));
|
||||
let ev = newe.set_kind(kind);
|
||||
event_handler(Ok(ev));
|
||||
}
|
||||
FILE_ACTION_ADDED => {
|
||||
let kind = EventKind::Create(CreateKind::Any);
|
||||
let ev = newe.set_kind(kind);
|
||||
event_handler(Ok(ev));
|
||||
}
|
||||
FILE_ACTION_REMOVED => {
|
||||
let kind = EventKind::Remove(RemoveKind::Any);
|
||||
let ev = newe.set_kind(kind);
|
||||
event_handler(Ok(ev));
|
||||
}
|
||||
FILE_ACTION_MODIFIED => {
|
||||
let kind = EventKind::Modify(ModifyKind::Any);
|
||||
let ev = newe.set_kind(kind);
|
||||
event_handler(Ok(ev));
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (*cur_entry).NextEntryOffset == 0 {
|
||||
break;
|
||||
}
|
||||
cur_offset = cur_offset.offset((*cur_entry).NextEntryOffset as isize);
|
||||
cur_entry = cur_offset as *const FILE_NOTIFY_INFORMATION;
|
||||
}
|
||||
}
|
||||
|
||||
/// Watcher implementation based on ReadDirectoryChanges
|
||||
#[derive(Debug)]
|
||||
pub struct ReadDirectoryChangesWatcher {
|
||||
tx: Sender<Action>,
|
||||
cmd_rx: Receiver<Result<PathBuf>>,
|
||||
wakeup_sem: HANDLE,
|
||||
}
|
||||
|
||||
impl ReadDirectoryChangesWatcher {
|
||||
pub fn create(
|
||||
event_handler: Arc<Mutex<dyn EventHandler>>,
|
||||
meta_tx: Sender<MetaEvent>,
|
||||
) -> Result<ReadDirectoryChangesWatcher> {
|
||||
let (cmd_tx, cmd_rx) = unbounded();
|
||||
|
||||
let wakeup_sem = unsafe { CreateSemaphoreW(ptr::null_mut(), 0, 1, ptr::null_mut()) };
|
||||
if wakeup_sem == 0 || wakeup_sem == INVALID_HANDLE_VALUE {
|
||||
return Err(Error::generic("Failed to create wakeup semaphore."));
|
||||
}
|
||||
|
||||
let action_tx =
|
||||
ReadDirectoryChangesServer::start(event_handler, meta_tx, cmd_tx, wakeup_sem);
|
||||
|
||||
Ok(ReadDirectoryChangesWatcher {
|
||||
tx: action_tx,
|
||||
cmd_rx,
|
||||
wakeup_sem,
|
||||
})
|
||||
}
|
||||
|
||||
fn wakeup_server(&mut self) {
|
||||
// breaks the server out of its wait state. right now this is really just an optimization,
|
||||
// so that if you add a watch you don't block for 100ms in watch() while the
|
||||
// server sleeps.
|
||||
unsafe {
|
||||
ReleaseSemaphore(self.wakeup_sem, 1, ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
fn send_action_require_ack(&mut self, action: Action, pb: &PathBuf) -> Result<()> {
|
||||
self.tx
|
||||
.send(action)
|
||||
.map_err(|_| Error::generic("Error sending to internal channel"))?;
|
||||
|
||||
// wake 'em up, we don't want to wait around for the ack
|
||||
self.wakeup_server();
|
||||
|
||||
let ack_pb = self
|
||||
.cmd_rx
|
||||
.recv()
|
||||
.map_err(|_| Error::generic("Error receiving from command channel"))?
|
||||
.map_err(|e| Error::generic(&format!("Error in watcher: {:?}", e)))?;
|
||||
|
||||
if pb.as_path() != ack_pb.as_path() {
|
||||
Err(Error::generic(&format!(
|
||||
"Expected ack for {:?} but got \
|
||||
ack for {:?}",
|
||||
pb, ack_pb
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn watch_inner(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
// path must exist and be either a file or directory
|
||||
if !pb.is_dir() && !pb.is_file() {
|
||||
return Err(Error::generic(
|
||||
"Input watch path is neither a file nor a directory.",
|
||||
));
|
||||
}
|
||||
self.send_action_require_ack(Action::Watch(pb.clone(), recursive_mode), &pb)
|
||||
}
|
||||
|
||||
fn unwatch_inner(&mut self, path: &Path) -> Result<()> {
|
||||
let pb = if path.is_absolute() {
|
||||
path.to_owned()
|
||||
} else {
|
||||
let p = env::current_dir().map_err(Error::io)?;
|
||||
p.join(path)
|
||||
};
|
||||
let res = self
|
||||
.tx
|
||||
.send(Action::Unwatch(pb))
|
||||
.map_err(|_| Error::generic("Error sending to internal channel"));
|
||||
self.wakeup_server();
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for ReadDirectoryChangesWatcher {
|
||||
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();
|
||||
let event_handler = Arc::new(Mutex::new(event_handler));
|
||||
Self::create(event_handler, meta_tx)
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path, recursive_mode: RecursiveMode) -> Result<()> {
|
||||
self.watch_inner(path, recursive_mode)
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> Result<()> {
|
||||
self.unwatch_inner(path)
|
||||
}
|
||||
|
||||
fn configure(&mut self, config: Config) -> Result<bool> {
|
||||
let (tx, rx) = bounded(1);
|
||||
self.tx.send(Action::Configure(config, tx))?;
|
||||
rx.recv()?
|
||||
}
|
||||
|
||||
fn kind() -> crate::WatcherKind {
|
||||
WatcherKind::ReadDirectoryChangesWatcher
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ReadDirectoryChangesWatcher {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.tx.send(Action::Stop);
|
||||
// better wake it up
|
||||
self.wakeup_server();
|
||||
}
|
||||
}
|
||||
|
||||
// `ReadDirectoryChangesWatcher` is not Send/Sync because of the semaphore Handle.
|
||||
// As said elsewhere it's perfectly safe to send it across threads.
|
||||
unsafe impl Send for ReadDirectoryChangesWatcher {}
|
||||
// Because all public methods are `&mut self` it's also perfectly safe to share references.
|
||||
unsafe impl Sync for ReadDirectoryChangesWatcher {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue