use dialoguer::{Confirm, Input, theme::ColorfulTheme};
use crate::commands::keyring::store::Store;
use crate::core::wizard::WizardInput;
pub(crate) struct SourceBucketInput<'a> {
pub flag: Option<String>,
pub store: &'a Store,
}
impl WizardInput for SourceBucketInput<'_> {
type Value = String;
fn flag_value(&self) -> Option<Result<String, String>> {
self.flag.clone().map(Ok)
}
fn prompt(&self) -> Result<String, String> {
self.store
.prompt_select_bucket()
.map(|bucket_config| bucket_config.alias.clone())
}
fn non_interactive_fallback(&self) -> Result<String, String> {
Err("--source-bucket is required when not running interactively".to_string())
}
}
pub(crate) struct ReportBucketInput<'a> {
pub flag: Option<String>,
pub store: &'a Store,
}
impl WizardInput for ReportBucketInput<'_> {
type Value = String;
fn flag_value(&self) -> Option<Result<String, String>> {
self.flag.clone().map(Ok)
}
fn prompt(&self) -> Result<String, String> {
self.store
.prompt_select_bucket()
.map(|bucket_config| bucket_config.alias.clone())
}
fn non_interactive_fallback(&self) -> Result<String, String> {
Err("--report-bucket is required when not running interactively".to_string())
}
}
pub(crate) struct UploadTargetInput<'a> {
pub flag: Option<String>,
pub store: &'a Store,
}
impl WizardInput for UploadTargetInput<'_> {
type Value = Option<String>;
fn flag_value(&self) -> Option<Result<Option<String>, String>> {
self.flag.clone().map(|alias| Ok(Some(alias)))
}
fn prompt(&self) -> Result<Option<String>, String> {
let upload = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Upload to a bucket-config?")
.default(false)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))?;
if !upload {
return Ok(None);
}
match self.store.prompt_select_bucket() {
Ok(bucket_config) => Ok(Some(bucket_config.alias.clone())),
Err(message) => {
println!("{message}");
Ok(None)
}
}
}
fn non_interactive_fallback(&self) -> Result<Option<String>, String> {
Ok(None)
}
}
pub(crate) struct EncryptionKeyInput<'a> {
pub flag: Option<String>,
pub store: &'a Store,
pub uploading: bool,
pub bucket_default: Option<String>,
}
impl WizardInput for EncryptionKeyInput<'_> {
type Value = Option<String>;
fn flag_value(&self) -> Option<Result<Option<String>, String>> {
self.flag.clone().map(|alias| Ok(Some(alias)))
}
fn prompt(&self) -> Result<Option<String>, String> {
if !self.uploading {
return Ok(None);
}
if let Some(default_alias) = &self.bucket_default {
let use_default = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(format!("Encrypt this upload using '{default_alias}'?"))
.default(true)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))?;
if use_default {
return Ok(Some(default_alias.clone()));
}
let use_different = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Use a different encryption key instead?")
.default(false)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))?;
if !use_different {
return Ok(None);
}
} else {
let encrypt = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Encrypt this upload?")
.default(false)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))?;
if !encrypt {
return Ok(None);
}
}
match self.store.prompt_select_encryption_key() {
Ok(key) => Ok(Some(key.alias.clone())),
Err(message) => {
println!("{message}");
Ok(None)
}
}
}
fn non_interactive_fallback(&self) -> Result<Option<String>, String> {
Ok(self.bucket_default.clone())
}
}
pub(crate) fn default_concurrency() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
}
pub(crate) struct CpuConcurrencyInput {
pub flag: Option<usize>,
}
impl WizardInput for CpuConcurrencyInput {
type Value = usize;
fn flag_value(&self) -> Option<Result<usize, String>> {
self.flag.map(|value| Ok(value.max(1)))
}
fn prompt(&self) -> Result<usize, String> {
let value = Input::<usize>::new()
.with_prompt("Concurrency")
.default(default_concurrency())
.interact_text()
.map_err(|err| format!("failed to read concurrency: {err}"))?;
Ok(value.max(1))
}
fn non_interactive_fallback(&self) -> Result<usize, String> {
Err("--concurrency is required when not running interactively".to_string())
}
}
const UPLOAD_CONCURRENCY_DEFAULT: usize = 16;
pub(crate) struct UploadConcurrencyInput {
pub flag: Option<usize>,
}
impl WizardInput for UploadConcurrencyInput {
type Value = usize;
fn flag_value(&self) -> Option<Result<usize, String>> {
self.flag.map(|value| Ok(value.max(1)))
}
fn prompt(&self) -> Result<usize, String> {
let value = Input::<usize>::new()
.with_prompt("Upload concurrency")
.default(UPLOAD_CONCURRENCY_DEFAULT)
.interact_text()
.map_err(|err| format!("failed to read upload concurrency: {err}"))?;
Ok(value.max(1))
}
fn non_interactive_fallback(&self) -> Result<usize, String> {
Ok(UPLOAD_CONCURRENCY_DEFAULT)
}
}
pub(crate) struct ConfirmInput {
pub yes: bool,
}
impl WizardInput for ConfirmInput {
type Value = bool;
fn flag_value(&self) -> Option<Result<bool, String>> {
self.yes.then_some(Ok(true))
}
fn prompt(&self) -> Result<bool, String> {
Confirm::with_theme(&ColorfulTheme::default())
.with_prompt("Proceed?")
.default(true)
.interact()
.map_err(|err| format!("failed to read confirmation: {err}"))
}
fn non_interactive_fallback(&self) -> Result<bool, String> {
Err(
"confirmation is required when not running interactively (pass --yes to skip)"
.to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_concurrency_is_at_least_one() {
assert!(default_concurrency() >= 1);
}
#[test]
fn cpu_concurrency_input_flag_value_overrides_the_default() {
let input = CpuConcurrencyInput { flag: Some(7) };
assert_eq!(input.flag_value(), Some(Ok(7)));
}
#[test]
fn cpu_concurrency_input_requires_a_flag_when_not_interactive() {
let input = CpuConcurrencyInput { flag: None };
assert!(input.non_interactive_fallback().is_err());
}
#[test]
fn upload_concurrency_input_flag_value_overrides_the_default() {
let input = UploadConcurrencyInput { flag: Some(32) };
assert_eq!(input.flag_value(), Some(Ok(32)));
}
#[test]
fn upload_concurrency_input_falls_back_to_a_default_when_not_interactive() {
let input = UploadConcurrencyInput { flag: None };
assert_eq!(
input.non_interactive_fallback(),
Ok(UPLOAD_CONCURRENCY_DEFAULT)
);
}
}