#![feature(unique)]
#![feature(const_fn)]
#![allow(non_camel_case_types)]
pub extern crate fswatch_sys;
extern crate libc;
#[macro_use]
extern crate cfg_if;
#[cfg(feature = "use_time")]
extern crate time;
pub use fswatch_sys as ffi;
use libc::{c_uint, c_void, c_double};
use std::ops::Drop;
use std::ffi::{CString, CStr};
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::sync::mpsc::{Sender, Receiver, channel};
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature = "fswatch_1_10_0")]
use std::ptr::Unique;
#[cfg(test)]
mod test;
type FswResult<T> = Result<T, FswError>;
#[derive(Debug, PartialEq)]
pub enum FswError {
FromFsw(FswStatus),
NulError(std::ffi::NulError),
MissingRequiredParameters
}
#[derive(Debug, PartialEq)]
pub enum FswStatus {
Ok,
UnknownError,
SessionUnknown,
MonitorAlreadyExists,
Memory,
UnknownMonitorType,
CallbackNotSet,
PathsNotSet,
MissingContext,
InvalidPath,
InvalidCallback,
InvalidLatency,
InvalidRegex,
MonitorAlreadyRunning,
UnknownValue,
InvalidProprety
}
impl From<ffi::FSW_STATUS> for FswStatus {
fn from(status: ffi::FSW_STATUS) -> FswStatus {
match status {
ffi::FSW_OK => FswStatus::Ok,
ffi::FSW_ERR_SESSION_UNKNOWN => FswStatus::SessionUnknown,
ffi::FSW_ERR_MONITOR_ALREADY_EXISTS => FswStatus::MonitorAlreadyExists,
ffi::FSW_ERR_MEMORY => FswStatus::Memory,
ffi::FSW_ERR_UNKNOWN_MONITOR_TYPE => FswStatus::UnknownMonitorType,
ffi::FSW_ERR_CALLBACK_NOT_SET => FswStatus::CallbackNotSet,
ffi::FSW_ERR_PATHS_NOT_SET => FswStatus::PathsNotSet,
ffi::FSW_ERR_MISSING_CONTEXT => FswStatus::MissingContext,
ffi::FSW_ERR_INVALID_PATH => FswStatus::InvalidPath,
ffi::FSW_ERR_INVALID_CALLBACK => FswStatus::InvalidCallback,
ffi::FSW_ERR_INVALID_LATENCY => FswStatus::InvalidLatency,
ffi::FSW_ERR_INVALID_REGEX => FswStatus::InvalidRegex,
ffi::FSW_ERR_MONITOR_ALREADY_RUNNING => FswStatus::MonitorAlreadyRunning,
ffi::FSW_ERR_UNKNOWN_VALUE => FswStatus::UnknownValue,
ffi::FSW_ERR_INVALID_PROPERTY => FswStatus::InvalidProprety,
ffi::FSW_ERR_UNKNOWN_ERROR | _ => FswStatus::UnknownError
}
}
}
#[derive(Debug, PartialEq)]
pub enum FswMonitorType {
SystemDefault,
FSEvents,
KQueue,
INotify,
Windows,
Poll,
Fen
}
impl From<FswMonitorType> for ffi::fsw_monitor_type {
fn from(monitor_type: FswMonitorType) -> ffi::fsw_monitor_type {
match monitor_type {
FswMonitorType::SystemDefault => ffi::fsw_monitor_type::system_default_monitor_type,
FswMonitorType::FSEvents => ffi::fsw_monitor_type::fsevents_monitor_type,
FswMonitorType::KQueue => ffi::fsw_monitor_type::kqueue_monitor_type,
FswMonitorType::INotify => ffi::fsw_monitor_type::inotify_monitor_type,
FswMonitorType::Windows => ffi::fsw_monitor_type::windows_monitor_type,
FswMonitorType::Poll => ffi::fsw_monitor_type::poll_monitor_type,
FswMonitorType::Fen => ffi::fsw_monitor_type::fen_monitor_type
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum FswEventFlag {
NoOp,
PlatformSpecific,
Created,
Updated,
Removed,
Renamed,
OwnerModified,
AttributeModified,
MovedFrom,
MovedTo,
IsFile,
IsDir,
IsSymLink,
Link,
Overflow
}
impl From<FswEventFlag> for ffi::fsw_event_flag {
fn from(flag: FswEventFlag) -> ffi::fsw_event_flag {
match flag {
FswEventFlag::NoOp => ffi::fsw_event_flag::NoOp,
FswEventFlag::PlatformSpecific => ffi::fsw_event_flag::PlatformSpecific,
FswEventFlag::Created => ffi::fsw_event_flag::Created,
FswEventFlag::Updated => ffi::fsw_event_flag::Updated,
FswEventFlag::Removed => ffi::fsw_event_flag::Removed,
FswEventFlag::Renamed => ffi::fsw_event_flag::Renamed,
FswEventFlag::OwnerModified => ffi::fsw_event_flag::OwnerModified,
FswEventFlag::AttributeModified => ffi::fsw_event_flag::AttributeModified,
FswEventFlag::MovedFrom => ffi::fsw_event_flag::MovedFrom,
FswEventFlag::MovedTo => ffi::fsw_event_flag::MovedTo,
FswEventFlag::IsFile => ffi::fsw_event_flag::IsFile,
FswEventFlag::IsDir => ffi::fsw_event_flag::IsDir,
FswEventFlag::IsSymLink => ffi::fsw_event_flag::IsSymLink,
FswEventFlag::Link => ffi::fsw_event_flag::Link,
FswEventFlag::Overflow => ffi::fsw_event_flag::Overflow
}
}
}
impl<'a> From<&'a ffi::fsw_event_flag> for FswEventFlag {
fn from(flag: &'a ffi::fsw_event_flag) -> FswEventFlag {
match *flag {
ffi::fsw_event_flag::NoOp => FswEventFlag::NoOp,
ffi::fsw_event_flag::PlatformSpecific => FswEventFlag::PlatformSpecific,
ffi::fsw_event_flag::Created => FswEventFlag::Created,
ffi::fsw_event_flag::Updated => FswEventFlag::Updated,
ffi::fsw_event_flag::Removed => FswEventFlag::Removed,
ffi::fsw_event_flag::Renamed => FswEventFlag::Renamed,
ffi::fsw_event_flag::OwnerModified => FswEventFlag::OwnerModified,
ffi::fsw_event_flag::AttributeModified => FswEventFlag::AttributeModified,
ffi::fsw_event_flag::MovedFrom => FswEventFlag::MovedFrom,
ffi::fsw_event_flag::MovedTo => FswEventFlag::MovedTo,
ffi::fsw_event_flag::IsFile => FswEventFlag::IsFile,
ffi::fsw_event_flag::IsDir => FswEventFlag::IsDir,
ffi::fsw_event_flag::IsSymLink => FswEventFlag::IsSymLink,
ffi::fsw_event_flag::Link => FswEventFlag::Link,
ffi::fsw_event_flag::Overflow => FswEventFlag::Overflow
}
}
}
#[derive(Debug)]
pub struct FswMonitorFilter {
pub text: String,
pub filter_type: FswFilterType,
pub case_sensitive: bool,
pub extended: bool
}
impl FswMonitorFilter {
pub fn new<S>(text: S, filter_type: FswFilterType, case_sensitive: bool, extended: bool) -> Self
where S: AsRef<str>
{
FswMonitorFilter {
text: text.as_ref().to_owned(),
filter_type: filter_type,
case_sensitive: case_sensitive,
extended: extended
}
}
}
#[derive(Debug)]
pub enum FswFilterType {
Include,
Exclude
}
impl From<FswFilterType> for ffi::fsw_filter_type {
fn from(filter_type: FswFilterType) -> ffi::fsw_filter_type {
match filter_type {
FswFilterType::Include => ffi::fsw_filter_type::filter_include,
FswFilterType::Exclude => ffi::fsw_filter_type::filter_exclude
}
}
}
#[derive(Debug)]
pub struct FswEvent {
pub path: String,
#[cfg(feature = "use_time")]
pub time: time::Tm,
#[cfg(not(feature = "use_time"))]
pub time: i64,
pub flags: Vec<FswEventFlag>
}
pub struct Fsw;
impl Fsw {
pub fn init_library() -> FswResult<()> {
let result = unsafe { ffi::fsw_init_library() };
FswSession::map_result((), result)
}
pub fn last_error() -> FswStatus {
let result = unsafe { ffi::fsw_last_error() };
result.into()
}
pub fn verbose() -> bool {
unsafe { ffi::fsw_is_verbose() }
}
pub fn set_verbose(verbose: bool) {
unsafe { ffi::fsw_set_verbose(verbose) };
}
}
#[derive(Debug)]
pub struct FswSessionBuilder {
paths: Vec<PathBuf>,
monitor_type: FswMonitorType,
properties: HashMap<String, String>,
overflow: Option<bool>,
latency: Option<c_double>,
recursive: Option<bool>,
directory_only: Option<bool>,
follow_symlinks: Option<bool>,
event_type_filters: Vec<FswEventFlag>,
filters: Vec<FswMonitorFilter>
}
impl FswSessionBuilder {
pub fn empty() -> Self {
FswSessionBuilder::create(None)
}
pub fn new<P>(paths: Vec<P>) -> Self
where P: AsRef<Path>
{
let paths = paths.iter().map(|x| x.as_ref().to_owned()).collect();
FswSessionBuilder::create(Some(paths))
}
fn create(paths: Option<Vec<PathBuf>>) -> Self {
FswSessionBuilder {
paths: paths.unwrap_or_else(Vec::new),
monitor_type: FswMonitorType::SystemDefault,
properties: Default::default(),
overflow: Default::default(),
latency: Default::default(),
recursive: Default::default(),
directory_only: Default::default(),
follow_symlinks: Default::default(),
event_type_filters: Default::default(),
filters: Default::default()
}
}
pub fn build(self) -> FswResult<FswSession> {
let session = FswSession::new(self.monitor_type)?;
for path in self.paths {
session.add_path(path)?;
}
for (name, value) in self.properties {
session.add_property(&name, &value)?;
}
if let Some(overflow) = self.overflow {
session.set_allow_overflow(overflow)?;
}
if let Some(latency) = self.latency {
session.set_latency(latency)?;
}
if let Some(recursive) = self.recursive {
session.set_recursive(recursive)?;
}
if let Some(directory_only) = self.directory_only {
session.set_directory_only(directory_only)?;
}
if let Some(follow_symlinks) = self.follow_symlinks {
session.set_follow_symlinks(follow_symlinks)?;
}
for event_type in self.event_type_filters {
session.add_event_type_filter(event_type)?;
}
for filter in self.filters {
session.add_filter(filter)?;
}
Ok(session)
}
pub fn build_callback<F>(self, callback: F) -> FswResult<FswSession>
where F: Fn(Vec<FswEvent>) + 'static
{
let session = self.build()?;
session.set_callback(callback)?;
Ok(session)
}
pub fn add_path<P>(mut self, path: P) -> Self
where P: AsRef<Path>
{
self.paths.push(path.as_ref().to_owned());
self
}
pub fn monitor(mut self, monitor: FswMonitorType) -> Self {
self.monitor_type = monitor;
self
}
pub fn property(mut self, name: &str, value: &str) -> Self {
self.properties.insert(name.to_owned(), value.to_owned());
self
}
pub fn overflow(mut self, overflow: Option<bool>) -> Self {
self.overflow = overflow;
self
}
pub fn latency(mut self, latency: Option<c_double>) -> Self {
self.latency = latency;
self
}
pub fn recursive(mut self, recursive: Option<bool>) -> Self {
self.recursive = recursive;
self
}
pub fn directory_only(mut self, directory_only: Option<bool>) -> Self {
self.directory_only = directory_only;
self
}
pub fn follow_symlinks(mut self, follow_symlinks: Option<bool>) -> Self {
self.follow_symlinks = follow_symlinks;
self
}
pub fn add_event_filter(mut self, filter_type: FswEventFlag) -> Self {
self.event_type_filters.push(filter_type);
self
}
pub fn add_filter(mut self, filter: FswMonitorFilter) -> Self {
self.filters.push(filter);
self
}
}
pub struct FswSession {
#[cfg(feature = "fswatch_1_10_0")]
handle: Unique<ffi::FSW_SESSION>,
#[cfg(not(feature = "fswatch_1_10_0"))]
handle: ffi::FSW_HANDLE,
callback_set: AtomicBool,
path_added: AtomicBool,
started: AtomicBool
}
impl FswSession {
pub fn new(monitor_type: FswMonitorType) -> FswResult<FswSession> {
let handle = unsafe { ffi::fsw_init_session(monitor_type.into()) };
if handle == ffi::FSW_INVALID_HANDLE {
return Err(FswError::FromFsw(FswStatus::UnknownError));
}
let struct_handle = {
#[cfg(feature = "fswatch_1_10_0")]
{ unsafe { Unique::new(handle) } }
#[cfg(not(feature = "fswatch_1_10_0"))]
{ handle }
};
Ok(FswSession {
handle: struct_handle,
callback_set: AtomicBool::new(false),
path_added: AtomicBool::new(false),
started: AtomicBool::new(false)
})
}
fn handle(&self) -> ffi::FSW_HANDLE {
#[cfg(feature = "fswatch_1_10_0")]
{ *self.handle }
#[cfg(not(feature = "fswatch_1_10_0"))]
{ self.handle }
}
pub fn default() -> FswResult<FswSession> {
FswSession::new(FswMonitorType::SystemDefault)
}
pub fn builder() -> FswSessionBuilder {
FswSessionBuilder::empty()
}
pub fn builder_paths<P>(paths: Vec<P>) -> FswSessionBuilder
where P: AsRef<Path>
{
FswSessionBuilder::new(paths)
}
fn map_result<T>(ret: T, result: ffi::FSW_STATUS) -> Result<T, FswError> {
let result: FswStatus = result.into();
match result {
FswStatus::Ok => Ok(ret),
_ => Err(FswError::FromFsw(result))
}
}
pub fn add_path<T: AsRef<Path>>(&self, path: T) -> FswResult<()> {
let path = path.as_ref().to_string_lossy().into_owned();
let c_path = CString::new(path).map_err(FswError::NulError)?;
let result = unsafe { ffi::fsw_add_path(self.handle(), c_path.as_ptr()) };
let res = FswSession::map_result((), result);
if res.is_ok() {
self.path_added.store(true, Ordering::Relaxed);
}
res
}
pub fn add_property(&self, name: &str, value: &str) -> FswResult<()> {
let c_name = CString::new(name).map_err(FswError::NulError)?;
let c_value = CString::new(value).map_err(FswError::NulError)?;
let result = unsafe { ffi::fsw_add_property(self.handle(), c_name.as_ptr(), c_value.as_ptr()) };
FswSession::map_result((), result)
}
pub fn set_allow_overflow(&self, allow_overflow: bool) -> FswResult<()> {
let result = unsafe { ffi::fsw_set_allow_overflow(self.handle(), allow_overflow) };
FswSession::map_result((), result)
}
extern fn callback_wrapper(events: *const ffi::fsw_cevent, event_num: c_uint, data: *mut c_void) {
let events: &[ffi::fsw_cevent] = unsafe { std::slice::from_raw_parts(events, event_num as usize) };
let mapped_events = events.iter()
.map(|x| {
let path = unsafe { CStr::from_ptr(x.path) }.to_string_lossy().to_string();
let flags = unsafe { std::slice::from_raw_parts(x.flags, x.flags_num as usize) };
let flags = flags.iter().map(Into::into).collect();
let time = {
#[cfg(feature = "use_time")]
{ time::at(time::Timespec::new(x.evt_time, 0)) }
#[cfg(not(feature = "use_time"))]
{ x.evt_time }
};
FswEvent {
path: path,
time: time,
flags: flags
}
})
.collect();
let closure: &Box<Fn(Vec<FswEvent>) + 'static> = unsafe { &*(data as *const Box<Fn(Vec<FswEvent>) + 'static>) };
closure(mapped_events);
}
pub fn set_callback<F>(&self, callback: F) -> FswResult<()>
where F: Fn(Vec<FswEvent>) + 'static
{
let cb: Box<Box<Fn(Vec<FswEvent>) + 'static>> = Box::new(Box::new(callback));
let raw = Box::into_raw(cb) as *mut _;
let result = unsafe { ffi::fsw_set_callback(self.handle(), FswSession::callback_wrapper, raw) };
let res = FswSession::map_result((), result);
if res.is_ok() {
self.callback_set.store(true, Ordering::Relaxed);
}
res
}
pub fn set_latency(&self, latency: c_double) -> FswResult<()> {
let result = unsafe { ffi::fsw_set_latency(self.handle(), latency) };
FswSession::map_result((), result)
}
pub fn set_recursive(&self, recursive: bool) -> FswResult<()> {
let result = unsafe { ffi::fsw_set_recursive(self.handle(), recursive) };
FswSession::map_result((), result)
}
pub fn set_directory_only(&self, directory_only: bool) -> FswResult<()> {
let result = unsafe { ffi::fsw_set_directory_only(self.handle(), directory_only) };
FswSession::map_result((), result)
}
pub fn set_follow_symlinks(&self, follow_symlinks: bool) -> FswResult<()> {
let result = unsafe { ffi::fsw_set_follow_symlinks(self.handle(), follow_symlinks) };
FswSession::map_result((), result)
}
pub fn add_event_type_filter(&self, event_type: FswEventFlag) -> FswResult<()> {
let filter = ffi::fsw_event_type_filter {
flag: event_type.into()
};
let result = unsafe { ffi::fsw_add_event_type_filter(self.handle(), filter) };
FswSession::map_result((), result)
}
pub fn add_filter(&self, filter: FswMonitorFilter) -> FswResult<()> {
let c_text = CString::new(filter.text).map_err(FswError::NulError)?;
let c_filter = ffi::fsw_cmonitor_filter {
text: c_text.as_ptr(),
filter_type: filter.filter_type.into(),
case_sensitive: filter.case_sensitive,
extended: filter.extended
};
let result = unsafe { ffi::fsw_add_filter(self.handle(), c_filter) };
FswSession::map_result((), result)
}
pub fn start_monitor(&self) -> FswResult<()> {
if !self.callback_set.load(Ordering::Relaxed) || !self.path_added.load(Ordering::Relaxed) {
return Err(FswError::MissingRequiredParameters);
}
self._start_monitor()
}
pub unsafe fn start_monitor_unchecked(&self) -> FswResult<()> {
self._start_monitor()
}
fn _start_monitor(&self) -> FswResult<()> {
let result = unsafe { ffi::fsw_start_monitor(self.handle()) };
let res = FswSession::map_result((), result);
if res.is_ok() {
self.started.store(true, Ordering::Relaxed);
}
res
}
#[cfg(feature = "fswatch_1_10_0")]
pub fn destroy_session(&self) -> FswResult<()> {
if self.started.load(Ordering::Relaxed) {
let result = unsafe { ffi::fsw_destroy_session(self.handle()) };
FswSession::map_result((), result)
} else {
Ok(())
}
}
#[cfg(not(feature = "fswatch_1_10_0"))]
pub fn destroy_session(&self) -> FswResult<()> {
let result = unsafe { ffi::fsw_destroy_session(self.handle()) };
FswSession::map_result((), result)
}
#[cfg(feature = "fswatch_1_10_0")]
pub fn stop_monitor(&self) -> FswResult<()> {
let result = unsafe { ffi::fsw_stop_monitor(self.handle()) };
FswSession::map_result((), result)
}
}
impl IntoIterator for FswSession {
type Item = FswEvent;
type IntoIter = FswSessionIterator;
fn into_iter(self) -> Self::IntoIter {
FswSessionIterator::assume_new(self)
}
}
impl Drop for FswSession {
fn drop(&mut self) {
let _ = self.destroy_session();
}
}
pub struct FswSessionIterator {
session: Option<FswSession>,
rx: Receiver<FswEvent>,
started: bool
}
impl FswSessionIterator {
pub fn new(session: FswSession) -> FswResult<Self> {
let (tx, rx) = channel();
FswSessionIterator::adapt_session(&session, tx)?;
Ok(FswSessionIterator::create(session, rx))
}
fn assume_new(session: FswSession) -> Self {
let (tx, rx) = channel();
let _ = FswSessionIterator::adapt_session(&session, tx);
FswSessionIterator::create(session, rx)
}
fn create(session: FswSession, rx: Receiver<FswEvent>) -> Self {
FswSessionIterator {
session: Some(session),
rx: rx,
started: false
}
}
fn adapt_session(session: &FswSession, tx: Sender<FswEvent>) -> FswResult<()> {
session.set_callback(move |events| {
for event in events {
tx.send(event).unwrap();
}
})
}
fn start(&mut self) {
let session = match self.session.take() {
Some(s) => s,
None => return
};
self.started = true;
std::thread::spawn(move || {
session.start_monitor().unwrap();
});
}
}
impl Iterator for FswSessionIterator {
type Item = FswEvent;
fn next(&mut self) -> Option<Self::Item> {
if !self.started {
self.start();
}
self.rx.recv().ok()
}
}