openlatch-client 0.1.8

The open-source security layer for AI agents — client forwarder
use std::path::Path;
use std::time::Duration;

use notify_debouncer_mini::{new_debouncer, DebounceEventResult, DebouncedEventKind};
use tokio::sync::mpsc;

use super::reconciler::request::ReconcileRequest;

pub fn spawn_watcher(
    settings_path: &Path,
    tx: mpsc::Sender<ReconcileRequest>,
) -> Result<notify_debouncer_mini::Debouncer<notify::RecommendedWatcher>, notify::Error> {
    let dir = settings_path
        .parent()
        .expect("settings.json has a parent directory")
        .to_path_buf();
    let target_name = settings_path
        .file_name()
        .expect("settings.json has a filename")
        .to_os_string();

    let mut debouncer = new_debouncer(Duration::from_secs(2), move |res: DebounceEventResult| {
        if let Ok(events) = res {
            let hit = events.iter().any(|e| {
                e.path.file_name() == Some(&target_name)
                    || e.kind == DebouncedEventKind::AnyContinuous
            });
            if hit {
                let _ = tx.blocking_send(ReconcileRequest::Fs);
            }
        }
    })?;

    debouncer
        .watcher()
        .watch(&dir, notify::RecursiveMode::NonRecursive)?;

    Ok(debouncer)
}

pub fn spawn_poll_fallback(tx: mpsc::Sender<ReconcileRequest>) -> tokio::task::JoinHandle<()> {
    tokio::spawn(async move {
        let mut interval = tokio::time::interval(Duration::from_secs(30));
        interval.tick().await;

        loop {
            interval.tick().await;
            if tx.send(ReconcileRequest::Poll).await.is_err() {
                break;
            }
        }
    })
}