mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 02:50:19 +00:00
Merge branch 'v1'
This commit is contained in:
commit
d85bc1e8ae
52 changed files with 1207 additions and 2761 deletions
1714
Cargo.lock
generated
1714
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
31
Cargo.toml
31
Cargo.toml
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "himalaya"
|
||||
description = "CLI to manage emails"
|
||||
version = "1.0.0-beta.4"
|
||||
version = "1.0.0"
|
||||
authors = ["soywod <clement.douin@posteo.net>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
categories = ["command-line-interface", "command-line-utilities", "email"]
|
||||
keywords = ["cli", "email", "imap", "smtp", "sync"]
|
||||
categories = ["command-line-utilities", "email"]
|
||||
keywords = ["cli", "email", "imap", "maildir", "smtp"]
|
||||
homepage = "https://pimalaya.org/"
|
||||
documentation = "https://pimalaya.org/himalaya/cli/latest/"
|
||||
repository = "https://github.com/soywod/himalaya/"
|
||||
|
@ -23,8 +23,9 @@ default = [
|
|||
"smtp",
|
||||
"sendmail",
|
||||
|
||||
"account-discovery",
|
||||
"account-sync",
|
||||
"wizard",
|
||||
# "keyring",
|
||||
# "oauth2",
|
||||
|
||||
# "pgp-commands",
|
||||
# "pgp-gpg",
|
||||
|
@ -37,8 +38,9 @@ notmuch = ["email-lib/notmuch"]
|
|||
smtp = ["email-lib/smtp"]
|
||||
sendmail = ["email-lib/sendmail"]
|
||||
|
||||
account-discovery = ["email-lib/account-discovery"]
|
||||
account-sync = ["email-lib/account-sync", "maildir"]
|
||||
keyring = ["email-lib/keyring", "secret-lib?/keyring-tokio"]
|
||||
oauth2 = ["dep:oauth-lib", "email-lib/oauth2"]
|
||||
wizard = ["dep:secret-lib", "email-lib/autoconfig"]
|
||||
|
||||
pgp = []
|
||||
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"]
|
||||
|
@ -48,7 +50,7 @@ pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pgp"]
|
|||
[dependencies]
|
||||
ariadne = "0.2"
|
||||
async-trait = "0.1"
|
||||
clap = { version = "4.4", features = ["derive", "wrap_help", "env"] }
|
||||
clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
|
||||
clap_complete = "4.4"
|
||||
clap_mangen = "0.2"
|
||||
color-eyre = "0.6.3"
|
||||
|
@ -56,19 +58,19 @@ comfy-table = "7.1.1"
|
|||
console = "0.15.2"
|
||||
crossterm = "0.27"
|
||||
dirs = "4"
|
||||
email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "tracing"] }
|
||||
email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] }
|
||||
email_address = "0.2.4"
|
||||
erased-serde = "0.3"
|
||||
indicatif = "0.17"
|
||||
inquire = "0.7.4"
|
||||
mail-builder = "0.3"
|
||||
md5 = "0.7"
|
||||
mml-lib = { version = "=1.0.12", default-features = false, features = ["derive"] }
|
||||
oauth-lib = "=0.1.1"
|
||||
mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"] }
|
||||
oauth-lib = { version = "=0.1.1", optional = true }
|
||||
once_cell = "1.16"
|
||||
petgraph = "0.6"
|
||||
process-lib = { version = "=0.4.2", features = ["derive"] }
|
||||
secret-lib = { version = "=0.4.4", features = ["derive"] }
|
||||
secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde-toml-merge = "0.3"
|
||||
serde_json = "1"
|
||||
|
@ -86,8 +88,3 @@ url = "2.2"
|
|||
uuid = { version = "0.8", features = ["v4"] }
|
||||
|
||||
[patch.crates-io]
|
||||
# WIP: transition from `imap` to `imap-{types,codec,client}`
|
||||
email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
|
||||
imap-client = { git = "https://github.com/soywod/imap-client.git" }
|
||||
imap-codec = { git = "https://github.com/duesee/imap-codec.git" }
|
||||
imap-types = { git = "https://github.com/duesee/imap-codec.git" }
|
||||
|
|
|
@ -26,11 +26,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
printer.log("Checking configuration integrity…")?;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
account,
|
||||
#[cfg(feature = "account-sync")]
|
||||
true,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(account)?;
|
||||
let used_backends = toml_account_config.get_used_backends();
|
||||
|
||||
printer.log("Checking backend context integrity…")?;
|
||||
|
@ -76,7 +72,7 @@ impl AccountCheckUpCommand {
|
|||
|
||||
#[cfg(feature = "notmuch")]
|
||||
{
|
||||
printer.print("Checking Notmuch integrity…")?;
|
||||
printer.log("Checking Notmuch integrity…")?;
|
||||
|
||||
let notmuch = ctx_builder
|
||||
.notmuch
|
||||
|
|
|
@ -42,6 +42,7 @@ impl AccountConfigureCommand {
|
|||
if let Some(ref config) = account_config.imap {
|
||||
let reset = match &config.auth {
|
||||
ImapAuthConfig::Passwd(config) => config.reset().await,
|
||||
#[cfg(feature = "oauth2")]
|
||||
ImapAuthConfig::OAuth2(config) => config.reset().await,
|
||||
};
|
||||
if let Err(err) = reset {
|
||||
|
@ -54,6 +55,7 @@ impl AccountConfigureCommand {
|
|||
if let Some(ref config) = account_config.smtp {
|
||||
let reset = match &config.auth {
|
||||
SmtpAuthConfig::Passwd(config) => config.reset().await,
|
||||
#[cfg(feature = "oauth2")]
|
||||
SmtpAuthConfig::OAuth2(config) => config.reset().await,
|
||||
};
|
||||
if let Err(err) = reset {
|
||||
|
@ -74,6 +76,7 @@ impl AccountConfigureCommand {
|
|||
ImapAuthConfig::Passwd(config) => {
|
||||
config.configure(|| prompt::passwd("IMAP password")).await
|
||||
}
|
||||
#[cfg(feature = "oauth2")]
|
||||
ImapAuthConfig::OAuth2(config) => {
|
||||
config
|
||||
.configure(|| prompt::secret("IMAP OAuth 2.0 client secret"))
|
||||
|
@ -88,6 +91,7 @@ impl AccountConfigureCommand {
|
|||
SmtpAuthConfig::Passwd(config) => {
|
||||
config.configure(|| prompt::passwd("SMTP password")).await
|
||||
}
|
||||
#[cfg(feature = "oauth2")]
|
||||
SmtpAuthConfig::OAuth2(config) => {
|
||||
config
|
||||
.configure(|| prompt::secret("SMTP OAuth 2.0 client secret"))
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
mod check_up;
|
||||
mod configure;
|
||||
mod list;
|
||||
#[cfg(feature = "account-sync")]
|
||||
mod sync;
|
||||
|
||||
use color_eyre::Result;
|
||||
use clap::Subcommand;
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::{config::TomlConfig, printer::Printer};
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use self::sync::AccountSyncCommand;
|
||||
use self::{
|
||||
check_up::AccountCheckUpCommand, configure::AccountConfigureCommand, list::AccountListCommand,
|
||||
};
|
||||
|
@ -30,10 +26,6 @@ pub enum AccountSubcommand {
|
|||
|
||||
#[command(alias = "lst")]
|
||||
List(AccountListCommand),
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(alias = "synchronize", alias = "synchronise")]
|
||||
Sync(AccountSyncCommand),
|
||||
}
|
||||
|
||||
impl AccountSubcommand {
|
||||
|
@ -43,8 +35,6 @@ impl AccountSubcommand {
|
|||
Self::CheckUp(cmd) => cmd.execute(printer, config).await,
|
||||
Self::Configure(cmd) => cmd.execute(printer, config).await,
|
||||
Self::List(cmd) => cmd.execute(printer, config).await,
|
||||
#[cfg(feature = "account-sync")]
|
||||
Self::Sync(cmd) => cmd.execute(printer, config).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,268 +0,0 @@
|
|||
use clap::{ArgAction, Parser};
|
||||
use color_eyre::{eyre::bail, eyre::eyre, Result};
|
||||
use email::backend::context::BackendContextBuilder;
|
||||
#[cfg(feature = "imap")]
|
||||
use email::imap::ImapContextBuilder;
|
||||
use email::{
|
||||
account::sync::AccountSyncBuilder,
|
||||
backend::BackendBuilder,
|
||||
folder::sync::config::FolderSyncStrategy,
|
||||
sync::{hash::SyncHash, SyncEvent},
|
||||
};
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::OptionalAccountNameArg, backend::BackendKind, config::TomlConfig,
|
||||
printer::Printer,
|
||||
};
|
||||
|
||||
static MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||
ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap()
|
||||
});
|
||||
|
||||
static SUB_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||
ProgressStyle::with_template(
|
||||
" {prefix:.bold} — {wide_msg:.dim} \n {wide_bar:.black/black} {percent}% ",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
static SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||
ProgressStyle::with_template(" {prefix:.bold} \n {wide_bar:.green} {percent}% ").unwrap()
|
||||
});
|
||||
|
||||
/// Synchronize an account.
|
||||
///
|
||||
/// This command allows you to synchronize all folders and emails
|
||||
/// (including envelopes and messages) of a given account into a local
|
||||
/// Maildir folder.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct AccountSyncCommand {
|
||||
#[command(flatten)]
|
||||
pub account: OptionalAccountNameArg,
|
||||
|
||||
/// Run the synchronization without applying any changes.
|
||||
///
|
||||
/// Instead, a report will be printed to stdout containing all the
|
||||
/// changes the synchronization plan to do.
|
||||
#[arg(long, short)]
|
||||
pub dry_run: bool,
|
||||
|
||||
/// Synchronize only specific folders.
|
||||
///
|
||||
/// Only the given folders will be synchronized (including
|
||||
/// associated envelopes and messages). Useful when you need to
|
||||
/// speed up the synchronization process. A good usecase is to
|
||||
/// synchronize only the INBOX in order to quickly check for new
|
||||
/// messages.
|
||||
#[arg(long, short = 'f')]
|
||||
#[arg(value_name = "FOLDER", action = ArgAction::Append)]
|
||||
#[arg(conflicts_with = "exclude_folder", conflicts_with = "all_folders")]
|
||||
pub include_folder: Vec<String>,
|
||||
|
||||
/// Omit specific folders from the synchronization.
|
||||
///
|
||||
/// The given folders will be excluded from the synchronization
|
||||
/// (including associated envelopes and messages). Useful when you
|
||||
/// have heavy folders that you do not want to take care of, or to
|
||||
/// speed up the synchronization process.
|
||||
#[arg(long, short = 'x')]
|
||||
#[arg(value_name = "FOLDER", action = ArgAction::Append)]
|
||||
#[arg(conflicts_with = "include_folder", conflicts_with = "all_folders")]
|
||||
pub exclude_folder: Vec<String>,
|
||||
|
||||
/// Synchronize all exsting folders.
|
||||
#[arg(long, short = 'A')]
|
||||
#[arg(conflicts_with = "include_folder", conflicts_with = "exclude_folder")]
|
||||
pub all_folders: bool,
|
||||
}
|
||||
|
||||
impl AccountSyncCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing sync account command");
|
||||
|
||||
let account = self.account.name.as_deref();
|
||||
let (toml_account_config, account_config) =
|
||||
config.clone().into_account_configs(account, true)?;
|
||||
let account_name = account_config.name.as_str();
|
||||
|
||||
match toml_account_config.sync_kind() {
|
||||
Some(BackendKind::Imap) | Some(BackendKind::ImapCache) => {
|
||||
let imap_config = toml_account_config
|
||||
.imap
|
||||
.as_ref()
|
||||
.map(Clone::clone)
|
||||
.map(Arc::new)
|
||||
.ok_or_else(|| eyre!("imap config not found"))?;
|
||||
let imap_ctx = ImapContextBuilder::new(account_config.clone(), imap_config)
|
||||
.with_prebuilt_credentials()
|
||||
.await?;
|
||||
let imap = BackendBuilder::new(account_config.clone(), imap_ctx);
|
||||
self.sync(printer, account_name, imap).await
|
||||
}
|
||||
Some(backend) => bail!("backend {backend:?} not supported for synchronization"),
|
||||
None => bail!("no backend configured for synchronization"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync(
|
||||
self,
|
||||
printer: &mut impl Printer,
|
||||
account_name: &str,
|
||||
right: BackendBuilder<impl BackendContextBuilder + SyncHash + 'static>,
|
||||
) -> Result<()> {
|
||||
let included_folders = BTreeSet::from_iter(self.include_folder);
|
||||
let excluded_folders = BTreeSet::from_iter(self.exclude_folder);
|
||||
|
||||
let folder_filters = if !included_folders.is_empty() {
|
||||
Some(FolderSyncStrategy::Include(included_folders))
|
||||
} else if !excluded_folders.is_empty() {
|
||||
Some(FolderSyncStrategy::Exclude(excluded_folders))
|
||||
} else if self.all_folders {
|
||||
Some(FolderSyncStrategy::All)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let sync_builder =
|
||||
AccountSyncBuilder::try_new(right)?.with_some_folder_filters(folder_filters);
|
||||
|
||||
if self.dry_run {
|
||||
let report = sync_builder.with_dry_run(true).sync().await?;
|
||||
let mut hunks_count = report.folder.patch.len();
|
||||
|
||||
if !report.folder.patch.is_empty() {
|
||||
printer.log("Folders patch:")?;
|
||||
for (hunk, _) in report.folder.patch {
|
||||
printer.log(format!(" - {hunk}"))?;
|
||||
}
|
||||
printer.log("")?;
|
||||
}
|
||||
|
||||
if !report.email.patch.is_empty() {
|
||||
printer.log("Envelopes patch:")?;
|
||||
for (hunk, _) in report.email.patch {
|
||||
hunks_count += 1;
|
||||
printer.log(format!(" - {hunk}"))?;
|
||||
}
|
||||
printer.log("")?;
|
||||
}
|
||||
|
||||
printer.out(format!(
|
||||
"Estimated patch length for account {account_name} to be synchronized: {hunks_count}"
|
||||
))?;
|
||||
} else if printer.is_json() {
|
||||
sync_builder.sync().await?;
|
||||
printer.out(format!("Account {account_name} successfully synchronized!"))?;
|
||||
} else {
|
||||
let multi = MultiProgress::new();
|
||||
let sub_progresses = Mutex::new(HashMap::new());
|
||||
let main_progress = multi.add(
|
||||
ProgressBar::new(100)
|
||||
.with_style(MAIN_PROGRESS_STYLE.clone())
|
||||
.with_message("Listing folders…"),
|
||||
);
|
||||
|
||||
main_progress.tick();
|
||||
|
||||
let report = sync_builder
|
||||
.with_handler(move |evt| {
|
||||
match evt {
|
||||
SyncEvent::ListedAllFolders => {
|
||||
main_progress.set_message("Synchronizing folders…");
|
||||
}
|
||||
SyncEvent::ProcessedAllFolderHunks => {
|
||||
main_progress.set_message("Listing envelopes…");
|
||||
}
|
||||
SyncEvent::GeneratedEmailPatch(patches) => {
|
||||
let patches_len = patches.values().flatten().count();
|
||||
main_progress.set_length(patches_len as u64);
|
||||
main_progress.set_position(0);
|
||||
main_progress.set_message("Synchronizing emails…");
|
||||
|
||||
let mut envelopes_progresses = sub_progresses.lock().unwrap();
|
||||
for (folder, patch) in patches {
|
||||
let progress = ProgressBar::new(patch.len() as u64)
|
||||
.with_style(SUB_PROGRESS_STYLE.clone())
|
||||
.with_prefix(folder.clone())
|
||||
.with_finish(ProgressFinish::AndClear);
|
||||
let progress = multi.add(progress);
|
||||
envelopes_progresses.insert(folder, progress.clone());
|
||||
}
|
||||
}
|
||||
SyncEvent::ProcessedEmailHunk(hunk) => {
|
||||
main_progress.inc(1);
|
||||
let mut progresses = sub_progresses.lock().unwrap();
|
||||
if let Some(progress) = progresses.get_mut(hunk.folder()) {
|
||||
progress.inc(1);
|
||||
if progress.position() == (progress.length().unwrap() - 1) {
|
||||
progress.set_style(SUB_PROGRESS_DONE_STYLE.clone())
|
||||
} else {
|
||||
progress.set_message(format!("{hunk}…"));
|
||||
}
|
||||
}
|
||||
}
|
||||
SyncEvent::ProcessedAllEmailHunks => {
|
||||
let mut progresses = sub_progresses.lock().unwrap();
|
||||
for progress in progresses.values() {
|
||||
progress.finish_and_clear()
|
||||
}
|
||||
progresses.clear();
|
||||
|
||||
main_progress.set_length(100);
|
||||
main_progress.set_position(100);
|
||||
main_progress.set_message("Expunging folders…");
|
||||
}
|
||||
SyncEvent::ExpungedAllFolders => {
|
||||
main_progress.finish_and_clear();
|
||||
}
|
||||
_ => {
|
||||
main_progress.tick();
|
||||
}
|
||||
};
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.sync()
|
||||
.await?;
|
||||
|
||||
let folders_patch_err = report
|
||||
.folder
|
||||
.patch
|
||||
.iter()
|
||||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||
.collect::<Vec<_>>();
|
||||
if !folders_patch_err.is_empty() {
|
||||
printer.log("")?;
|
||||
printer.log("Errors occurred while applying the folders patch:")?;
|
||||
folders_patch_err
|
||||
.iter()
|
||||
.try_for_each(|(hunk, err)| printer.log(format!(" - {hunk}: {err}")))?;
|
||||
}
|
||||
|
||||
let envelopes_patch_err = report
|
||||
.email
|
||||
.patch
|
||||
.iter()
|
||||
.filter_map(|(hunk, err)| err.as_ref().map(|err| (hunk, err)))
|
||||
.collect::<Vec<_>>();
|
||||
if !envelopes_patch_err.is_empty() {
|
||||
printer.log("")?;
|
||||
printer.log("Errors occurred while applying the envelopes patch:")?;
|
||||
for (hunk, err) in envelopes_patch_err {
|
||||
printer.log(format!(" - {hunk}: {err}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(format!("Account {account_name} successfully synchronized!"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -24,25 +24,6 @@ use crate::{
|
|||
folder::config::FolderConfig, message::config::MessageConfig,
|
||||
};
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SyncConfig {
|
||||
pub backend: Option<BackendKind>,
|
||||
pub enable: Option<bool>,
|
||||
pub dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl From<SyncConfig> for email::account::sync::config::SyncConfig {
|
||||
fn from(config: SyncConfig) -> Self {
|
||||
Self {
|
||||
enable: config.enable,
|
||||
dir: config.dir,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents all existing kind of account config.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
|
@ -55,8 +36,6 @@ pub struct TomlAccountConfig {
|
|||
pub downloads_dir: Option<PathBuf>,
|
||||
pub backend: Option<BackendKind>,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
pub sync: Option<SyncConfig>,
|
||||
#[cfg(feature = "pgp")]
|
||||
pub pgp: Option<PgpConfig>,
|
||||
|
||||
|
@ -79,13 +58,6 @@ pub struct TomlAccountConfig {
|
|||
}
|
||||
|
||||
impl TomlAccountConfig {
|
||||
pub fn sync_kind(&self) -> Option<&BackendKind> {
|
||||
self.sync
|
||||
.as_ref()
|
||||
.and_then(|sync| sync.backend.as_ref())
|
||||
.or(self.backend.as_ref())
|
||||
}
|
||||
|
||||
pub fn add_folder_kind(&self) -> Option<&BackendKind> {
|
||||
self.folder
|
||||
.as_ref()
|
||||
|
@ -150,14 +122,6 @@ impl TomlAccountConfig {
|
|||
.or(self.backend.as_ref())
|
||||
}
|
||||
|
||||
pub fn watch_envelopes_kind(&self) -> Option<&BackendKind> {
|
||||
self.envelope
|
||||
.as_ref()
|
||||
.and_then(|envelope| envelope.watch.as_ref())
|
||||
.and_then(|watch| watch.backend.as_ref())
|
||||
.or(self.backend.as_ref())
|
||||
}
|
||||
|
||||
pub fn add_flags_kind(&self) -> Option<&BackendKind> {
|
||||
self.flag
|
||||
.as_ref()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub mod arg;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
#[cfg(feature = "wizard")]
|
||||
pub(crate) mod wizard;
|
||||
|
||||
use comfy_table::{presets, Attribute, Cell, Color, ContentArrangement, Row, Table};
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
#[cfg(feature = "account-sync")]
|
||||
use crate::account::config::SyncConfig;
|
||||
use color_eyre::{eyre::OptionExt, Result};
|
||||
#[cfg(feature = "account-sync")]
|
||||
use email_address::EmailAddress;
|
||||
use inquire::validator::{ErrorMessage, Validation};
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[cfg(feature = "account-discovery")]
|
||||
use crate::wizard_warn;
|
||||
use crate::{
|
||||
backend::{self, config::BackendConfig, BackendKind},
|
||||
|
@ -34,14 +30,11 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
|||
|
||||
let addr = EmailAddress::from_str(&config.email).unwrap();
|
||||
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
let autoconfig_email = config.email.to_owned();
|
||||
#[cfg(feature = "account-discovery")]
|
||||
let autoconfig = tokio::spawn(async move {
|
||||
email::account::discover::from_addr(&autoconfig_email)
|
||||
.await
|
||||
.ok()
|
||||
});
|
||||
#[cfg(feature = "wizard")]
|
||||
let autoconfig =
|
||||
tokio::spawn(async move { email::autoconfig::from_addr(&autoconfig_email).await.ok() });
|
||||
|
||||
let account_name = inquire::Text::new("Account name: ")
|
||||
.with_default(
|
||||
|
@ -65,12 +58,12 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
|||
));
|
||||
|
||||
let email = &config.email;
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
let autoconfig = autoconfig.await?;
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
let autoconfig = autoconfig.as_ref();
|
||||
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
if let Some(config) = autoconfig {
|
||||
if config.is_gmail() {
|
||||
println!();
|
||||
|
@ -83,7 +76,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
|||
match backend::wizard::configure(
|
||||
&account_name,
|
||||
email,
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
autoconfig,
|
||||
)
|
||||
.await?
|
||||
|
@ -109,7 +102,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
|||
match backend::wizard::configure_sender(
|
||||
&account_name,
|
||||
email,
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
autoconfig,
|
||||
)
|
||||
.await?
|
||||
|
@ -139,21 +132,5 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
|||
_ => (),
|
||||
};
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
{
|
||||
let should_configure_sync =
|
||||
inquire::Confirm::new("Do you need offline access for your account?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
if should_configure_sync {
|
||||
config.sync = Some(SyncConfig {
|
||||
enable: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some((account_name, config)))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
pub mod config;
|
||||
|
||||
#[cfg(feature = "wizard")]
|
||||
pub(crate) mod wizard;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::Result;
|
||||
use std::{fmt::Display, ops::Deref, sync::Arc};
|
||||
use tracing::instrument;
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
use email::imap::{ImapContextBuilder, ImapContextSync};
|
||||
|
@ -24,7 +27,6 @@ use email::{
|
|||
get::GetEnvelope,
|
||||
list::{ListEnvelopes, ListEnvelopesOptions},
|
||||
thread::ThreadEnvelopes,
|
||||
watch::WatchEnvelopes,
|
||||
Id, SingleId,
|
||||
},
|
||||
flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags, Flag, Flags},
|
||||
|
@ -59,8 +61,6 @@ pub enum BackendKind {
|
|||
|
||||
#[cfg(feature = "imap")]
|
||||
Imap,
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
ImapCache,
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
Maildir,
|
||||
|
@ -85,8 +85,6 @@ impl Display for BackendKind {
|
|||
|
||||
#[cfg(feature = "imap")]
|
||||
Self::Imap => "IMAP",
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Self::ImapCache => "IMAP cache",
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
Self::Maildir => "Maildir",
|
||||
|
@ -112,9 +110,6 @@ pub struct BackendContextBuilder {
|
|||
#[cfg(feature = "imap")]
|
||||
pub imap: Option<ImapContextBuilder>,
|
||||
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
pub imap_cache: Option<MaildirContextBuilder>,
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
pub maildir: Option<MaildirContextBuilder>,
|
||||
|
||||
|
@ -156,26 +151,6 @@ impl BackendContextBuilder {
|
|||
}
|
||||
},
|
||||
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
imap_cache: {
|
||||
let builder = toml_account_config
|
||||
.imap
|
||||
.as_ref()
|
||||
.filter(|_| kinds.contains(&&BackendKind::ImapCache))
|
||||
.map(Clone::clone)
|
||||
.map(Arc::new)
|
||||
.map(|imap_config| {
|
||||
email::backend::context::BackendContextBuilder::try_to_sync_cache_builder(
|
||||
&ImapContextBuilder::new(account_config.clone(), imap_config),
|
||||
&account_config,
|
||||
)
|
||||
});
|
||||
match builder {
|
||||
Some(builder) => Some(builder?),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
maildir: toml_account_config
|
||||
.maildir
|
||||
|
@ -227,11 +202,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.add_folder_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.add_folder_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.add_folder()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.add_folder_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -244,11 +214,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.list_folders_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.list_folders_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.list_folders()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.list_folders_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -261,11 +226,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.expunge_folder_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.expunge_folder_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.expunge_folder()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.expunge_folder_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -278,11 +238,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.purge_folder_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.purge_folder_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.purge_folder()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.purge_folder_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -295,11 +250,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.delete_folder_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.delete_folder_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.delete_folder()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.delete_folder_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -312,11 +262,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.get_envelope_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.get_envelope_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.get_envelope()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.get_envelope_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -329,11 +274,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.list_envelopes_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.list_envelopes_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.list_envelopes()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.list_envelopes_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -346,11 +286,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.thread_envelopes_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.thread_envelopes_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.thread_envelopes()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -359,32 +294,10 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn watch_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn WatchEnvelopes>> {
|
||||
match self.toml_account_config.watch_envelopes_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.watch_envelopes_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.watch_envelopes()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.watch_envelopes_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
Some(BackendKind::Notmuch) => self.watch_envelopes_with_some(&self.notmuch),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_flags(&self) -> Option<BackendFeature<Self::Context, dyn AddFlags>> {
|
||||
match self.toml_account_config.add_flags_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.add_flags_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.add_flags()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.add_flags_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -397,11 +310,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.set_flags_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.set_flags_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.set_flags()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.set_flags_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -414,11 +322,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.remove_flags_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.remove_flags_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.remove_flags()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.remove_flags_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -431,11 +334,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.add_message_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.add_message_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.add_message()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.add_message_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -458,11 +356,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.peek_messages_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.peek_messages_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.peek_messages()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.peek_messages_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -475,11 +368,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.get_messages_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.get_messages_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.get_messages()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.get_messages_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -492,11 +380,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.copy_messages_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.copy_messages_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.copy_messages()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.copy_messages_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -509,11 +392,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.move_messages_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.move_messages_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.move_messages()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.move_messages_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -526,11 +404,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
match self.toml_account_config.delete_messages_kind() {
|
||||
#[cfg(feature = "imap")]
|
||||
Some(BackendKind::Imap) => self.delete_messages_with_some(&self.imap),
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
let f = self.imap_cache.as_ref()?.delete_messages()?;
|
||||
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => self.delete_messages_with_some(&self.maildir),
|
||||
#[cfg(feature = "notmuch")]
|
||||
|
@ -547,11 +420,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
|||
ctx.imap = Some(imap.build().await?);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
if let Some(maildir) = self.imap_cache {
|
||||
ctx.imap_cache = Some(maildir.build().await?);
|
||||
}
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
if let Some(maildir) = self.maildir {
|
||||
ctx.maildir = Some(maildir.build().await?);
|
||||
|
@ -581,9 +449,6 @@ pub struct BackendContext {
|
|||
#[cfg(feature = "imap")]
|
||||
pub imap: Option<ImapContextSync>,
|
||||
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
pub imap_cache: Option<MaildirContextSync>,
|
||||
|
||||
#[cfg(feature = "maildir")]
|
||||
pub maildir: Option<MaildirContextSync>,
|
||||
|
||||
|
@ -663,37 +528,28 @@ impl Backend {
|
|||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn build_id_mapper(
|
||||
&self,
|
||||
folder: &str,
|
||||
backend_kind: Option<&BackendKind>,
|
||||
) -> Result<IdMapper> {
|
||||
#[allow(unused_mut)]
|
||||
let mut id_mapper = IdMapper::Dummy;
|
||||
|
||||
match backend_kind {
|
||||
#[cfg(feature = "maildir")]
|
||||
Some(BackendKind::Maildir) => {
|
||||
if let Some(_) = &self.toml_account_config.maildir {
|
||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
||||
}
|
||||
#[cfg(feature = "maildir")]
|
||||
if let Some(BackendKind::Maildir) = backend_kind {
|
||||
if let Some(_) = &self.toml_account_config.maildir {
|
||||
return Ok(IdMapper::new(&self.backend.account_config, folder)?);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||
Some(BackendKind::ImapCache) => {
|
||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
||||
#[cfg(feature = "notmuch")]
|
||||
if let Some(BackendKind::Notmuch) = backend_kind {
|
||||
if let Some(_) = &self.toml_account_config.notmuch {
|
||||
return Ok(IdMapper::new(&self.backend.account_config, folder)?);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "notmuch")]
|
||||
Some(BackendKind::Notmuch) => {
|
||||
if let Some(_) = &self.toml_account_config.notmuch {
|
||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
Ok(id_mapper)
|
||||
Ok(IdMapper::Dummy)
|
||||
}
|
||||
|
||||
pub async fn list_envelopes(
|
||||
|
@ -868,11 +724,6 @@ impl Backend {
|
|||
self.backend.send_message_then_save_copy(msg).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn watch_envelopes(&self, folder: &str) -> Result<()> {
|
||||
self.backend.watch_envelopes(folder).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Backend {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use color_eyre::Result;
|
||||
#[cfg(feature = "account-discovery")]
|
||||
use email::account::discover::config::AutoConfig;
|
||||
use email::autoconfig::config::AutoConfig;
|
||||
use inquire::Select;
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
|
@ -35,7 +34,7 @@ const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
|||
pub(crate) async fn configure(
|
||||
account_name: &str,
|
||||
email: &str,
|
||||
#[cfg(feature = "account-discovery")] autoconfig: Option<&AutoConfig>,
|
||||
autoconfig: Option<&AutoConfig>,
|
||||
) -> Result<Option<BackendConfig>> {
|
||||
let kind = Select::new("Default email backend", DEFAULT_BACKEND_KINDS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
|
@ -47,7 +46,7 @@ pub(crate) async fn configure(
|
|||
imap::wizard::configure(
|
||||
account_name,
|
||||
email,
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
autoconfig,
|
||||
)
|
||||
.await?,
|
||||
|
@ -65,7 +64,7 @@ pub(crate) async fn configure(
|
|||
pub(crate) async fn configure_sender(
|
||||
account_name: &str,
|
||||
email: &str,
|
||||
#[cfg(feature = "account-discovery")] autoconfig: Option<&AutoConfig>,
|
||||
autoconfig: Option<&AutoConfig>,
|
||||
) -> Result<Option<BackendConfig>> {
|
||||
let kind = Select::new(
|
||||
"Backend for sending messages",
|
||||
|
@ -80,7 +79,7 @@ pub(crate) async fn configure_sender(
|
|||
smtp::wizard::configure(
|
||||
account_name,
|
||||
email,
|
||||
#[cfg(feature = "account-discovery")]
|
||||
#[cfg(feature = "wizard")]
|
||||
autoconfig,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "wizard")]
|
||||
pub mod wizard;
|
||||
|
||||
use color_eyre::{
|
||||
|
@ -7,7 +8,7 @@ use color_eyre::{
|
|||
use dirs::{config_dir, home_dir};
|
||||
use email::{
|
||||
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
|
||||
flag::config::FlagConfig, folder::config::FolderConfig, message::config::MessageConfig,
|
||||
folder::config::FolderConfig, message::config::MessageConfig,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_toml_merge::merge;
|
||||
|
@ -16,9 +17,9 @@ use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|||
use toml::{self, Value};
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::backend::BackendKind;
|
||||
use crate::{account::config::TomlAccountConfig, wizard_warn};
|
||||
use crate::account::config::TomlAccountConfig;
|
||||
#[cfg(feature = "wizard")]
|
||||
use crate::wizard_warn;
|
||||
|
||||
/// Represents the user config file.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
|
@ -84,9 +85,8 @@ impl TomlConfig {
|
|||
/// program stops.
|
||||
///
|
||||
/// NOTE: the wizard can only be used with interactive shells.
|
||||
#[cfg(feature = "wizard")]
|
||||
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
||||
use std::process;
|
||||
|
||||
wizard_warn!("Cannot find existing configuration at {path:?}.");
|
||||
|
||||
let confirm = inquire::Confirm::new("Would you like to create one with the wizard? ")
|
||||
|
@ -95,10 +95,15 @@ impl TomlConfig {
|
|||
.unwrap_or_default();
|
||||
|
||||
if !confirm {
|
||||
process::exit(0);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
wizard::configure(path).await
|
||||
return wizard::configure(path).await;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wizard"))]
|
||||
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
||||
bail!("Cannot find existing configuration at {path:?}.");
|
||||
}
|
||||
|
||||
/// Read and parse the TOML configuration from default paths.
|
||||
|
@ -182,14 +187,14 @@ impl TomlConfig {
|
|||
.ok_or_else(|| eyre!("cannot find account {name}")),
|
||||
}?;
|
||||
|
||||
#[cfg(feature = "imap")]
|
||||
#[cfg(all(feature = "imap", feature = "keyring"))]
|
||||
if let Some(imap_config) = toml_account_config.imap.as_mut() {
|
||||
imap_config
|
||||
.auth
|
||||
.replace_undefined_keyring_entries(&account_name)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "smtp")]
|
||||
#[cfg(all(feature = "smtp", feature = "keyring"))]
|
||||
if let Some(smtp_config) = toml_account_config.smtp.as_mut() {
|
||||
smtp_config
|
||||
.auth
|
||||
|
@ -203,21 +208,8 @@ impl TomlConfig {
|
|||
pub fn into_account_configs(
|
||||
self,
|
||||
account_name: Option<&str>,
|
||||
#[cfg(feature = "account-sync")] disable_cache: bool,
|
||||
) -> Result<(Arc<TomlAccountConfig>, Arc<AccountConfig>)> {
|
||||
#[cfg_attr(not(feature = "account-sync"), allow(unused_mut))]
|
||||
let (account_name, mut toml_account_config) =
|
||||
self.into_toml_account_config(account_name)?;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
if let Some(true) = toml_account_config.sync.as_ref().and_then(|c| c.enable) {
|
||||
if !disable_cache {
|
||||
toml_account_config.backend = match toml_account_config.backend {
|
||||
Some(BackendKind::Imap) => Some(BackendKind::ImapCache),
|
||||
backend => backend,
|
||||
}
|
||||
}
|
||||
}
|
||||
let (account_name, toml_account_config) = self.into_toml_account_config(account_name)?;
|
||||
|
||||
let config = Config {
|
||||
display_name: self.display_name,
|
||||
|
@ -239,31 +231,19 @@ impl TomlConfig {
|
|||
folder: config.folder.map(|c| FolderConfig {
|
||||
aliases: c.alias,
|
||||
list: c.list.map(|c| c.remote),
|
||||
#[cfg(feature = "account-sync")]
|
||||
sync: c.sync,
|
||||
}),
|
||||
envelope: config.envelope.map(|c| EnvelopeConfig {
|
||||
list: c.list.map(|c| c.remote),
|
||||
thread: c.thread.map(|c| c.remote),
|
||||
watch: c.watch.map(|c| c.remote),
|
||||
#[cfg(feature = "account-sync")]
|
||||
sync: c.sync,
|
||||
}),
|
||||
flag: config.flag.map(|c| FlagConfig {
|
||||
#[cfg(feature = "account-sync")]
|
||||
sync: c.sync,
|
||||
}),
|
||||
flag: None,
|
||||
message: config.message.map(|c| MessageConfig {
|
||||
read: c.read.map(|c| c.remote),
|
||||
write: c.write.map(|c| c.remote),
|
||||
send: c.send.map(|c| c.remote),
|
||||
delete: c.delete.map(Into::into),
|
||||
#[cfg(feature = "account-sync")]
|
||||
sync: c.sync,
|
||||
}),
|
||||
template: config.template,
|
||||
#[cfg(feature = "account-sync")]
|
||||
sync: config.sync.map(Into::into),
|
||||
#[cfg(feature = "pgp")]
|
||||
pgp: config.pgp,
|
||||
},
|
||||
|
|
|
@ -152,9 +152,6 @@ fn pretty_serialize(config: &TomlConfig) -> Result<String> {
|
|||
#[cfg(feature = "sendmail")]
|
||||
set_table_dotted(item, "sendmail");
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
set_table_dotted(item, "sync");
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
set_table_dotted(item, "pgp");
|
||||
})
|
||||
|
@ -214,68 +211,6 @@ fn set_tables_dotted<'a>(item: &'a mut Item, keys: impl IntoIterator<Item = &'a
|
|||
// )
|
||||
// }
|
||||
|
||||
// #[cfg(feature = "account-sync")]
|
||||
// #[test]
|
||||
// fn pretty_serialize_sync_all() {
|
||||
// use email::account::sync::config::SyncConfig;
|
||||
|
||||
// assert_eq(
|
||||
// TomlAccountConfig {
|
||||
// email: "test@localhost".into(),
|
||||
// sync: Some(SyncConfig {
|
||||
// enable: Some(false),
|
||||
// dir: Some("/tmp/test".into()),
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// r#"[accounts.test]
|
||||
// email = "test@localhost"
|
||||
// sync.enable = false
|
||||
// sync.dir = "/tmp/test"
|
||||
// "#,
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[cfg(feature = "account-sync")]
|
||||
// #[test]
|
||||
// fn pretty_serialize_sync_include() {
|
||||
// use email::{
|
||||
// account::sync::config::SyncConfig,
|
||||
// folder::sync::config::{FolderSyncConfig, FolderSyncStrategy},
|
||||
// };
|
||||
// use std::collections::BTreeSet;
|
||||
|
||||
// use crate::folder::config::FolderConfig;
|
||||
|
||||
// assert_eq(
|
||||
// TomlAccountConfig {
|
||||
// email: "test@localhost".into(),
|
||||
// sync: Some(SyncConfig {
|
||||
// enable: Some(true),
|
||||
// dir: Some("/tmp/test".into()),
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// folder: Some(FolderConfig {
|
||||
// sync: Some(FolderSyncConfig {
|
||||
// filter: FolderSyncStrategy::Include(BTreeSet::from_iter(["test".into()])),
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// r#"[accounts.test]
|
||||
// email = "test@localhost"
|
||||
// sync.enable = true
|
||||
// sync.dir = "/tmp/test"
|
||||
// folder.sync.filter.include = ["test"]
|
||||
// folder.sync.permissions.create = true
|
||||
// folder.sync.permissions.delete = true
|
||||
// "#,
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[cfg(feature = "imap")]
|
||||
// #[test]
|
||||
// fn pretty_serialize_imap_passwd_cmd() {
|
||||
|
|
|
@ -8,8 +8,6 @@ use email::{
|
|||
use std::process::exit;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
envelope::EnvelopesTable, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
|
@ -37,10 +35,6 @@ pub struct ListEnvelopesCommand {
|
|||
#[arg(long, short = 's', value_name = "NUMBER")]
|
||||
pub page_size: Option<usize>,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
|
||||
|
@ -130,8 +124,6 @@ impl Default for ListEnvelopesCommand {
|
|||
folder: Default::default(),
|
||||
page: 1,
|
||||
page_size: Default::default(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
cache: Default::default(),
|
||||
account: Default::default(),
|
||||
query: Default::default(),
|
||||
table_max_width: Default::default(),
|
||||
|
@ -143,11 +135,9 @@ impl ListEnvelopesCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing list envelopes command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let page = 1.max(self.page) - 1;
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
pub mod list;
|
||||
pub mod thread;
|
||||
pub mod watch;
|
||||
|
||||
use clap::Subcommand;
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::{config::TomlConfig, printer::Printer};
|
||||
|
||||
use self::{
|
||||
list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand, watch::WatchEnvelopesCommand,
|
||||
};
|
||||
use self::{list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand};
|
||||
|
||||
/// Manage envelopes.
|
||||
///
|
||||
|
@ -24,9 +21,6 @@ pub enum EnvelopeSubcommand {
|
|||
|
||||
#[command()]
|
||||
Thread(ThreadEnvelopesCommand),
|
||||
|
||||
#[command()]
|
||||
Watch(WatchEnvelopesCommand),
|
||||
}
|
||||
|
||||
impl EnvelopeSubcommand {
|
||||
|
@ -35,7 +29,6 @@ impl EnvelopeSubcommand {
|
|||
match self {
|
||||
Self::List(cmd) => cmd.execute(printer, config).await,
|
||||
Self::Thread(cmd) => cmd.execute(printer, config).await,
|
||||
Self::Watch(cmd) => cmd.execute(printer, config).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ use email::{
|
|||
use std::process::exit;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
envelope::EnvelopesTree, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
|
@ -24,10 +22,6 @@ pub struct ThreadEnvelopesCommand {
|
|||
#[command(flatten)]
|
||||
pub folder: FolderNameOptionalFlag,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
|
||||
|
@ -43,11 +37,9 @@ impl ThreadEnvelopesCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing thread envelopes command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let thread_envelopes_kind = toml_account_config.thread_envelopes_kind();
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||
};
|
||||
|
||||
/// Watch envelopes for changes.
|
||||
///
|
||||
/// This command allows you to watch a folder and execute hooks when
|
||||
/// changes occur on envelopes.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct WatchEnvelopesCommand {
|
||||
#[command(flatten)]
|
||||
pub folder: FolderNameOptionalFlag,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
||||
impl WatchEnvelopesCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing watch envelopes command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
|
||||
let watch_envelopes_kind = toml_account_config.watch_envelopes_kind();
|
||||
|
||||
let backend = Backend::new(
|
||||
toml_account_config.clone(),
|
||||
account_config,
|
||||
watch_envelopes_kind,
|
||||
|builder| builder.set_watch_envelopes(BackendFeatureSource::Context),
|
||||
)
|
||||
.await?;
|
||||
|
||||
printer.out(format!(
|
||||
"Start watching folder {folder} for envelopes changes…"
|
||||
))?;
|
||||
|
||||
backend.watch_envelopes(folder).await
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
#[cfg(feature = "account-sync")]
|
||||
use email::envelope::sync::config::EnvelopeSyncConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
@ -9,10 +7,7 @@ use crate::backend::BackendKind;
|
|||
pub struct EnvelopeConfig {
|
||||
pub list: Option<ListEnvelopesConfig>,
|
||||
pub thread: Option<ThreadEnvelopesConfig>,
|
||||
pub watch: Option<WatchEnvelopesConfig>,
|
||||
pub get: Option<GetEnvelopeConfig>,
|
||||
#[cfg(feature = "account-sync")]
|
||||
pub sync: Option<EnvelopeSyncConfig>,
|
||||
}
|
||||
|
||||
impl EnvelopeConfig {
|
||||
|
@ -23,10 +18,6 @@ impl EnvelopeConfig {
|
|||
kinds.extend(list.get_used_backends());
|
||||
}
|
||||
|
||||
if let Some(watch) = &self.watch {
|
||||
kinds.extend(watch.get_used_backends());
|
||||
}
|
||||
|
||||
if let Some(get) = &self.get {
|
||||
kinds.extend(get.get_used_backends());
|
||||
}
|
||||
|
@ -75,26 +66,6 @@ impl ThreadEnvelopesConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct WatchEnvelopesConfig {
|
||||
pub backend: Option<BackendKind>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub remote: email::envelope::watch::config::WatchEnvelopeConfig,
|
||||
}
|
||||
|
||||
impl WatchEnvelopesConfig {
|
||||
pub fn get_used_backends(&self) -> HashSet<&BackendKind> {
|
||||
let mut kinds = HashSet::default();
|
||||
|
||||
if let Some(kind) = &self.backend {
|
||||
kinds.insert(kind);
|
||||
}
|
||||
|
||||
kinds
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct GetEnvelopeConfig {
|
||||
pub backend: Option<BackendKind>,
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -26,10 +24,6 @@ pub struct FlagAddCommand {
|
|||
#[command(flatten)]
|
||||
pub args: IdsAndFlagsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl FlagAddCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_flags_kind = toml_account_config.add_flags_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -26,10 +24,6 @@ pub struct FlagRemoveCommand {
|
|||
#[command(flatten)]
|
||||
pub args: IdsAndFlagsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl FlagRemoveCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let remove_flags_kind = toml_account_config.remove_flags_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -26,10 +24,6 @@ pub struct FlagSetCommand {
|
|||
#[command(flatten)]
|
||||
pub args: IdsAndFlagsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl FlagSetCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let set_flags_kind = toml_account_config.set_flags_kind();
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#[cfg(feature = "account-sync")]
|
||||
use email::flag::sync::config::FlagSyncConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
@ -10,8 +8,6 @@ pub struct FlagConfig {
|
|||
pub add: Option<FlagAddConfig>,
|
||||
pub set: Option<FlagSetConfig>,
|
||||
pub remove: Option<FlagRemoveConfig>,
|
||||
#[cfg(feature = "account-sync")]
|
||||
pub sync: Option<FlagSyncConfig>,
|
||||
}
|
||||
|
||||
impl FlagConfig {
|
||||
|
|
|
@ -5,8 +5,6 @@ use std::{fs, path::PathBuf};
|
|||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||
|
@ -25,10 +23,6 @@ pub struct AttachmentDownloadCommand {
|
|||
#[command(flatten)]
|
||||
pub envelopes: EnvelopeIdsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl AttachmentDownloadCommand {
|
|||
let folder = &self.folder.name;
|
||||
let ids = &self.envelopes.ids;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -26,10 +24,6 @@ pub struct MessageCopyCommand {
|
|||
#[command(flatten)]
|
||||
pub envelopes: EnvelopeIdsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -42,11 +36,9 @@ impl MessageCopyCommand {
|
|||
let target = &self.target_folder.name;
|
||||
let ids = &self.envelopes.ids;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let copy_messages_kind = toml_account_config.copy_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||
|
@ -25,10 +23,6 @@ pub struct MessageDeleteCommand {
|
|||
#[command(flatten)]
|
||||
pub envelopes: EnvelopeIdsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl MessageDeleteCommand {
|
|||
let folder = &self.folder.name;
|
||||
let ids = &self.envelopes.ids;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let delete_messages_kind = toml_account_config.delete_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::{eyre::eyre, Result};
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -36,10 +34,6 @@ pub struct MessageForwardCommand {
|
|||
#[command(flatten)]
|
||||
pub body: MessageRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -50,11 +44,9 @@ impl MessageForwardCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
|
|
|
@ -5,8 +5,6 @@ use mail_builder::MessageBuilder;
|
|||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig, printer::Printer,
|
||||
ui::editor,
|
||||
|
@ -24,10 +22,6 @@ pub struct MessageMailtoCommand {
|
|||
#[arg()]
|
||||
pub url: Url,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -36,8 +30,6 @@ impl MessageMailtoCommand {
|
|||
pub fn new(url: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
url: Url::parse(url)?,
|
||||
#[cfg(feature = "account-sync")]
|
||||
cache: Default::default(),
|
||||
account: Default::default(),
|
||||
})
|
||||
}
|
||||
|
@ -45,11 +37,9 @@ impl MessageMailtoCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing mailto message command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
#[allow(unused)]
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
|
@ -27,10 +25,6 @@ pub struct MessageMoveCommand {
|
|||
#[command(flatten)]
|
||||
pub envelopes: EnvelopeIdsArgs,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -43,11 +37,9 @@ impl MessageMoveCommand {
|
|||
let target = &self.target_folder.name;
|
||||
let ids = &self.envelopes.ids;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let move_messages_kind = toml_account_config.move_messages_kind();
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
|||
use mml::message::FilterParts;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
#[allow(unused)]
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
|
@ -70,10 +68,6 @@ pub struct MessageReadCommand {
|
|||
#[arg(conflicts_with = "no_headers")]
|
||||
pub headers: Vec<String>,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -85,11 +79,9 @@ impl MessageReadCommand {
|
|||
let folder = &self.folder.name;
|
||||
let ids = &self.envelopes.ids;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::{eyre::eyre, Result};
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -39,10 +37,6 @@ pub struct MessageReplyCommand {
|
|||
#[command(flatten)]
|
||||
pub body: MessageRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -52,11 +46,9 @@ impl MessageReplyCommand {
|
|||
info!("executing reply message command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
|||
use std::io::{self, BufRead, IsTerminal};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
#[allow(unused)]
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
|
@ -23,10 +21,6 @@ pub struct MessageSaveCommand {
|
|||
#[command(flatten)]
|
||||
pub message: MessageRawArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -37,11 +31,9 @@ impl MessageSaveCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
|||
use std::io::{self, BufRead, IsTerminal};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
message::arg::MessageRawArg, printer::Printer,
|
||||
|
@ -20,10 +18,6 @@ pub struct MessageSendCommand {
|
|||
#[command(flatten)]
|
||||
pub message: MessageRawArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -32,11 +26,9 @@ impl MessageSendCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing send message command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
||||
toml_account_config
|
||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
|||
use mml::message::FilterParts;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::envelope::arg::ids::EnvelopeIdArg;
|
||||
#[allow(unused)]
|
||||
use crate::{
|
||||
|
@ -71,10 +69,6 @@ pub struct MessageThreadCommand {
|
|||
#[arg(conflicts_with = "no_headers")]
|
||||
pub headers: Vec<String>,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -86,11 +80,9 @@ impl MessageThreadCommand {
|
|||
let folder = &self.folder.name;
|
||||
let id = &self.envelope.id;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::{backend::feature::BackendFeatureSource, message::Message};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -28,10 +26,6 @@ pub struct MessageWriteCommand {
|
|||
#[command(flatten)]
|
||||
pub body: MessageRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -40,11 +34,9 @@ impl MessageWriteCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing write message command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
let send_message_kind = toml_account_config.send_message_kind();
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use email::message::delete::config::DeleteMessageStyle;
|
||||
#[cfg(feature = "account-sync")]
|
||||
use email::message::sync::config::MessageSyncConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
|
@ -15,8 +13,6 @@ pub struct MessageConfig {
|
|||
pub copy: Option<MessageCopyConfig>,
|
||||
pub r#move: Option<MessageMoveConfig>,
|
||||
pub delete: Option<DeleteMessageConfig>,
|
||||
#[cfg(feature = "account-sync")]
|
||||
pub sync: Option<MessageSyncConfig>,
|
||||
}
|
||||
|
||||
impl MessageConfig {
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::{eyre::eyre, Result};
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -34,10 +32,6 @@ pub struct TemplateForwardCommand {
|
|||
#[command(flatten)]
|
||||
pub body: MessageRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -48,11 +42,9 @@ impl TemplateForwardCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::{eyre::eyre, Result};
|
|||
use email::backend::feature::BackendFeatureSource;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -38,10 +36,6 @@ pub struct TemplateReplyCommand {
|
|||
#[command(flatten)]
|
||||
pub body: MessageRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -53,11 +47,9 @@ impl TemplateReplyCommand {
|
|||
let folder = &self.folder.name;
|
||||
let id = self.envelope.id;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ use mml::MmlCompilerBuilder;
|
|||
use std::io::{self, BufRead, IsTerminal};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
email::template::arg::TemplateRawArg, folder::arg::name::FolderNameOptionalFlag,
|
||||
|
@ -27,10 +25,6 @@ pub struct TemplateSaveCommand {
|
|||
#[command(flatten)]
|
||||
pub template: TemplateRawArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -41,11 +35,9 @@ impl TemplateSaveCommand {
|
|||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_message_kind = toml_account_config.add_message_kind();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ use mml::MmlCompilerBuilder;
|
|||
use std::io::{self, BufRead, IsTerminal};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
email::template::arg::TemplateRawArg, printer::Printer,
|
||||
|
@ -23,10 +21,6 @@ pub struct TemplateSendCommand {
|
|||
#[command(flatten)]
|
||||
pub template: TemplateRawArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -35,11 +29,9 @@ impl TemplateSendCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing send template command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
||||
toml_account_config
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::message::Message;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig,
|
||||
email::template::arg::body::TemplateRawBodyArg, message::arg::header::HeaderRawArgs,
|
||||
|
@ -23,10 +21,6 @@ pub struct TemplateWriteCommand {
|
|||
#[command(flatten)]
|
||||
pub body: TemplateRawBodyArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -35,11 +29,9 @@ impl TemplateWriteCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing write template command");
|
||||
|
||||
let (_, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (_, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let tpl = Message::new_tpl_builder(account_config)
|
||||
.with_headers(self.headers.raw)
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::{backend::feature::BackendFeatureSource, folder::add::AddFolder};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
|
@ -19,10 +17,6 @@ pub struct AddFolderCommand {
|
|||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -32,11 +26,9 @@ impl AddFolderCommand {
|
|||
info!("executing create folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let add_folder_kind = toml_account_config.add_folder_kind();
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ use inquire::Confirm;
|
|||
use std::process;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
|
@ -21,10 +19,6 @@ pub struct FolderDeleteCommand {
|
|||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -42,11 +36,9 @@ impl FolderDeleteCommand {
|
|||
process::exit(0);
|
||||
};
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let delete_folder_kind = toml_account_config.delete_folder_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::{backend::feature::BackendFeatureSource, folder::expunge::ExpungeFolder};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
|
@ -20,10 +18,6 @@ pub struct FolderExpungeCommand {
|
|||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -33,11 +27,9 @@ impl FolderExpungeCommand {
|
|||
info!("executing expunge folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let expunge_folder_kind = toml_account_config.expunge_folder_kind();
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
|||
use email::{backend::feature::BackendFeatureSource, folder::list::ListFolders};
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag,
|
||||
backend::Backend,
|
||||
|
@ -18,10 +16,6 @@ use crate::{
|
|||
/// This command allows you to list all exsting folders.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FolderListCommand {
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
|
||||
|
@ -38,11 +32,9 @@ impl FolderListCommand {
|
|||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing list folders command");
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let list_folders_kind = toml_account_config.list_folders_kind();
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ use email::{backend::feature::BackendFeatureSource, folder::purge::PurgeFolder};
|
|||
use std::process;
|
||||
use tracing::info;
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
use crate::cache::arg::disable::CacheDisableFlag;
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||
folder::arg::name::FolderNameArg, printer::Printer,
|
||||
|
@ -20,10 +18,6 @@ pub struct FolderPurgeCommand {
|
|||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[cfg(feature = "account-sync")]
|
||||
#[command(flatten)]
|
||||
pub cache: CacheDisableFlag,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
@ -42,11 +36,9 @@ impl FolderPurgeCommand {
|
|||
process::exit(0);
|
||||
};
|
||||
|
||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||
self.account.name.as_deref(),
|
||||
#[cfg(feature = "account-sync")]
|
||||
self.cache.disable,
|
||||
)?;
|
||||
let (toml_account_config, account_config) = config
|
||||
.clone()
|
||||
.into_account_configs(self.account.name.as_deref())?;
|
||||
|
||||
let purge_folder_kind = toml_account_config.purge_folder_kind();
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#[cfg(feature = "account-sync")]
|
||||
use email::folder::sync::config::FolderSyncConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
|
@ -14,8 +12,6 @@ pub struct FolderConfig {
|
|||
pub expunge: Option<FolderExpungeConfig>,
|
||||
pub purge: Option<FolderPurgeConfig>,
|
||||
pub delete: Option<FolderDeleteConfig>,
|
||||
#[cfg(feature = "account-sync")]
|
||||
pub sync: Option<FolderSyncConfig>,
|
||||
}
|
||||
|
||||
impl FolderConfig {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#[cfg(feature = "wizard")]
|
||||
pub(crate) mod wizard;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use color_eyre::Result;
|
||||
#[cfg(feature = "account-discovery")]
|
||||
use email::account::discover::config::{AuthenticationType, AutoConfig, SecurityType, ServerType};
|
||||
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
||||
#[cfg(feature = "oauth2")]
|
||||
use email::{
|
||||
account::config::{
|
||||
oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||
passwd::PasswdConfig,
|
||||
},
|
||||
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||
autoconfig::config::AuthenticationType,
|
||||
};
|
||||
use email::{
|
||||
account::config::passwd::PasswdConfig,
|
||||
imap::config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
|
||||
};
|
||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
||||
#[cfg(feature = "oauth2")]
|
||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
||||
use secret::Secret;
|
||||
|
||||
use crate::{backend::config::BackendConfig, ui::prompt, wizard_log};
|
||||
use crate::{backend::config::BackendConfig, ui::prompt};
|
||||
|
||||
const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
||||
ImapEncryptionKind::Tls,
|
||||
|
@ -20,11 +22,13 @@ const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
|||
ImapEncryptionKind::None,
|
||||
];
|
||||
|
||||
const XOAUTH2: &str = "XOAUTH2";
|
||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||
|
||||
const SECRETS: &[&str] = &[KEYRING, RAW, CMD];
|
||||
const SECRETS: &[&str] = &[
|
||||
#[cfg(feature = "keyring")]
|
||||
KEYRING,
|
||||
RAW,
|
||||
CMD,
|
||||
];
|
||||
#[cfg(feature = "keyring")]
|
||||
const KEYRING: &str = "Ask my password, then save it in my system's global keyring";
|
||||
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
|
||||
const CMD: &str = "Ask me a shell command that exposes my password";
|
||||
|
@ -49,16 +53,14 @@ impl StringValidator for U16Validator {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "account-discovery")]
|
||||
pub(crate) async fn configure(
|
||||
account_name: &str,
|
||||
email: &str,
|
||||
autoconfig: Option<&AutoConfig>,
|
||||
) -> Result<BackendConfig> {
|
||||
use color_eyre::eyre::OptionExt as _;
|
||||
use inquire::{validator::MinLengthValidator, Confirm, Password, Select, Text};
|
||||
use inquire::{validator, Select, Text};
|
||||
|
||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||
let autoconfig_server = autoconfig.and_then(|c| {
|
||||
c.email_provider()
|
||||
.incoming_servers()
|
||||
|
@ -93,7 +95,7 @@ pub(crate) async fn configure(
|
|||
ImapEncryptionKind::None => 2,
|
||||
};
|
||||
|
||||
let encryption_idx = Select::new("IMAP encryption", ENCRYPTIONS.to_vec())
|
||||
let encryption_kind = Select::new("IMAP encryption", ENCRYPTIONS.to_vec())
|
||||
.with_starting_cursor(default_encryption_idx)
|
||||
.prompt_skippable()?;
|
||||
|
||||
|
@ -101,28 +103,28 @@ pub(crate) async fn configure(
|
|||
.and_then(|s| s.port())
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| match &autoconfig_encryption {
|
||||
ImapEncryptionKind::Tls => 993,
|
||||
ImapEncryptionKind::StartTls => 143,
|
||||
ImapEncryptionKind::None => 143,
|
||||
ImapEncryptionKind::Tls => 465,
|
||||
ImapEncryptionKind::StartTls => 587,
|
||||
ImapEncryptionKind::None => 25,
|
||||
});
|
||||
|
||||
let (encryption, default_port) = match encryption_idx {
|
||||
Some(enc_kind)
|
||||
if &enc_kind
|
||||
let (encryption, default_port) = match encryption_kind {
|
||||
Some(idx)
|
||||
if &idx
|
||||
== ENCRYPTIONS.get(default_encryption_idx).ok_or_eyre(
|
||||
"something impossible happened while selecting the encryption of imap.",
|
||||
"something impossible happened during finding default match for encryption.",
|
||||
)? =>
|
||||
{
|
||||
(Some(autoconfig_encryption), autoconfig_port)
|
||||
}
|
||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 993),
|
||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 143),
|
||||
_ => (Some(ImapEncryptionKind::None), 143),
|
||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 465),
|
||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 587),
|
||||
_ => (Some(ImapEncryptionKind::None), 25),
|
||||
};
|
||||
|
||||
let port = Text::new("IMAP port")
|
||||
.with_validators(&[
|
||||
Box::new(MinLengthValidator::new(1)),
|
||||
Box::new(validator::MinLengthValidator::new(1)),
|
||||
Box::new(U16Validator {}),
|
||||
])
|
||||
.with_default(&default_port.to_string())
|
||||
|
@ -141,173 +143,167 @@ pub(crate) async fn configure(
|
|||
.with_default(&default_login)
|
||||
.prompt()?;
|
||||
|
||||
let default_oauth2_enabled = autoconfig_server
|
||||
.and_then(|imap| {
|
||||
imap.authentication_type()
|
||||
.into_iter()
|
||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||
})
|
||||
.filter(|_| autoconfig_oauth2.is_some())
|
||||
.unwrap_or_default();
|
||||
#[cfg(feature = "oauth2")]
|
||||
let auth = {
|
||||
use inquire::{Confirm, Password};
|
||||
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(default_oauth2_enabled)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
const XOAUTH2: &str = "XOAUTH2";
|
||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||
|
||||
let auth = if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST.to_owned();
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||
|
||||
let method_idx = Select::new("IMAP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
config.method = match method_idx {
|
||||
Some(XOAUTH2) => OAuth2Method::XOAuth2,
|
||||
Some(OAUTHBEARER) => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
config.client_id = Text::new("IMAP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("IMAP OAuth 2.0 client secret").prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
let default_auth_url = autoconfig_oauth2
|
||||
.map(|o| o.auth_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.auth_url = Text::new("IMAP OAuth 2.0 authorization URL")
|
||||
.with_default(&default_auth_url)
|
||||
.prompt()?;
|
||||
|
||||
let default_token_url = autoconfig_oauth2
|
||||
.map(|o| o.token_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.token_url = Text::new("IMAP OAuth 2.0 token URL")
|
||||
.with_default(&default_token_url)
|
||||
.prompt()?;
|
||||
|
||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(match &autoconfig_scopes {
|
||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?
|
||||
.map(ToOwned::to_owned),
|
||||
None => {
|
||||
Some(Text::new(prompt).prompt()?.to_owned()).filter(|scope| !scope.is_empty())
|
||||
}
|
||||
let default_oauth2_enabled = autoconfig_server
|
||||
.and_then(|imap| {
|
||||
imap.authentication_type()
|
||||
.into_iter()
|
||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||
})
|
||||
};
|
||||
.filter(|_| autoconfig_oauth2.is_some())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(scope) = prompt_scope("IMAP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(default_oauth2_enabled)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more IMAP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST;
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
|
||||
Ok(confirm)
|
||||
};
|
||||
let method_idx = Select::new("IMAP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
config.method = match method_idx {
|
||||
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
||||
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
config.client_id = Text::new("IMAP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("IMAP OAuth 2.0 client secret")
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
let default_auth_url = autoconfig_oauth2
|
||||
.map(|o| o.auth_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.auth_url = Text::new("IMAP OAuth 2.0 authorization URL")
|
||||
.with_default(&default_auth_url)
|
||||
.prompt()?;
|
||||
|
||||
let default_token_url = autoconfig_oauth2
|
||||
.map(|o| o.token_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.token_url = Text::new("IMAP OAuth 2.0 token URL")
|
||||
.with_default(&default_token_url)
|
||||
.prompt()?;
|
||||
|
||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(match &autoconfig_scopes {
|
||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?
|
||||
.map(ToOwned::to_owned),
|
||||
None => Some(Text::new(prompt).prompt()?).filter(|scope| !scope.is_empty()),
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("IMAP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more IMAP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
Ok(confirm)
|
||||
};
|
||||
|
||||
wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
};
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
}
|
||||
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
|
||||
crate::wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
ImapAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
let secret_idx = Select::new("IMAP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let secret = match secret_idx {
|
||||
Some(KEYRING) => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("IMAP password")?)
|
||||
.await?;
|
||||
secret
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
Some(RAW) => Secret::new_raw(prompt::passwd("IMAP password")?),
|
||||
Some(CMD) => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-imap-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
ImapAuthConfig::Passwd(PasswdConfig(secret))
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token = Secret::try_new_keyring_entry(format!(
|
||||
"{account_name}-imap-oauth2-refresh-token"
|
||||
))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
ImapAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
configure_passwd(account_name).await?
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
let auth = configure_passwd(account_name).await?;
|
||||
|
||||
let config = ImapConfig {
|
||||
host,
|
||||
port,
|
||||
|
@ -320,191 +316,30 @@ pub(crate) async fn configure(
|
|||
Ok(BackendConfig::Imap(config))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "account-discovery"))]
|
||||
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> {
|
||||
use inquire::{
|
||||
validator::MinLengthValidator, Confirm, Password, PasswordDisplayMode, Select, Text,
|
||||
};
|
||||
pub(crate) async fn configure_passwd(account_name: &str) -> Result<ImapAuthConfig> {
|
||||
use inquire::{Select, Text};
|
||||
|
||||
let default_host = format!("imap.{}", email.rsplit_once('@').unwrap().1);
|
||||
|
||||
let host = Text::new("IMAP hostname")
|
||||
.with_default(&default_host)
|
||||
.prompt()?;
|
||||
|
||||
let encryption_idx = Select::new("IMAP encryption", ENCRYPTIONS.to_vec())
|
||||
let secret_idx = Select::new("IMAP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let (encryption, default_port) = match encryption_idx {
|
||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 993),
|
||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 143),
|
||||
_ => (Some(ImapEncryptionKind::None), 143),
|
||||
let secret = match secret_idx {
|
||||
#[cfg(feature = "keyring")]
|
||||
Some(sec) if sec == KEYRING => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("IMAP password")?)
|
||||
.await?;
|
||||
secret
|
||||
}
|
||||
Some(sec) if sec == RAW => Secret::new_raw(prompt::passwd("IMAP password")?),
|
||||
Some(sec) if sec == CMD => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-imap-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let port = Text::new("IMAP port")
|
||||
.with_validators(&[
|
||||
Box::new(MinLengthValidator::new(1)),
|
||||
Box::new(U16Validator {}),
|
||||
])
|
||||
.with_default(&default_port.to_string())
|
||||
.prompt()
|
||||
.map(|input| input.parse::<u16>().unwrap())?;
|
||||
|
||||
let default_login = email.to_owned();
|
||||
|
||||
let login = Text::new("IMAP login")
|
||||
.with_default(&default_login)
|
||||
.prompt()?;
|
||||
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let auth = if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST.to_owned();
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
|
||||
let method_idx = Select::new("IMAP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
config.method = match method_idx {
|
||||
Some(XOAUTH2) => OAuth2Method::XOAuth2,
|
||||
Some(OAUTHBEARER) => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
config.client_id = Text::new("IMAP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("IMAP OAuth 2.0 client secret")
|
||||
.with_display_mode(PasswordDisplayMode::Masked)
|
||||
.prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
config.auth_url = Text::new("IMAP OAuth 2.0 authorization URL").prompt()?;
|
||||
|
||||
config.token_url = Text::new("IMAP OAuth 2.0 token URL").prompt()?;
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(Some(Text::new(prompt).prompt()?.to_owned()).filter(|scope| !scope.is_empty()))
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("IMAP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more IMAP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(confirm)
|
||||
};
|
||||
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
}
|
||||
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
|
||||
wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-refresh-token"))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
ImapAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
let secret_idx = Select::new("IMAP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let secret = match secret_idx {
|
||||
Some(KEYRING) => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("IMAP password")?)
|
||||
.await?;
|
||||
secret
|
||||
}
|
||||
Some(RAW) => Secret::new_raw(prompt::passwd("IMAP password")?),
|
||||
Some(CMD) => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-imap-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
ImapAuthConfig::Passwd(PasswdConfig(secret))
|
||||
};
|
||||
|
||||
let config = ImapConfig {
|
||||
host,
|
||||
port,
|
||||
encryption,
|
||||
login,
|
||||
auth,
|
||||
watch: None,
|
||||
};
|
||||
|
||||
Ok(BackendConfig::Imap(config))
|
||||
Ok(ImapAuthConfig::Passwd(PasswdConfig(secret)))
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#[cfg(feature = "wizard")]
|
||||
pub(crate) mod wizard;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#[cfg(feature = "wizard")]
|
||||
pub(crate) mod wizard;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use color_eyre::Result;
|
||||
#[cfg(feature = "account-discovery")]
|
||||
use email::account::discover::config::{AuthenticationType, AutoConfig, SecurityType, ServerType};
|
||||
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
||||
#[cfg(feature = "oauth2")]
|
||||
use email::{
|
||||
account::config::{
|
||||
oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||
passwd::PasswdConfig,
|
||||
},
|
||||
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||
autoconfig::config::AuthenticationType,
|
||||
};
|
||||
use email::{
|
||||
account::config::passwd::PasswdConfig,
|
||||
smtp::config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
|
||||
};
|
||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
||||
#[cfg(feature = "oauth2")]
|
||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
||||
use secret::Secret;
|
||||
|
||||
use crate::{backend::config::BackendConfig, ui::prompt, wizard_log};
|
||||
use crate::{backend::config::BackendConfig, ui::prompt};
|
||||
|
||||
const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
||||
SmtpEncryptionKind::Tls,
|
||||
|
@ -20,11 +22,13 @@ const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
|||
SmtpEncryptionKind::None,
|
||||
];
|
||||
|
||||
const XOAUTH2: &str = "XOAUTH2";
|
||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||
|
||||
const SECRETS: &[&str] = &[KEYRING, RAW, CMD];
|
||||
const SECRETS: &[&str] = &[
|
||||
#[cfg(feature = "keyring")]
|
||||
KEYRING,
|
||||
RAW,
|
||||
CMD,
|
||||
];
|
||||
#[cfg(feature = "keyring")]
|
||||
const KEYRING: &str = "Ask my password, then save it in my system's global keyring";
|
||||
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
|
||||
const CMD: &str = "Ask me a shell command that exposes my password";
|
||||
|
@ -49,16 +53,14 @@ impl StringValidator for U16Validator {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "account-discovery")]
|
||||
pub(crate) async fn configure(
|
||||
account_name: &str,
|
||||
email: &str,
|
||||
autoconfig: Option<&AutoConfig>,
|
||||
) -> Result<BackendConfig> {
|
||||
use color_eyre::eyre::OptionExt as _;
|
||||
use inquire::{validator, Confirm, Password, Select, Text};
|
||||
use inquire::{validator, Select, Text};
|
||||
|
||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||
let autoconfig_server = autoconfig.and_then(|c| {
|
||||
c.email_provider()
|
||||
.outgoing_servers()
|
||||
|
@ -141,173 +143,167 @@ pub(crate) async fn configure(
|
|||
.with_default(&default_login)
|
||||
.prompt()?;
|
||||
|
||||
let default_oauth2_enabled = autoconfig_server
|
||||
.and_then(|smtp| {
|
||||
smtp.authentication_type()
|
||||
.into_iter()
|
||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||
})
|
||||
.filter(|_| autoconfig_oauth2.is_some())
|
||||
.unwrap_or_default();
|
||||
#[cfg(feature = "oauth2")]
|
||||
let auth = {
|
||||
use inquire::{Confirm, Password};
|
||||
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(default_oauth2_enabled)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
const XOAUTH2: &str = "XOAUTH2";
|
||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||
|
||||
let auth = if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST;
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||
|
||||
let method_idx = Select::new("SMTP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
config.method = match method_idx {
|
||||
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
||||
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
config.client_id = Text::new("SMTP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("SMTP OAuth 2.0 client secret")
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
let default_auth_url = autoconfig_oauth2
|
||||
.map(|o| o.auth_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.auth_url = Text::new("SMTP OAuth 2.0 authorization URL")
|
||||
.with_default(&default_auth_url)
|
||||
.prompt()?;
|
||||
|
||||
let default_token_url = autoconfig_oauth2
|
||||
.map(|o| o.token_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.token_url = Text::new("SMTP OAuth 2.0 token URL")
|
||||
.with_default(&default_token_url)
|
||||
.prompt()?;
|
||||
|
||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(match &autoconfig_scopes {
|
||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?
|
||||
.map(ToOwned::to_owned),
|
||||
None => Some(Text::new(prompt).prompt()?).filter(|scope| !scope.is_empty()),
|
||||
let default_oauth2_enabled = autoconfig_server
|
||||
.and_then(|smtp| {
|
||||
smtp.authentication_type()
|
||||
.into_iter()
|
||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||
})
|
||||
};
|
||||
.filter(|_| autoconfig_oauth2.is_some())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(scope) = prompt_scope("SMTP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(default_oauth2_enabled)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more SMTP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST;
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
|
||||
Ok(confirm)
|
||||
};
|
||||
let method_idx = Select::new("SMTP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
config.method = match method_idx {
|
||||
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
||||
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
config.client_id = Text::new("SMTP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("SMTP OAuth 2.0 client secret")
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
let default_auth_url = autoconfig_oauth2
|
||||
.map(|o| o.auth_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.auth_url = Text::new("SMTP OAuth 2.0 authorization URL")
|
||||
.with_default(&default_auth_url)
|
||||
.prompt()?;
|
||||
|
||||
let default_token_url = autoconfig_oauth2
|
||||
.map(|o| o.token_url().to_owned())
|
||||
.unwrap_or_default();
|
||||
config.token_url = Text::new("SMTP OAuth 2.0 token URL")
|
||||
.with_default(&default_token_url)
|
||||
.prompt()?;
|
||||
|
||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(match &autoconfig_scopes {
|
||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?
|
||||
.map(ToOwned::to_owned),
|
||||
None => Some(Text::new(prompt).prompt()?).filter(|scope| !scope.is_empty()),
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("SMTP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more SMTP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
Ok(confirm)
|
||||
};
|
||||
|
||||
wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
};
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
}
|
||||
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
|
||||
crate::wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token"))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
SmtpAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
let secret_idx = Select::new("SMTP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let secret = match secret_idx {
|
||||
Some(sec) if sec == KEYRING => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("SMTP password")?)
|
||||
.await?;
|
||||
secret
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
Some(sec) if sec == RAW => Secret::new_raw(prompt::passwd("SMTP password")?),
|
||||
Some(sec) if sec == CMD => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-smtp-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
SmtpAuthConfig::Passwd(PasswdConfig(secret))
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token = Secret::try_new_keyring_entry(format!(
|
||||
"{account_name}-smtp-oauth2-refresh-token"
|
||||
))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
SmtpAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
configure_passwd(account_name).await?
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "oauth2"))]
|
||||
let auth = configure_passwd(account_name).await?;
|
||||
|
||||
let config = SmtpConfig {
|
||||
host,
|
||||
port,
|
||||
|
@ -319,188 +315,30 @@ pub(crate) async fn configure(
|
|||
Ok(BackendConfig::Smtp(config))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "account-discovery"))]
|
||||
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> {
|
||||
use inquire::{validator::MinLengthValidator, Confirm, Password, Select, Text};
|
||||
pub(crate) async fn configure_passwd(account_name: &str) -> Result<SmtpAuthConfig> {
|
||||
use inquire::{Select, Text};
|
||||
|
||||
let default_host = format!("smtp.{}", email.rsplit_once('@').unwrap().1);
|
||||
|
||||
let host = Text::new("SMTP hostname")
|
||||
.with_default(&default_host)
|
||||
.prompt()?;
|
||||
|
||||
let encryption_idx = Select::new("SMTP encryption", ENCRYPTIONS.to_vec())
|
||||
let secret_idx = Select::new("SMTP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let (encryption, default_port) = match encryption_idx {
|
||||
Some(SmtpEncryptionKind::Tls) => (Some(SmtpEncryptionKind::Tls), 465),
|
||||
Some(SmtpEncryptionKind::StartTls) => (Some(SmtpEncryptionKind::StartTls), 587),
|
||||
_ => (Some(SmtpEncryptionKind::None), 25),
|
||||
let secret = match secret_idx {
|
||||
#[cfg(feature = "keyring")]
|
||||
Some(sec) if sec == KEYRING => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("SMTP password")?)
|
||||
.await?;
|
||||
secret
|
||||
}
|
||||
Some(sec) if sec == RAW => Secret::new_raw(prompt::passwd("SMTP password")?),
|
||||
Some(sec) if sec == CMD => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-smtp-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let port = Text::new("SMTP port")
|
||||
.with_validators(&[
|
||||
Box::new(MinLengthValidator::new(1)),
|
||||
Box::new(U16Validator {}),
|
||||
])
|
||||
.with_default(&default_port.to_string())
|
||||
.prompt()
|
||||
.map(|input| input.parse::<u16>().unwrap())?;
|
||||
|
||||
let default_login = email.to_owned();
|
||||
|
||||
let login = Text::new("SMTP login")
|
||||
.with_default(&default_login)
|
||||
.prompt()?;
|
||||
|
||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let auth = if oauth2_enabled {
|
||||
let mut config = OAuth2Config::default();
|
||||
let redirect_host = OAuth2Config::LOCALHOST.to_owned();
|
||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||
|
||||
let method_idx = Select::new("SMTP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
config.method = match method_idx {
|
||||
Some(XOAUTH2) => OAuth2Method::XOAuth2,
|
||||
Some(OAUTHBEARER) => OAuth2Method::OAuthBearer,
|
||||
_ => OAuth2Method::XOAuth2,
|
||||
};
|
||||
|
||||
config.client_id = Text::new("SMTP OAuth 2.0 client id").prompt()?;
|
||||
|
||||
let client_secret: String = Password::new("SMTP OAuth 2.0 client secret")
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
.prompt()?;
|
||||
config.client_secret =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
|
||||
config
|
||||
.client_secret
|
||||
.set_only_keyring(&client_secret)
|
||||
.await?;
|
||||
|
||||
config.auth_url = Text::new("SMTP OAuth 2.0 authorization URL").prompt()?;
|
||||
|
||||
config.token_url = Text::new("SMTP OAuth 2.0 token URL").prompt()?;
|
||||
|
||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
||||
Ok(Some(Text::new(prompt).prompt()?.to_owned()).filter(|scope| !scope.is_empty()))
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("SMTP OAuth 2.0 main scope")? {
|
||||
config.scopes = OAuth2Scopes::Scope(scope);
|
||||
}
|
||||
|
||||
let confirm_additional_scope = || -> Result<bool> {
|
||||
let confirm = Confirm::new("Would you like to add more SMTP OAuth 2.0 scopes?")
|
||||
.with_default(false)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(confirm)
|
||||
};
|
||||
|
||||
while confirm_additional_scope()? {
|
||||
let mut scopes = match config.scopes {
|
||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
||||
};
|
||||
|
||||
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
||||
scopes.push(scope)
|
||||
}
|
||||
|
||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
||||
}
|
||||
|
||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
||||
.with_default(true)
|
||||
.prompt_skippable()?
|
||||
.unwrap_or(true);
|
||||
|
||||
wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
||||
|
||||
let client = Client::new(
|
||||
config.client_id.clone(),
|
||||
client_secret,
|
||||
config.auth_url.clone(),
|
||||
config.token_url.clone(),
|
||||
)?
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port)
|
||||
.build()?;
|
||||
|
||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||
.with_redirect_host(redirect_host.to_owned())
|
||||
.with_redirect_port(redirect_port);
|
||||
|
||||
if config.pkce {
|
||||
auth_code_grant = auth_code_grant.with_pkce();
|
||||
}
|
||||
|
||||
for scope in config.scopes.clone() {
|
||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
||||
}
|
||||
|
||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
||||
|
||||
println!("{redirect_url}");
|
||||
println!();
|
||||
|
||||
let (access_token, refresh_token) = auth_code_grant
|
||||
.wait_for_redirection(&client, csrf_token)
|
||||
.await?;
|
||||
|
||||
config.access_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
|
||||
config.access_token.set_only_keyring(access_token).await?;
|
||||
|
||||
if let Some(refresh_token) = &refresh_token {
|
||||
config.refresh_token =
|
||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token"))?;
|
||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
||||
}
|
||||
|
||||
SmtpAuthConfig::OAuth2(config)
|
||||
} else {
|
||||
let secret_idx = Select::new("SMTP authentication strategy", SECRETS.to_vec())
|
||||
.with_starting_cursor(0)
|
||||
.prompt_skippable()?;
|
||||
|
||||
let secret = match secret_idx {
|
||||
Some(KEYRING) => {
|
||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
|
||||
secret
|
||||
.set_only_keyring(prompt::passwd("SMTP password")?)
|
||||
.await?;
|
||||
secret
|
||||
}
|
||||
Some(RAW) => Secret::new_raw(prompt::passwd("SMTP password")?),
|
||||
Some(CMD) => Secret::new_command(
|
||||
Text::new("Shell command")
|
||||
.with_default(&format!("pass show {account_name}-smtp-passwd"))
|
||||
.prompt()?,
|
||||
),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
SmtpAuthConfig::Passwd(PasswdConfig(secret))
|
||||
};
|
||||
|
||||
let config = SmtpConfig {
|
||||
host,
|
||||
port,
|
||||
encryption,
|
||||
login,
|
||||
auth,
|
||||
};
|
||||
|
||||
Ok(BackendConfig::Smtp(config))
|
||||
Ok(SmtpAuthConfig::Passwd(PasswdConfig(secret)))
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ pub(crate) fn passwd(prompt: &str) -> io::Result<String> {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "oauth2")]
|
||||
pub(crate) fn secret(prompt: &str) -> io::Result<String> {
|
||||
inquire::Password::new(prompt)
|
||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||
|
|
Loading…
Reference in a new issue