use crate::{Error, Result};
use std::io::{BufRead, BufReader, Read, Write};
use std::marker::Send;
use std::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, Command, ExitStatus, Stdio};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread::{self, JoinHandle};
pub type PID = usize;
pub struct Process {
pub prog: String,
pub args: Vec<String>,
pub child: Child,
_stdout_thread: JoinHandle<()>,
_stderr_thread: JoinHandle<()>,
receiver: Receiver<String>,
pub output: Vec<u8>,
stdin: ChildStdin,
pub working_dir: PathBuf,
}
impl Process {
pub fn start(
prog: &String,
args: &Vec<String>,
working_dir: &Path,
) -> Result<Self> {
let (tx, receiver) = mpsc::channel::<String>();
let mut child = match Command::new(prog)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.current_dir(working_dir)
.spawn()
{
Ok(child) => child,
Err(e) => return Err(Error::ProcessError(e)),
};
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
let _stdout_thread = read_thread(tx.clone(), stdout);
let _stderr_thread = read_thread(tx, stderr);
let output = Vec::new();
Ok(Self {
prog: prog.clone(),
args: args.clone(),
child,
_stdout_thread,
_stderr_thread,
receiver,
output,
stdin,
working_dir: working_dir.to_owned(),
})
}
pub fn gather_output(&mut self) {
for line in self.receiver.try_iter() {
self.output.append(&mut line.into_bytes());
self.output.push(b'\n');
}
}
pub fn is_alive(&mut self) -> Option<bool> {
match self.child.try_wait() {
Ok(Some(_)) => Some(false),
Ok(None) => Some(true),
_ => None,
}
}
pub fn exit_status(&mut self) -> Option<ExitStatus> {
match self.child.try_wait() {
Ok(s) => s,
_ => None,
}
}
pub fn kill(&mut self) -> Result<()> {
match self.child.kill() {
Ok(_) => {
self.child.wait().expect("failed to wait on process");
Ok(())
}
Err(e) => Err(Error::ProcessError(e)),
}
}
pub fn send_input(&mut self, line: &str) -> Result<()> {
let mut input = line.to_owned();
input.push('\n');
if let Err(e) = self.stdin.write_all(input.as_bytes()) {
Err(Error::ProcessError(e))
} else {
Ok(())
}
}
}
fn read_thread<R: Read + Send + 'static>(
tx: Sender<String>,
instance: R,
) -> JoinHandle<()> {
thread::spawn(move || {
let reader = BufReader::new(instance);
for line in reader.lines() {
match line {
Ok(line) => tx.send(line).expect("failed to send"),
Err(e) => {
eprintln!("failed to read line: {}", e);
return;
}
}
}
})
}