use crate::{OwnedChild, OwnedCommand, ProcessHandle, TerminationOptions};
use std::{io, process::ExitStatus};
pub type AsyncStdin = tokio::process::ChildStdin;
pub type AsyncStdout = tokio::process::ChildStdout;
pub type AsyncStderr = tokio::process::ChildStderr;
#[derive(Debug)]
pub struct OwnedTokioChild {
child: Option<OwnedChild>,
pub stdin: Option<AsyncStdin>,
pub stdout: Option<AsyncStdout>,
pub stderr: Option<AsyncStderr>,
}
impl OwnedCommand {
pub fn spawn_tokio(mut self) -> io::Result<OwnedTokioChild> {
tokio::runtime::Handle::try_current().map_err(io::Error::other)?;
self.pipe_stdio(true);
let mut child = self.spawn()?;
match streams(&mut child) {
Ok((stdin, stdout, stderr)) => Ok(OwnedTokioChild {
child: Some(child),
stdin,
stdout,
stderr,
}),
Err(error) => {
let _ = child.terminate_tree_bounded(std::time::Duration::from_secs(2));
Err(error)
}
}
}
}
type Streams = (Option<AsyncStdin>, Option<AsyncStdout>, Option<AsyncStderr>);
fn streams(child: &mut OwnedChild) -> io::Result<Streams> {
Ok((
child
.take_stdin()
.map(tokio::process::ChildStdin::from_std)
.transpose()?,
child
.take_stdout()
.map(tokio::process::ChildStdout::from_std)
.transpose()?,
child
.take_stderr()
.map(tokio::process::ChildStderr::from_std)
.transpose()?,
))
}
impl OwnedTokioChild {
fn inner(&self) -> &OwnedChild {
self.child
.as_ref()
.expect("the child is present outside an in-flight terminate_tree")
}
pub fn id(&self) -> u32 {
self.inner().id()
}
pub fn take_stdin(&mut self) -> Option<AsyncStdin> {
self.stdin.take()
}
pub fn take_stdout(&mut self) -> Option<AsyncStdout> {
self.stdout.take()
}
pub fn take_stderr(&mut self) -> Option<AsyncStderr> {
self.stderr.take()
}
pub fn process_handle(&self) -> io::Result<ProcessHandle> {
self.inner().process_handle()
}
pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
self.child
.as_mut()
.expect("the child is present outside an in-flight terminate_tree")
.try_wait()
}
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
self.stdin = None;
let handle = self.process_handle()?;
tokio::task::spawn_blocking(move || handle.wait())
.await
.map_err(io::Error::other)?
}
pub async fn terminate_tree(&mut self, options: TerminationOptions) -> io::Result<ExitStatus> {
self.stdin = None;
let mut child = self
.child
.take()
.expect("the child is present outside an in-flight terminate_tree");
let (child, result) = tokio::task::spawn_blocking(move || {
let result = child.terminate_tree_with_options(options);
(child, result)
})
.await
.map_err(io::Error::other)?;
self.child = Some(child);
result
}
pub fn into_parts(
mut self,
) -> (
OwnedChild,
Option<AsyncStdin>,
Option<AsyncStdout>,
Option<AsyncStderr>,
) {
let child = self
.child
.take()
.expect("the child is present outside an in-flight terminate_tree");
(
child,
self.stdin.take(),
self.stdout.take(),
self.stderr.take(),
)
}
}