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)] pub 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}