use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::{Context, Result};
use config::Config;
use ipc::SessionState;
use store::Store;
use crate::cli::DaemonCmd;
use crate::commands::{broker, load, open_store};
use crate::output;
pub(crate) async fn dispatch(
cmd: DaemonCmd,
device: Option<&str>,
config_path: Option<PathBuf>,
) -> Result<Option<String>> {
match cmd {
DaemonCmd::Start { foreground: true } => {
let cfg = load(config_path)?;
let db = open_store(&cfg).await?;
start_foreground(cfg, device.map(str::to_owned), db).await?;
Ok(None)
}
DaemonCmd::Start { foreground: false } => {
let cfg = load(config_path.clone())?;
Ok(Some(start_background(&cfg, device, config_path).await?))
}
DaemonCmd::Stop => {
let cfg = load(config_path)?;
Ok(Some(stop(&cfg, device).await?))
}
DaemonCmd::Status => {
let cfg = load(config_path)?;
Ok(Some(broker::run_status(&cfg, device, "daemon").await?))
}
DaemonCmd::Install { system } => {
let addr = match device {
Some(d) => d.to_owned(),
None => default_device(config_path.clone(), system)?,
};
Ok(Some(install(&addr, config_path.as_deref(), system)?))
}
DaemonCmd::Uninstall { system } => Ok(Some(uninstall(system)?)),
}
}
fn default_device(config_path: Option<PathBuf>, system: bool) -> Result<String> {
if system && config_path.is_none() {
let home = service::invoking_home().context("resolving config for --system install")?;
let explicit = home.join(".config/imsg/imsg.toml");
return Ok(load(Some(explicit))?.device.address().to_owned());
}
Ok(load(config_path)?.device.address().to_owned())
}
async fn start_foreground(cfg: Config, device: Option<String>, store: Store) -> Result<()> {
store.set_meta("daemon_enabled", "true").await?;
output::line("daemon starting — Ctrl-C to stop")?;
let addr = device.clone().unwrap_or_else(|| cfg.device.address().to_owned());
tokio::spawn(announce_when_connected(addr));
imsg_broker::run_daemon(cfg, device, store).await
}
async fn announce_when_connected(addr: String) {
loop {
match broker::query_state(&addr).await {
Some(SessionState::Active) => {
let _ = output::line(&format!("daemon for {addr}: connected"));
return;
}
Some(SessionState::Failed) => return,
_ => {}
}
tokio::time::sleep(Duration::from_millis(250)).await;
}
}
async fn start_background(
cfg: &Config,
device: Option<&str>,
config_path: Option<PathBuf>,
) -> Result<String> {
let addr = broker::resolve_addr(cfg, device).to_owned();
match broker::query_persistent(&addr).await {
Some(true) => return Ok(format!("daemon for {addr}: already running")),
Some(false) => {
return Err(anyhow::anyhow!(
"an ephemeral broker for {addr} is already using this socket — wait for it to \
idle out (or check `imsg broker status`) before starting the daemon"
));
}
None => {}
}
let log_path = config::daemon_log_path(&addr);
let mut child = imsg_proc::respawn_self(
&["daemon", "start", "--foreground"],
&addr,
config_path.as_deref(),
&log_path,
true,
true,
)
.await?;
broker::connect_retry(
&addr,
&mut child,
&log_path,
cfg.broker.readiness_wait(),
cfg.broker.readiness_poll(),
)
.await?;
Ok(format!("daemon for {addr}: started (log: {})", log_path.display()))
}
async fn stop(cfg: &Config, device: Option<&str>) -> Result<String> {
broker::run_stop(cfg, device).await
}
const fn level(system: bool) -> service::ServiceLevel {
if system {
service::ServiceLevel::System
} else {
service::ServiceLevel::User
}
}
fn install(addr: &str, config_path: Option<&Path>, system: bool) -> Result<String> {
let lvl = level(system);
service::install(Some(addr), config_path, lvl).context("installing daemon service")?;
Ok(format!("daemon service installed ({lvl:?})"))
}
fn uninstall(system: bool) -> Result<String> {
let lvl = level(system);
let outcome = service::uninstall(lvl).context("uninstalling daemon service")?;
Ok(uninstall_message(outcome, lvl))
}
fn uninstall_message(outcome: service::UninstallOutcome, lvl: service::ServiceLevel) -> String {
match outcome {
service::UninstallOutcome::Uninstalled => format!("daemon service uninstalled ({lvl:?})"),
service::UninstallOutcome::NotInstalled => {
format!("no daemon service installed ({lvl:?})")
}
}
}
#[cfg(test)]
mod tests;