use super::errors::CodeError;
use std::{
borrow::Cow,
ffi::OsStr,
process::{Output, Stdio},
};
use tokio::process::Command;
pub async fn capture_command_and_check_status(
command_str: impl AsRef<OsStr>,
args: &[impl AsRef<OsStr>],
) -> Result<std::process::Output, CodeError> {
let output = capture_command(&command_str, args).await?;
check_output_status(output, || {
format!(
"{} {}",
command_str.as_ref().to_string_lossy(),
args.iter()
.map(|a| a.as_ref().to_string_lossy())
.collect::<Vec<Cow<'_, str>>>()
.join(" ")
)
})
}
pub fn check_output_status(
output: Output,
cmd_str: impl FnOnce() -> String,
) -> Result<std::process::Output, CodeError> {
if !output.status.success() {
return Err(CodeError::CommandFailed {
command: cmd_str(),
code: output.status.code().unwrap_or(-1),
output: String::from_utf8_lossy(if output.stderr.is_empty() {
&output.stdout
} else {
&output.stderr
})
.into(),
});
}
Ok(output)
}
pub async fn capture_command<A, I, S>(
command_str: A,
args: I,
) -> Result<std::process::Output, CodeError>
where
A: AsRef<OsStr>,
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
new_tokio_command(&command_str)
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.output()
.await
.map_err(|e| CodeError::CommandFailed {
command: command_str.as_ref().to_string_lossy().to_string(),
code: -1,
output: e.to_string(),
})
}
#[cfg(windows)]
pub fn new_tokio_command(exe: impl AsRef<OsStr>) -> Command {
let mut p = tokio::process::Command::new(exe);
p.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
p
}
#[cfg(not(windows))]
pub fn new_tokio_command(exe: impl AsRef<OsStr>) -> Command {
tokio::process::Command::new(exe)
}
#[cfg(windows)]
pub fn new_script_command(script: impl AsRef<OsStr>) -> Command {
let mut cmd = new_tokio_command("cmd");
cmd.arg("/Q");
cmd.arg("/C");
cmd.arg(script);
cmd
}
#[cfg(not(windows))]
pub fn new_script_command(script: impl AsRef<OsStr>) -> Command {
new_tokio_command(script)
}
#[cfg(windows)]
pub fn new_std_command(exe: impl AsRef<OsStr>) -> std::process::Command {
let mut p = std::process::Command::new(exe);
std::os::windows::process::CommandExt::creation_flags(
&mut p,
winapi::um::winbase::CREATE_NO_WINDOW,
);
p
}
#[cfg(not(windows))]
pub fn new_std_command(exe: impl AsRef<OsStr>) -> std::process::Command {
std::process::Command::new(exe)
}
#[cfg(windows)]
pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> {
capture_command("taskkill", &["/t", "/pid", &process_id.to_string()]).await?;
Ok(())
}
#[cfg(not(windows))]
pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> {
use futures::future::join_all;
use tokio::io::{AsyncBufReadExt, BufReader};
async fn kill_single_pid(process_id_str: String) {
capture_command("kill", &[&process_id_str]).await.ok();
}
let parent_id = process_id.to_string();
let mut prgrep_cmd = Command::new("pgrep")
.arg("-P")
.arg(&parent_id)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| CodeError::CommandFailed {
command: format!("pgrep -P {parent_id}"),
code: -1,
output: e.to_string(),
})?;
let mut kill_futures = vec![tokio::spawn(
async move { kill_single_pid(parent_id).await },
)];
if let Some(stdout) = prgrep_cmd.stdout.take() {
let mut reader = BufReader::new(stdout).lines();
while let Some(line) = reader.next_line().await.unwrap_or(None) {
kill_futures.push(tokio::spawn(async move { kill_single_pid(line).await }))
}
}
join_all(kill_futures).await;
prgrep_cmd.kill().await.ok();
Ok(())
}