///|
/// ReadDirectoryChangesW backend (Windows). On non-Windows the underlying
/// FFI returns 0 from `start`, so callers fall through to the polling backend.
#borrow(paths)
extern "C" fn rdcw_start_ffi(
paths : Bytes,
total_len : Int,
num_paths : Int,
) -> Int64 = "mizchi_x_watch_rdcw_start"
///|
#borrow(out_buf)
extern "C" fn rdcw_pop_ffi(
handle : Int64,
out_buf : FixedArray[Byte],
out_cap : Int,
) -> Int = "mizchi_x_watch_rdcw_pop"
///|
extern "C" fn rdcw_close_ffi(handle : Int64) -> Unit = "mizchi_x_watch_rdcw_close"
///|
priv struct RDCWState {
handle : Int64
interval_ms : Int
norm : NormalizeState
}
///|
/// Returns `None` on non-Windows platforms or if any of the roots can't be
/// opened with `FILE_LIST_DIRECTORY`.
async fn rdcw_start(
roots : Array[String],
interval_ms : Int,
exclude : (String) -> Bool,
) -> RDCWState? {
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 = rdcw_start_ffi(joined, joined.length(), roots.length())
if handle == 0 {
return None
}
let norm = build_normalize_state(roots, exclude)
Some(RDCWState::{ handle, interval_ms, norm })
}
///|
async fn rdcw_drain(state : RDCWState) -> Array[Event] {
let events : Array[Event] = []
let cap = 4096
let buf : FixedArray[Byte] = FixedArray::make(cap, b'\x00')
for ;; {
let path_len = rdcw_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 rdcw_next(state : RDCWState) -> Array[Event] {
@async.sleep(state.interval_ms)
rdcw_drain(state)
}
///|
fn rdcw_close(state : RDCWState) -> Unit {
rdcw_close_ffi(state.handle)
}