Merge pull request #171 from NobodyXu/fix/confirm

This commit is contained in:
Félix Saparelli 2022-06-11 20:11:20 +12:00 committed by GitHub
commit 50183a38c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 30 deletions

View file

@ -1,10 +1,9 @@
use std::{
io::{stderr, stdin, Write},
path::{Path, PathBuf},
};
use cargo_toml::Manifest;
use log::{debug, info};
use log::debug;
use reqwest::Method;
use serde::Serialize;
use tinytemplate::TinyTemplate;
@ -18,6 +17,9 @@ pub use async_extracter::extract_archive_stream;
mod auto_abort_join_handle;
pub use auto_abort_join_handle::AutoAbortJoinHandle;
mod ui_thread;
pub use ui_thread::UIThread;
mod extracter;
mod readable_rx;
@ -129,23 +131,6 @@ pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathB
dir
}
pub fn confirm() -> Result<(), BinstallError> {
loop {
info!("Do you wish to continue? yes/[no]");
eprint!("? ");
stderr().flush().ok();
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
match input.as_str().trim() {
"yes" | "y" | "YES" | "Y" => break Ok(()),
"no" | "n" | "NO" | "N" | "" => break Err(BinstallError::UserAbort),
_ => continue,
}
}
}
pub trait Template: Serialize {
fn render(&self, template: &str) -> Result<String, BinstallError>
where

97
src/helpers/ui_thread.rs Normal file
View file

@ -0,0 +1,97 @@
use std::io::{self, BufRead, Write};
use tokio::sync::mpsc;
use tokio::task::spawn_blocking;
use crate::BinstallError;
#[derive(Debug)]
struct UIThreadInner {
/// Request for confirmation
request_tx: mpsc::Sender<()>,
/// Confirmation
confirm_rx: mpsc::Receiver<Result<(), BinstallError>>,
}
impl UIThreadInner {
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();
stdin.read_line(&mut input).unwrap();
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 UIThread(Option<UIThreadInner>);
impl UIThread {
/// * `enable` - `true` to enable confirmation, `false` to disable it.
pub fn new(enable: bool) -> Self {
Self(if enable {
Some(UIThreadInner::new())
} else {
None
})
}
pub async fn confirm(&mut self) -> Result<(), BinstallError> {
if let Some(inner) = self.0.as_mut() {
inner.confirm().await
} else {
Ok(())
}
}
}

View file

@ -191,6 +191,8 @@ async fn entry() -> Result<()> {
)
.unwrap();
let mut uithread = UIThread::new(!opts.no_confirm);
// Compute install directory
let install_path = get_install_path(opts.install_path.as_deref()).ok_or_else(|| {
error!("No viable install path found of specified, try `--install-path`");
@ -228,8 +230,8 @@ async fn entry() -> Result<()> {
})
);
if !opts.no_confirm && !opts.dry_run {
confirm()?;
if !opts.dry_run {
uithread.confirm().await?;
}
}
@ -303,6 +305,7 @@ async fn entry() -> Result<()> {
opts,
package,
temp_dir,
&mut uithread,
)
.await
}
@ -317,7 +320,7 @@ async fn entry() -> Result<()> {
.first()
.ok_or_else(|| miette!("No viable targets found, try with `--targets`"))?;
install_from_source(opts, package, target).await
install_from_source(opts, package, target, &mut uithread).await
}
}
}
@ -331,6 +334,7 @@ async fn install_from_package(
opts: Options,
package: Package<Meta>,
temp_dir: TempDir,
uithread: &mut UIThread,
) -> Result<()> {
// Prompt user for third-party source
if fetcher.is_third_party() {
@ -338,8 +342,8 @@ async fn install_from_package(
"The package will be downloaded from third-party source {}",
fetcher.source_name()
);
if !opts.no_confirm && !opts.dry_run {
confirm()?;
if !opts.dry_run {
uithread.confirm().await?;
}
} else {
info!(
@ -429,9 +433,7 @@ async fn install_from_package(
return Ok(());
}
if !opts.no_confirm {
confirm()?;
}
uithread.confirm().await?;
info!("Installing binaries...");
for file in &bin_files {
@ -456,11 +458,16 @@ async fn install_from_package(
Ok(())
}
async fn install_from_source(opts: Options, package: Package<Meta>, target: &str) -> Result<()> {
async fn install_from_source(
opts: Options,
package: Package<Meta>,
target: &str,
uithread: &mut UIThread,
) -> Result<()> {
// Prompt user for source install
warn!("The package will be installed from source (with cargo)",);
if !opts.no_confirm && !opts.dry_run {
confirm()?;
if !opts.dry_run {
uithread.confirm().await?;
}
if opts.dry_run {