use async_channel::Sender;
use inotify::{EventMask, Inotify, WatchMask};
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
pub const SENDER_CHANNEL_ERROR: &str = "SENDER_CHANNEL_CLOSED";
pub type FsSender = Sender<WatcherOutcome>;
#[derive(Debug)]
pub struct FsWatcher {
path: Option<PathBuf>, sender: FsSender,
}
impl FsWatcher {
pub fn new(sender: FsSender) -> Self {
Self {
sender,
path: Option::default(),
}
}
pub fn path(mut self, path: impl AsRef<Path>) -> Self {
self.path.replace(path.as_ref().to_path_buf());
self
}
pub async fn watch(self, watch_for: WatchMask) -> futures_lite::io::Result<()> {
if let Some(path) = self.path {
let mut inotify = Inotify::init()?;
inotify.watches().add(&path, watch_for)?;
let mut buffer = [0u8; 4096];
loop {
let events = inotify.read_events_blocking(&mut buffer)?;
for event in events {
let outcome: WatcherOutcome = event.into();
if self.sender.clone().send(outcome).await.is_err() {
return Err(futures_lite::io::Error::new(
futures_lite::io::ErrorKind::Other,
SENDER_CHANNEL_ERROR,
));
}
}
}
} else {
Err(futures_lite::io::Error::new(
futures_lite::io::ErrorKind::NotFound,
"The path was not found, maybe you didn't specify it",
))
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub enum WatcherEvents {
Access,
Attrib,
CloseWrite,
CloseNoWrite,
Create,
Delete,
DeleteSelf,
Modify,
MoveSelf,
MovedFrom,
MovedTo,
Open,
Ignored,
IsDir,
QueueOverflow,
Unmount,
Unsupported,
}
impl From<EventMask> for WatcherEvents {
fn from(value: EventMask) -> Self {
match value {
EventMask::ACCESS => Self::Access,
EventMask::ATTRIB => Self::Attrib,
EventMask::CLOSE_WRITE => Self::CloseWrite,
EventMask::CLOSE_NOWRITE => Self::CloseNoWrite,
EventMask::CREATE => Self::Create,
EventMask::DELETE => Self::Delete,
EventMask::DELETE_SELF => Self::DeleteSelf,
EventMask::MODIFY => Self::Modify,
EventMask::MOVE_SELF => Self::MoveSelf,
EventMask::MOVED_FROM => Self::MovedFrom,
EventMask::MOVED_TO => Self::MovedTo,
EventMask::OPEN => Self::Open,
EventMask::IGNORED => Self::Ignored,
EventMask::ISDIR => Self::IsDir,
EventMask::Q_OVERFLOW => Self::QueueOverflow,
EventMask::UNMOUNT => Self::Unmount,
_ => Self::Unsupported,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct WatcherOutcome {
pub descriptor: i32,
pub mask: WatcherEvents,
pub cookie: u32,
pub name: Option<String>,
}
impl From<inotify::Event<&OsStr>> for WatcherOutcome {
fn from(event: inotify::Event<&OsStr>) -> Self {
let name = event
.name
.map(|inner_name| inner_name.to_string_lossy().to_string());
Self {
descriptor: event.wd.get_watch_descriptor_id(),
mask: event.mask.into(),
cookie: event.cookie,
name,
}
}
}