wtr_watcher/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4include!(concat!(env!("OUT_DIR"), "/watcher_c.rs"));
5use core::ffi::c_char;
6use core::ffi::c_void;
7use core::ffi::CStr;
8use core::mem::transmute;
9use core::pin::Pin;
10use core::str;
11use core::task::Context;
12use core::task::Poll;
13use futures::channel::mpsc::unbounded as async_channel;
14use futures::channel::mpsc::UnboundedReceiver as Rx;
15use futures::channel::mpsc::UnboundedSender as Tx;
16use futures::Stream;
17use serde::Deserialize;
18use serde::Serialize;
19use std::ffi::CString;
20
21#[derive(Debug, Clone, Copy)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
24pub enum EffectType {
25    Rename,
26    Modify,
27    Create,
28    Destroy,
29    Owner,
30    Other,
31}
32
33#[derive(Debug, Clone, Copy)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
36pub enum PathType {
37    Dir,
38    File,
39    HardLink,
40    SymLink,
41    Watcher,
42    Other,
43}
44
45#[derive(Debug, Clone)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[cfg_attr(feature = "serde", serde(rename = "event"))]
48pub struct Event {
49    pub effect_time: i64,
50    pub path_name: String,
51    pub associated_path_name: Option<String>,
52    pub effect_type: EffectType,
53    pub path_type: PathType,
54}
55
56fn c_chars_as_str<'a>(ptr: *const c_char) -> Option<&'a str> {
57    match ptr.is_null() {
58        true => None,
59        false => {
60            let b = unsafe { CStr::from_ptr(ptr).to_bytes() };
61            let s = str::from_utf8(b).unwrap_or_default();
62            Some(s)
63        }
64    }
65}
66
67fn effect_type_from_c(effect_type: i8) -> EffectType {
68    use EffectType::*;
69    match effect_type {
70        WTR_WATCHER_EFFECT_RENAME => Rename,
71        WTR_WATCHER_EFFECT_MODIFY => Modify,
72        WTR_WATCHER_EFFECT_CREATE => Create,
73        WTR_WATCHER_EFFECT_DESTROY => Destroy,
74        WTR_WATCHER_EFFECT_OWNER => Owner,
75        WTR_WATCHER_EFFECT_OTHER => Other,
76        _ => Other,
77    }
78}
79
80fn path_type_from_c(path_type: i8) -> PathType {
81    use PathType::*;
82    match path_type {
83        WTR_WATCHER_PATH_DIR => Dir,
84        WTR_WATCHER_PATH_FILE => File,
85        WTR_WATCHER_PATH_HARD_LINK => HardLink,
86        WTR_WATCHER_PATH_SYM_LINK => SymLink,
87        WTR_WATCHER_PATH_WATCHER => Watcher,
88        WTR_WATCHER_PATH_OTHER => Other,
89        _ => Other,
90    }
91}
92
93fn ev_from_c<'a>(ev: wtr_watcher_event) -> Event {
94    Event {
95        effect_time: ev.effect_time,
96        path_name: c_chars_as_str(ev.path_name).unwrap_or_default().to_string(),
97        associated_path_name: c_chars_as_str(ev.associated_path_name).map(str::to_string),
98        effect_type: effect_type_from_c(ev.effect_type),
99        path_type: path_type_from_c(ev.path_type),
100    }
101}
102
103unsafe extern "C" fn fwd_ev(ev: wtr_watcher_event, cx: *mut c_void) {
104    let ev = ev_from_c(ev);
105    let tx = transmute::<*mut c_void, &Tx<Event>>(cx);
106    let _ = tx.unbounded_send(ev);
107}
108
109#[allow(dead_code)] // tx is used in fwd_ev
110pub struct Watch {
111    w: *mut c_void,
112    tx: Box<Tx<Event>>,
113    rx: Pin<Box<Rx<Event>>>,
114}
115
116impl Watch {
117    pub fn try_new(path: &str) -> Result<Watch, &'static str> {
118        let p = CString::new(path).unwrap();
119        let (tx, rx) = async_channel();
120        let tx = Box::new(tx);
121        let rx = Box::pin(rx);
122        let cx = unsafe { transmute::<&Tx<Event>, *mut c_void>(&tx) };
123        match unsafe { wtr_watcher_open(p.as_ptr(), Some(fwd_ev), cx) } {
124            w if w.is_null() => Err("wtr_watcher_open"),
125            w => Ok(Watch { w, tx, rx }),
126        }
127    }
128
129    pub fn close(&self) -> Result<(), &'static str> {
130        match unsafe { wtr_watcher_close(self.w) } {
131            false => Err("wtr_watcher_close"),
132            true => Ok(()),
133        }
134    }
135}
136
137impl Stream for Watch {
138    type Item = Event;
139
140    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Event>> {
141        self.rx.as_mut().poll_next(cx)
142    }
143}
144
145impl Drop for Watch {
146    fn drop(&mut self) {
147        let _ = self.close();
148    }
149}