use std::{process::Stdio, time::Duration}; use serde::{Deserialize, Serialize}; use tokio::io::{AsyncBufReadExt, BufReader}; use crate::status::Sender; use super::IntoService; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Exited with status code: {code}\n{stderr}")] ExitCode { code: i32, stderr: String }, #[error("Process terminated by signal")] Signal, #[error("Serialization error: {0}")] Serialization(#[from] serde_json::error::Error), #[error("{0}")] Stderr(String), #[error("{0}")] Output(String), #[error("Exited with status code: {0}")] PersistExitCode(i32), #[error("Failed to get stderr of child process")] NoStderr, #[error("Failed to get stdout of child process")] NoStdout, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Command { pub command: String, pub args: Vec, #[serde(default)] pub persist: bool, #[serde(default = "super::default_interval")] pub interval: Duration, } impl Command { async fn persist(&self, tx: Sender) -> Result<(), Error> { let mut command = tokio::process::Command::new(&self.command); command.args(&self.args); let mut child = command .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let mut lines = BufReader::new(child.stdout.take().ok_or(Error::NoStdout)?).lines(); while let Some(line) = lines.next_line().await? { let res = ("Ok" == line).then_some(()).ok_or(Error::Output(line)); tx.send_if_modified(|s| s.update(res.into())); } match child.wait().await?.code() { Some(0) => Ok(()), Some(code) => Err(Error::PersistExitCode(code)), None => Err(Error::Signal), } } async fn interval(&self) -> Result<(), Error> { let mut command = tokio::process::Command::new(&self.command); command.args(&self.args); let output = command.output().await?; match output.status.code() { Some(0) => Ok(()), Some(code) => { let stderr = String::from_utf8_lossy(&output.stderr).to_string(); Err(Error::ExitCode { code, stderr }) } None => Err(Error::Signal), } } } impl IntoService for Command { async fn into_service(self, tx: Sender) { let mut interval = tokio::time::interval(self.interval); loop { interval.tick().await; let res = if self.persist { self.persist(tx.clone()).await } else { self.interval().await }; tx.send_if_modified(|s| s.update(res.into())); } } }