regy 0.1.0

Private-by-default desktop agent for the Regy web interface
use std::{path::Path, time::Duration};

use tokio::task::JoinHandle;

use crate::{
    domain::{
        errors::AgentResult,
        pi_rpc::{PiRpcCommand, PiRpcResponse},
    },
    infrastructure::pi_rpc_process::{PiProcessOptions, PiRpcProcess, PiSessionMode},
};

#[derive(Clone)]
pub(crate) struct PiRpcProbe {
    command: Vec<String>,
    request_timeout: Duration,
}

impl PiRpcProbe {
    pub(crate) fn new(command: Vec<String>, request_timeout: Duration) -> Self {
        Self {
            command,
            request_timeout,
        }
    }

    pub(crate) fn is_configured(&self) -> bool {
        !self.command.is_empty()
    }

    pub(crate) async fn request(
        &self,
        cwd: &Path,
        command: PiRpcCommand,
    ) -> AgentResult<PiRpcResponse> {
        let spawned = PiRpcProcess::spawn(PiProcessOptions {
            command: self.command.clone(),
            cwd: cwd.to_path_buf(),
            session: PiSessionMode::Ephemeral,
        })
        .await?;
        let process = spawned.process;
        let mut events = spawned.events;
        let mut drain = EventDrainGuard::new(tokio::spawn(async move {
            while events.recv().await.is_some() {}
        }));
        let response = process.request(command, self.request_timeout).await;
        let shutdown = process.shutdown().await;
        drain.finish().await;

        let response = response?;
        shutdown?;
        Ok(response)
    }
}

struct EventDrainGuard {
    task: Option<JoinHandle<()>>,
}

impl EventDrainGuard {
    fn new(task: JoinHandle<()>) -> Self {
        Self { task: Some(task) }
    }

    async fn finish(&mut self) {
        if let Some(task) = self.task.take() {
            let _ = task.await;
        }
    }
}

impl Drop for EventDrainGuard {
    fn drop(&mut self) {
        if let Some(task) = self.task.take() {
            task.abort();
        }
    }
}