use std::io::{self, BufRead, Write}; use tokio::sync::mpsc; use tokio::task::spawn_blocking; use crate::BinstallError; #[derive(Debug)] struct ConfirmerInner { /// Request for confirmation request_tx: mpsc::Sender<()>, /// Confirmation confirm_rx: mpsc::Receiver>, } impl ConfirmerInner { fn new() -> Self { let (request_tx, mut request_rx) = mpsc::channel(1); let (confirm_tx, confirm_rx) = mpsc::channel(10); spawn_blocking(move || { // This task should be the only one able to // access stdin let mut stdin = io::stdin().lock(); let mut input = String::with_capacity(16); loop { if request_rx.blocking_recv().is_none() { break; } // Lock stdout so that nobody can interfere // with confirmation. let mut stdout = io::stdout().lock(); let res = loop { writeln!(&mut stdout, "Do you wish to continue? yes/[no]").unwrap(); write!(&mut stdout, "? ").unwrap(); stdout.flush().unwrap(); input.clear(); if stdin.read_line(&mut input).is_err() { break Err(BinstallError::UserAbort); } match input.as_str().trim() { "yes" | "y" | "YES" | "Y" => break Ok(()), "no" | "n" | "NO" | "N" | "" => break Err(BinstallError::UserAbort), _ => continue, } }; confirm_tx .blocking_send(res) .expect("entry exits when confirming request"); } }); Self { request_tx, confirm_rx, } } async fn confirm(&mut self) -> Result<(), BinstallError> { self.request_tx .send(()) .await .map_err(|_| BinstallError::UserAbort)?; self.confirm_rx .recv() .await .unwrap_or(Err(BinstallError::UserAbort)) } } #[derive(Debug)] pub struct Confirmer(Option); impl Confirmer { /// * `enable` - `true` to enable confirmation, `false` to disable it. pub fn new(enable: bool) -> Self { Self(if enable { Some(ConfirmerInner::new()) } else { None }) } pub async fn confirm(&mut self) -> Result<(), BinstallError> { if let Some(inner) = self.0.as_mut() { inner.confirm().await } else { Ok(()) } } }