use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; use tokio::io::Lines; use tokio::process::Child; use tokio::process::ChildStderr; use tokio::process::ChildStdout; use crate::Result; use crate::database::db_handle::DbHandle; pub(crate) struct RunningBuild<'db> { db_handle: &'db DbHandle, } impl<'db> RunningBuild<'db> { pub(crate) fn new(db_handle: &'db DbHandle) -> Result { Ok(RunningBuild { db_handle }) } pub(crate) async fn run_to_completion(&mut self, mut child: Child) -> Result<()> { let mut output_stream = OutputStream::from_child(&mut child)?; let exit_status_handle = tokio::spawn(async move { let status = child .wait() .await .expect("nixos-rebuild encountered an error"); status }); loop { let next_line = output_stream.next_line().await?; match next_line { OutputLine::Stdout(line) | OutputLine::Stderr(line) => { self.handle_line(line)?; } OutputLine::Done => break, }; } let exit_status = exit_status_handle.await?; println!("nixos-rebuild status was: {}", exit_status); Ok(()) } fn handle_line(&mut self, line: String) -> Result<()> { Ok(()) } } struct OutputStream { stdout: Option>>, stderr: Option>>, } impl OutputStream { pub(crate) fn from_child(child: &mut Child) -> Result { let stdout = child .stdout .take() .expect("child did not have a handle to stdout"); let stderr = child .stderr .take() .expect("child did not have a handle to stderr"); let stdout = BufReader::new(stdout).lines(); let stderr = BufReader::new(stderr).lines(); Ok(OutputStream { stdout: Some(stdout), stderr: Some(stderr), }) } pub(crate) async fn next_line(&mut self) -> Result { loop { match (&mut self.stdout, &mut self.stderr) { (None, None) => { return Ok(OutputLine::Done); } (None, Some(err)) => { if let Some(line) = err.next_line().await? { return Ok(OutputLine::Stderr(line)); } else { return Ok(OutputLine::Done); } } (Some(out), None) => { if let Some(line) = out.next_line().await? { return Ok(OutputLine::Stdout(line)); } else { return Ok(OutputLine::Done); } } (Some(out), Some(err)) => { tokio::select! { Ok(line) = out.next_line() => match line { Some(line) => { return Ok(OutputLine::Stdout(line)); }, None => { self.stdout.take(); }, }, Ok(line) = err.next_line() => match line { Some(line) => { return Ok(OutputLine::Stderr(line)); }, None => { self.stderr.take(); }, }, else => { return Ok(OutputLine::Done); }, }; } }; } } } enum OutputLine { Stdout(String), Stderr(String), Done, }