use crate::{ffi, result, runtime::ConfigFile, Error, Exit};
use std::{fs::File, io::Read};
#[derive(Debug)]
pub struct Child {
pub(crate) process: Option<ffi::Handle>,
pub(crate) _config: ConfigFile,
pub(crate) stdin: Option<File>,
pub(crate) stdout: Option<File>,
pub(crate) stderr: Option<File>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Output {
pub exit: Exit,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
}
impl Child {
#[must_use]
pub fn id(&self) -> u64 {
self.process
.as_ref()
.and_then(|process| ffi::process_id(process).ok())
.unwrap_or(0)
}
pub fn take_stdin(&mut self) -> Option<File> {
self.stdin.take()
}
pub fn take_stdout(&mut self) -> Option<File> {
self.stdout.take()
}
pub fn take_stderr(&mut self) -> Option<File> {
self.stderr.take()
}
pub fn try_wait(&mut self) -> Result<Option<Exit>, Error> {
ffi::try_wait(self.process.as_ref().ok_or(Error::InvalidState)?)
.map_err(native_error)?
.map(result::native)
.transpose()
}
pub fn force_stop(&mut self) -> Result<(), Error> {
ffi::kill(self.process.as_ref().ok_or(Error::InvalidState)?).map_err(native_error)
}
pub fn wait(mut self) -> Result<Exit, Error> {
let exit = result::native(
ffi::wait(self.process.as_ref().ok_or(Error::InvalidState)?).map_err(native_error)?,
)?;
ffi::destroy(self.process.take().ok_or(Error::InvalidState)?);
Ok(exit)
}
pub fn output(mut self) -> Result<Output, Error> {
drop(self.stdin.take());
let stdout = self.stdout.take().map(|mut file| {
std::thread::spawn(move || {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).map(|_| bytes)
})
});
let stderr = self.stderr.take().map(|mut file| {
std::thread::spawn(move || {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes).map(|_| bytes)
})
});
let status = self.wait()?;
let stdout = join(stdout)?;
let stderr = join(stderr)?;
Ok(Output {
exit: status,
stdout,
stderr,
})
}
}
impl Drop for Child {
fn drop(&mut self) {
if let Some(process) = self.process.take() {
ffi::destroy(process);
}
}
}
fn native_error(status: i32) -> Error {
Error::Engine { status, detail: 0 }
}
fn join(
thread: Option<std::thread::JoinHandle<std::io::Result<Vec<u8>>>>,
) -> Result<Vec<u8>, Error> {
match thread {
None => Ok(Vec::new()),
Some(thread) => thread
.join()
.map_err(|_| Error::InvalidState)?
.map_err(Error::Io),
}
}