Impl helpers::confirm::Confirmer

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2022-06-10 13:27:28 +10:00
parent c1809d41fa
commit dd2fa2de33
No known key found for this signature in database
GPG key ID: 591C0B03040416D6
2 changed files with 100 additions and 4 deletions

View file

@ -20,7 +20,7 @@ mod auto_abort_join_handle;
pub use auto_abort_join_handle::AutoAbortJoinHandle; pub use auto_abort_join_handle::AutoAbortJoinHandle;
mod confirm; mod confirm;
pub use confirm::confirm; pub use confirm::{confirm, Confirmer};
mod extracter; mod extracter;
mod readable_rx; mod readable_rx;

View file

@ -1,5 +1,8 @@
use std::io::{self, BufRead, Write};
use log::info; use log::info;
use std::io::{stderr, stdin, Write}; use tokio::sync::mpsc;
use tokio::task::spawn_blocking;
use crate::BinstallError; use crate::BinstallError;
@ -7,10 +10,10 @@ pub fn confirm() -> Result<(), BinstallError> {
loop { loop {
info!("Do you wish to continue? yes/[no]"); info!("Do you wish to continue? yes/[no]");
eprint!("? "); eprint!("? ");
stderr().flush().ok(); io::stderr().flush().ok();
let mut input = String::new(); let mut input = String::new();
stdin().read_line(&mut input).unwrap(); io::stdin().read_line(&mut input).unwrap();
match input.as_str().trim() { match input.as_str().trim() {
"yes" | "y" | "YES" | "Y" => break Ok(()), "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<Result<(), BinstallError>>,
}
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<ConfirmerInner>);
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(())
}
}
}