use tokio::io::{AsyncReadExt, AsyncWriteExt};
use shlex::Shlex;
use tokio::process::{Child, ChildStderr, ChildStdin, ChildStdout};
use crate::error::{into_sfoio_err, sfoio_err, SfoIOErrorCode, SfoIOResult};
pub struct QAProcess {
stdin: Option<ChildStdin>,
stdout: Option<ChildStdout>,
stderr: Option<ChildStderr>,
child: Child,
}
impl QAProcess {
pub fn new(mut child: Child) -> Self {
Self {
stdin: child.stdin.take(),
stdout: child.stdout.take(),
stderr: child.stderr.take(),
child,
}
}
pub async fn answer(&mut self, question: &str, answer: &str) -> SfoIOResult<()> {
let mut offset = 0;
let mut buf = [0u8; 4096];
let mut error_buf = [0u8; 4096];
let mut error_offset = 0;
loop {
if offset == buf.len() || error_offset == error_buf.len() {
return Err(sfoio_err!(SfoIOErrorCode::Failed, "Buffer overflow"));
}
tokio::select! {
ret = self.stderr.as_mut().unwrap().read(&mut error_buf[error_offset..error_offset+1]) => {
match ret {
Ok(len) => {
if len == 0 {
return Err(sfoio_err!(SfoIOErrorCode::Failed, "EOF"));
}
error_offset += len;
let current = String::from_utf8_lossy(&error_buf[..error_offset]).to_string();
if current.ends_with(question) {
let stdin = self.stdin.as_mut().ok_or_else(||sfoio_err!(SfoIOErrorCode::Failed, "Failed to get stdin"))?;
stdin.write_all(answer.as_bytes()).await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
stdin.write_all("\n".as_bytes()).await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
break;
}
},
Err(e) => {
return Err(into_sfoio_err!(SfoIOErrorCode::Failed)(e))
}
}
},
ret = self.stdout.as_mut().unwrap().read(&mut buf[offset..offset+1]) => {
match ret {
Ok(len) => {
if len == 0 {
return Err(sfoio_err!(SfoIOErrorCode::Failed, "EOF"));
}
offset += len;
let current = String::from_utf8_lossy(&buf[..offset]).to_string();
if current.ends_with(question) {
let stdin = self.stdin.as_mut().ok_or_else(||sfoio_err!(SfoIOErrorCode::Failed, "Failed to get stdin"))?;
stdin.write_all(answer.as_bytes()).await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
stdin.write_all("\n".as_bytes()).await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
break;
}
},
Err(e) => {
return Err(into_sfoio_err!(SfoIOErrorCode::Failed)(e))
}
}
}
_ = self.child.wait() => {
break;
}
}
}
Ok(())
}
pub async fn wait(&mut self) -> SfoIOResult<()> {
let status = self.child.wait().await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
if status.success() {
Ok(())
} else {
let stderr = self.stderr.as_mut().ok_or_else(||sfoio_err!(SfoIOErrorCode::Failed, "Failed to get stderr"))?;
let mut error = Vec::new();
stderr.read_to_end(&mut error).await.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
Err(sfoio_err!(SfoIOErrorCode::Failed, "{}", String::from_utf8_lossy(error.as_slice())))
}
}
}
pub async fn execute(cmd: &str) -> SfoIOResult<Vec<u8>> {
let mut lexer = Shlex::new(cmd);
let args: Vec<String> = lexer.by_ref().collect();
let output = tokio::process::Command::new(args[0].as_str())
.args(&args[1..])
.output()
.await
.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
if output.status.success() {
Ok(output.stdout)
} else {
Err(sfoio_err!(SfoIOErrorCode::CmdReturnFailed, "{}", String::from_utf8_lossy(output.stderr.as_slice())))
}
}
pub async fn spawn(cmd: &str) -> SfoIOResult<QAProcess> {
let mut lexer = Shlex::new(cmd);
let args: Vec<String> = lexer.by_ref().collect();
let child = tokio::process::Command::new(args[0].as_str())
.args(&args[1..])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(into_sfoio_err!(SfoIOErrorCode::Failed))?;
Ok(QAProcess::new(child))
}