unifier-cli 0.2.0

Filesystem postbox for inter-process communication via a Unix tree
Documentation
//! Hot in-memory daemon lifecycle and client dispatch.

#[cfg(unix)]
mod client;
#[cfg(unix)]
mod notify;
#[cfg(unix)]
mod paths;
#[cfg(unix)]
mod protocol;
#[cfg(unix)]
mod server;

#[cfg(unix)]
pub use client::{
    flush as client_flush, ping, read_pid, response_found, response_messages, response_ok,
    response_uuid, response_value, shutdown, Client,
};
#[cfg(unix)]
pub use notify::{subscribe, watch, EventHub, Notice};
#[cfg(unix)]
pub use protocol::{ok_empty, Request, Response};
#[cfg(unix)]
pub use paths::{daemon_dir, events_socket_path, pid_path, socket_path};
#[cfg(unix)]
pub use server::run as run_server;

use std::process::{Command, Stdio};

use crate::error::{Error, Result};
use crate::home::UnifierHome;

/// Start the daemon if it is not already running (Gradle-style invocation).
pub fn ensure_running(home: &UnifierHome) -> Result<()> {
    if is_running(home) {
        return Ok(());
    }
    start(home, false)
}

/// Whether a hot daemon is running for this store root.
pub fn is_running(home: &UnifierHome) -> bool {
    #[cfg(unix)]
    {
        Client::is_running(home)
    }
    #[cfg(not(unix))]
    {
        let _ = home;
        false
    }
}

/// Start the hot daemon. With `foreground`, blocks in the current process (for tests).
pub fn start(home: &UnifierHome, foreground: bool) -> Result<()> {
    #[cfg(not(unix))]
    {
        let _ = (home, foreground);
        return Err(Error::msg("hot daemon requires a Unix platform"));
    }

    #[cfg(unix)]
    {
        if Client::is_running(home) {
            return Err(Error::msg("daemon is already running"));
        }

        home.ensure()?;
        std::fs::create_dir_all(crate::daemon::paths::daemon_dir(home))?;

        if foreground {
            return run_server(home.clone());
        }

        let exe = std::env::current_exe()?;
        let mut cmd = Command::new(exe);
        cmd.arg("daemon").arg("run");
        if let Some(p) = home.global_path().to_str() {
            cmd.args(["--home", p]);
        }
        if let Some(name) = home.chroot_name() {
            cmd.args(["--chroot", name]);
        }
        cmd.stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::null());

        let child = cmd.spawn()?;
        wait_for_socket(home, child.id())?;
        Ok(())
    }
}

/// Stop the daemon, flushing dirty state first.
pub fn stop(home: &UnifierHome) -> Result<()> {
    #[cfg(not(unix))]
    {
        let _ = home;
        return Err(Error::msg("hot daemon requires a Unix platform"));
    }

    #[cfg(unix)]
    {
        shutdown(home)
    }
}

/// Print daemon status to stdout.
pub fn status(home: &UnifierHome) -> Result<()> {
    #[cfg(not(unix))]
    {
        let _ = home;
        println!("daemon: unavailable (requires Unix)");
        return Ok(());
    }

    #[cfg(unix)]
    {
        if Client::is_running(home) {
            let pid = read_pid(home)?.unwrap_or(0);
            println!("daemon: running (pid {pid})");
            println!("socket: {}", socket_path(home).display());
            println!("events: {}", events_socket_path(home).display());
        } else {
            println!("daemon: stopped");
        }
        Ok(())
    }
}

/// Flush dirty in-memory state to disk via the running daemon.
pub fn flush(home: &UnifierHome) -> Result<()> {
    #[cfg(not(unix))]
    {
        let _ = home;
        return Err(Error::msg("hot daemon requires a Unix platform"));
    }

    #[cfg(unix)]
    {
        let dirty = client_flush(home)?;
        if dirty {
            println!("flushed dirty state to disk");
        } else {
            println!("nothing to flush");
        }
        Ok(())
    }
}

#[cfg(unix)]
fn wait_for_socket(home: &UnifierHome, _pid: u32) -> Result<()> {
    let path = socket_path(home);
    for _ in 0..100 {
        if path.exists() && Client::is_running(home) {
            return ping(home);
        }
        std::thread::sleep(std::time::Duration::from_millis(50));
    }
    Err(Error::msg("daemon failed to start"))
}