use std::io::Read as _;
use std::process::{ChildStdout, Command, Stdio};
use std::sync::mpsc::Receiver;
use std::time::Duration;
pub(super) const TICK: Duration = Duration::from_millis(50);
pub(super) enum Ended {
Exited { code: Option<i32>, stdout: String },
Unstarted,
Unanswered { stopping: Option<String> },
}
pub(super) fn ask(command: &mut Command, knocks: u32) -> Ended {
let started = command
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn();
let Ok(mut child) = started else {
return Ended::Unstarted;
};
let printed = child.stdout.take().map(drain);
let mut left = knocks;
loop {
match child.try_wait() {
Ok(Some(status)) => {
return Ended::Exited {
code: status.code(),
stdout: printed.map_or_else(String::new, |printed| {
collected(&printed, TICK.saturating_mul(left.max(1)))
}),
};
}
Ok(None) => {}
Err(_unwatchable) => {
return Ended::Unanswered {
stopping: super::running::stop(&mut child),
};
}
}
let Some(fewer) = left.checked_sub(1) else {
return Ended::Unanswered {
stopping: super::running::stop(&mut child),
};
};
left = fewer;
std::thread::sleep(TICK);
}
}
fn drain(mut stdout: ChildStdout) -> Receiver<Vec<u8>> {
let (sender, printed) = std::sync::mpsc::channel();
let reading = std::thread::Builder::new()
.name("doctor-asking".to_owned())
.spawn(move || {
let mut bytes = Vec::new();
let read = match stdout.read_to_end(&mut bytes) {
Ok(_) => bytes,
Err(_unreadable) => Vec::new(),
};
drop(sender.send(read));
});
drop(reading);
printed
}
fn collected(printed: &Receiver<Vec<u8>>, patience: Duration) -> String {
match printed.recv_timeout(patience) {
Ok(bytes) => String::from_utf8_lossy(&bytes).into_owned(),
Err(_late_or_gone) => String::new(),
}
}