///|
/// FSEvents backend (macOS). On non-macOS the underlying FFI returns 0 from
/// `start`, so callers fall through to the polling backend.
#borrow(paths)
extern "C" fn fsevents_start_ffi(
paths : Bytes,
total_len : Int,
num_paths : Int,
latency_ms : Double,
) -> Int64 = "mizchi_x_watch_fsevents_start"
///|
#borrow(out_buf)
extern "C" fn fsevents_pop_ffi(
handle : Int64,
out_buf : FixedArray[Byte],
out_cap : Int,
) -> Int = "mizchi_x_watch_fsevents_pop"
///|
extern "C" fn fsevents_close_ffi(handle : Int64) -> Unit = "mizchi_x_watch_fsevents_close"
///|
priv struct FSEventsState {
handle : Int64
interval_ms : Int
norm : NormalizeState
}
///|
/// Returns `None` on non-Apple platforms or if FSEvents framework is
/// unreachable for some reason (dlopen failure, etc).
async fn fsevents_start(
roots : Array[String],
interval_ms : Int,
exclude : (String) -> Bool,
latency_ms : Double,
) -> FSEventsState? {
// Join roots as null-byte-separated UTF-8 buffer.
let buf = StringBuilder::new()
for root in roots {
buf.write_string(root)
buf.write_char('\u{00}')
}
let joined = @utf8.encode(buf.to_string())
let handle = fsevents_start_ffi(
joined,
joined.length(),
roots.length(),
latency_ms,
)
if handle == 0 {
return None
}
let norm = build_normalize_state(roots, exclude)
Some(FSEventsState::{ handle, interval_ms, norm })
}
///|
async fn fsevents_drain(state : FSEventsState) -> Array[Event] {
let events : Array[Event] = []
let cap = 4096
let buf : FixedArray[Byte] = FixedArray::make(cap, b'\x00')
for ;; {
let path_len = fsevents_pop_ffi(state.handle, buf, cap)
if path_len == -2 {
break
}
if path_len < 0 {
continue
}
let path_bytes = FixedArray::make(path_len, b'\x00')
if path_len > 0 {
FixedArray::unsafe_blit(path_bytes, 0, buf, 4, path_len)
}
let raw_path = @utf8.decode_lossy(Bytes::from_array(path_bytes))
match classify_event(state.norm, raw_path) {
Some(e) => events.push(e)
None => ()
}
}
events
}
///|
async fn fsevents_next(state : FSEventsState) -> Array[Event] {
@async.sleep(state.interval_ms)
fsevents_drain(state)
}
///|
fn fsevents_close(state : FSEventsState) -> Unit {
fsevents_close_ffi(state.handle)
}