ntfy-run/src/runner.rs

88 lines
2.6 KiB
Rust
Raw Normal View History

use crate::tap_stream::{ReadOrWrite, TapStream};
2024-10-13 23:50:36 -04:00
use std::process::{ExitStatus, Stdio};
use tokio::process::Command;
use tokio::{io, select};
2024-10-13 23:50:36 -04:00
pub enum CaptureError {
Spawn(io::Error),
Stdout(io::Error),
Stderr(io::Error),
Wait(io::Error),
2024-10-13 23:50:36 -04:00
}
pub struct CapturedOutput {
pub status: Option<ExitStatus>,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
2024-10-13 23:50:36 -04:00
pub errors: Vec<CaptureError>,
}
impl CapturedOutput {
pub fn is_empty(&self) -> bool {
self.errors.is_empty() && self.stdout.is_empty() && self.stderr.is_empty()
}
}
pub async fn run_forward_and_capture(cmdline: &Vec<String>) -> CapturedOutput {
let command = cmdline.first().unwrap();
let ref args = cmdline[1..];
let mut child = match Command::new(command)
.args(args)
.stdout(Stdio::inherit())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(process) => process,
Err(error) => {
return CapturedOutput {
status: None,
stdout: vec![],
stderr: vec![],
2024-10-13 23:50:36 -04:00
errors: vec![CaptureError::Spawn(error)],
}
}
};
let mut stdout_tap = TapStream::new(child.stdout.take().unwrap(), io::stdout());
let mut stderr_tap = TapStream::new(child.stderr.take().unwrap(), io::stderr());
2024-10-13 23:50:36 -04:00
let mut stdout = vec![];
let mut stderr = vec![];
2024-10-13 23:50:36 -04:00
let mut errors = Vec::new();
let mut stdout_eof = false;
let mut stderr_eof = false;
let mut maybe_status: Option<ExitStatus> = None;
2024-10-13 23:50:36 -04:00
let status = loop {
select! {
result = stdout_tap.step(), if !stdout_eof => match result {
Ok(ReadOrWrite::Read(bytes)) => stdout.extend_from_slice(bytes),
Ok(ReadOrWrite::Written) => (),
Ok(ReadOrWrite::EOF) => stdout_eof = true,
2024-10-13 23:50:36 -04:00
Err(error) => errors.push(CaptureError::Stdout(error)),
},
result = stderr_tap.step(), if !stderr_eof => match result {
Ok(ReadOrWrite::Read(bytes)) => stderr.extend_from_slice(bytes),
Ok(ReadOrWrite::Written) => (),
Ok(ReadOrWrite::EOF) => stderr_eof = true,
2024-10-13 23:50:36 -04:00
Err(error) => errors.push(CaptureError::Stderr(error)),
},
status = child.wait(), if maybe_status.is_none() => match status {
Ok(status) => maybe_status = Some(status),
2024-10-13 23:50:36 -04:00
Err(error) => errors.push(CaptureError::Wait(error)),
},
else => break maybe_status.unwrap(),
2024-10-13 23:50:36 -04:00
}
};
CapturedOutput {
status: Some(status),
stdout,
stderr,
2024-10-13 23:50:36 -04:00
errors,
}
}