async_file_watcher_raw/
async_file_watcher_raw.rs1use std::{
2 ffi::CStr,
3 fs,
4 os::raw::{c_uint, c_void},
5 path::PathBuf,
6 time::{Duration, SystemTime},
7};
8
9use wolfram_library_link::{
10 self as wll, rtl,
11 sys::{self, mint, MArgument, LIBRARY_FUNCTION_ERROR, LIBRARY_NO_ERROR},
12};
13
14struct FileWatcherArgs {
15 pause_interval_ms: u64,
16 path: PathBuf,
17}
18
19#[no_mangle]
23pub extern "C" fn start_file_watcher(
24 lib_data: sys::WolframLibraryData,
25 arg_count: mint,
26 args: *mut MArgument,
27 res: MArgument,
28) -> c_uint {
29 let args = unsafe { std::slice::from_raw_parts(args, arg_count as usize) };
30
31 if args.len() != 2 {
32 return LIBRARY_FUNCTION_ERROR;
33 }
34
35 if unsafe { wll::initialize(lib_data) }.is_err() {
36 return LIBRARY_FUNCTION_ERROR;
37 }
38
39 let task_arg = unsafe {
40 FileWatcherArgs {
41 pause_interval_ms: u64::try_from(*args[0].integer)
42 .expect("i64 interval overflows u64"),
43 path: {
44 let cstr = CStr::from_ptr(*args[1].utf8string);
45 match cstr.to_str() {
46 Ok(s) => PathBuf::from(s),
47 Err(_) => return LIBRARY_FUNCTION_ERROR,
48 }
49 },
50 }
51 };
52
53 let task_arg = Box::into_raw(Box::new(task_arg)) as *mut c_void;
55
56 unsafe {
59 let task_id: mint = wll::rtl::createAsynchronousTaskWithThread(
60 Some(file_watch_thread_function),
61 task_arg,
62 );
63 *res.integer = task_id;
64 }
65
66 LIBRARY_NO_ERROR
67}
68
69extern "C" fn file_watch_thread_function(async_object_id: mint, task_arg: *mut c_void) {
71 let task_arg = task_arg as *mut FileWatcherArgs;
72 let task_arg: &FileWatcherArgs = unsafe { &*task_arg };
73
74 let FileWatcherArgs {
75 pause_interval_ms,
76 ref path,
77 } = *task_arg;
78
79 let mut prev_changed: Option<SystemTime> = fs::metadata(path)
80 .and_then(|metadata| metadata.modified())
81 .ok();
82
83 let mut check_for_modification = || -> Option<_> {
88 let changed: Option<fs::Metadata> = fs::metadata(path).ok();
89
90 let notify: Option<SystemTime> = match (&prev_changed, changed) {
91 (Some(prev), Some(latest)) => {
92 let latest: SystemTime = match latest.modified() {
93 Ok(latest) => latest,
94 Err(_) => return None,
95 };
96
97 if *prev != latest {
98 prev_changed = Some(latest.clone());
99 Some(latest)
100 } else {
101 None
102 }
103 },
104 (Some(_prev), None) => None,
106 (None, Some(latest)) => latest.modified().ok(),
107 (None, None) => None,
108 };
109
110 let time = notify?;
111
112 let since_epoch = match time.duration_since(std::time::UNIX_EPOCH) {
113 Ok(duration) => duration,
114 Err(_) => return None,
115 };
116
117 let since_epoch = since_epoch.as_secs();
118
119 Some(since_epoch)
120 };
121
122 loop {
123 if unsafe { rtl::asynchronousTaskAliveQ(async_object_id) } == 0 {
124 break;
125 }
126
127 if let Some(modification) = check_for_modification() {
130 unsafe {
131 let data_store: sys::DataStore = rtl::createDataStore();
132 rtl::DataStore_addInteger(data_store, modification as i64);
133
134 rtl::raiseAsyncEvent(
135 async_object_id,
136 "change\0".as_ptr() as *mut _,
137 data_store,
138 )
139 }
140 }
141
142 std::thread::sleep(Duration::from_millis(pause_interval_ms));
144 }
145}