use super::Supervisor;
use crate::Result;
use crate::daemon_id::DaemonId;
use crate::ipc::IpcResponse;
use crate::pitchfork_toml::PitchforkToml;
use crate::settings::settings;
use log::LevelFilter::Info;
use std::path::Path;
use tokio::time;
impl Supervisor {
pub(crate) async fn leave_dir(&self, dir: &Path) -> Result<()> {
debug!("left dir {}", dir.display());
let shell_dirs = self.get_dirs_with_shell_pids().await;
let shell_dirs = shell_dirs.keys().collect::<Vec<_>>();
let autostop_delay = settings().general_autostop_delay();
for daemon in self.active_daemons().await {
if !daemon.autostop {
continue;
}
if let Some(daemon_dir) = daemon.dir.as_ref()
&& daemon_dir.starts_with(dir)
&& !shell_dirs.iter().any(|d| d.starts_with(daemon_dir))
{
if autostop_delay.is_zero() {
info!("autostopping {daemon}");
self.stop(&daemon.id).await?;
self.add_notification(Info, format!("autostopped {daemon}"))
.await;
} else {
let stop_at = time::Instant::now() + autostop_delay;
let mut pending = self.pending_autostops.lock().await;
if !pending.contains_key(&daemon.id) {
info!(
"scheduling autostop for {} in {:?}",
daemon.id, autostop_delay
);
pending.insert(daemon.id.clone(), stop_at);
}
}
}
}
Ok(())
}
pub(crate) async fn cancel_pending_autostops_for_dir(&self, dir: &Path) {
let mut pending = self.pending_autostops.lock().await;
let daemons_to_cancel: Vec<DaemonId> = {
let state_file = self.state_file.lock().await;
state_file
.daemons
.iter()
.filter(|(_id, d)| {
d.dir.as_ref().is_some_and(|daemon_dir| {
dir.starts_with(daemon_dir) || daemon_dir.starts_with(dir)
})
})
.map(|(id, _)| id.clone())
.collect()
};
for daemon_id in daemons_to_cancel {
if pending.remove(&daemon_id).is_some() {
info!("cancelled pending autostop for {daemon_id}");
}
}
}
pub(crate) async fn process_pending_autostops(&self) -> Result<()> {
let now = time::Instant::now();
let to_stop: Vec<DaemonId> = {
let pending = self.pending_autostops.lock().await;
pending
.iter()
.filter(|(_, stop_at)| now >= **stop_at)
.map(|(id, _)| id.clone())
.collect()
};
for daemon_id in to_stop {
{
let mut pending = self.pending_autostops.lock().await;
pending.remove(&daemon_id);
}
if let Some(daemon) = self.get_daemon(&daemon_id).await
&& daemon.autostop
&& daemon.status.is_running()
{
let shell_dirs = self.get_dirs_with_shell_pids().await;
let shell_dirs = shell_dirs.keys().collect::<Vec<_>>();
if let Some(daemon_dir) = daemon.dir.as_ref()
&& !shell_dirs.iter().any(|d| d.starts_with(daemon_dir))
{
info!("autostopping {daemon_id} (after delay)");
self.stop(&daemon_id).await?;
self.add_notification(Info, format!("autostopped {daemon_id}"))
.await;
}
}
}
Ok(())
}
pub(crate) async fn start_boot_daemons(&self) -> Result<()> {
info!("Scanning for boot_start daemons");
let pt = PitchforkToml::all_merged()?;
let boot_daemons: Vec<_> = pt
.daemons
.iter()
.filter(|(_id, d)| d.boot_start.unwrap_or(false))
.collect();
if boot_daemons.is_empty() {
info!("No daemons configured with boot_start = true");
return Ok(());
}
info!("Found {} daemon(s) to start at boot", boot_daemons.len());
for (id, daemon) in boot_daemons {
info!("Starting boot daemon: {id}");
let cmd = match shell_words::split(&daemon.run) {
Ok(cmd) => cmd,
Err(e) => {
error!("failed to parse command for boot daemon {id}: {e}");
continue;
}
};
let mut run_opts = daemon.to_run_options(id, cmd);
run_opts.autostop = false; run_opts.wait_ready = false;
match self.run(run_opts).await {
Ok(IpcResponse::DaemonStart { .. }) | Ok(IpcResponse::DaemonReady { .. }) => {
info!("Successfully started boot daemon: {id}");
}
Ok(IpcResponse::DaemonAlreadyRunning) => {
info!("Boot daemon already running: {id}");
}
Ok(other) => {
warn!("Unexpected response when starting boot daemon {id}: {other:?}");
}
Err(e) => {
error!("Failed to start boot daemon {id}: {e}");
}
}
}
Ok(())
}
}