diff --git a/src/helpers.rs b/src/helpers.rs index 9b71d3e1..a55b6a37 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -20,7 +20,7 @@ mod auto_abort_join_handle; pub use auto_abort_join_handle::AutoAbortJoinHandle; mod confirm; -pub use confirm::confirm; +pub use confirm::{confirm, Confirmer}; mod extracter; mod readable_rx; diff --git a/src/helpers/confirm.rs b/src/helpers/confirm.rs index 8872bc2b..30102aa4 100644 --- a/src/helpers/confirm.rs +++ b/src/helpers/confirm.rs @@ -1,5 +1,8 @@ +use std::io::{self, BufRead, Write}; + use log::info; -use std::io::{stderr, stdin, Write}; +use tokio::sync::mpsc; +use tokio::task::spawn_blocking; use crate::BinstallError; @@ -7,10 +10,10 @@ pub fn confirm() -> Result<(), BinstallError> { loop { info!("Do you wish to continue? yes/[no]"); eprint!("? "); - stderr().flush().ok(); + io::stderr().flush().ok(); let mut input = String::new(); - stdin().read_line(&mut input).unwrap(); + io::stdin().read_line(&mut input).unwrap(); match input.as_str().trim() { "yes" | "y" | "YES" | "Y" => break Ok(()), @@ -19,3 +22,96 @@ pub fn confirm() -> Result<(), 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(()) + } + } +}