mirror of
https://github.com/soywod/himalaya.git
synced 2024-09-28 20:21:13 +00:00
fix cargo features
This commit is contained in:
parent
f9b92e6e7a
commit
d26314cd48
1710
Cargo.lock
generated
1710
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
34
Cargo.toml
34
Cargo.toml
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "himalaya"
|
name = "himalaya"
|
||||||
description = "CLI to manage emails"
|
description = "CLI to manage emails"
|
||||||
version = "1.0.0-beta.4"
|
version = "1.0.0"
|
||||||
authors = ["soywod <clement.douin@posteo.net>"]
|
authors = ["soywod <clement.douin@posteo.net>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
categories = ["command-line-interface", "command-line-utilities", "email"]
|
categories = ["command-line-utilities", "email"]
|
||||||
keywords = ["cli", "email", "imap", "smtp", "sync"]
|
keywords = ["cli", "email", "imap", "maildir", "smtp"]
|
||||||
homepage = "https://pimalaya.org/"
|
homepage = "https://pimalaya.org/"
|
||||||
documentation = "https://pimalaya.org/himalaya/cli/latest/"
|
documentation = "https://pimalaya.org/himalaya/cli/latest/"
|
||||||
repository = "https://github.com/soywod/himalaya/"
|
repository = "https://github.com/soywod/himalaya/"
|
||||||
|
@ -23,8 +23,9 @@ default = [
|
||||||
"smtp",
|
"smtp",
|
||||||
"sendmail",
|
"sendmail",
|
||||||
|
|
||||||
"account-discovery",
|
"wizard",
|
||||||
"account-sync",
|
# "keyring",
|
||||||
|
# "oauth2",
|
||||||
|
|
||||||
# "pgp-commands",
|
# "pgp-commands",
|
||||||
# "pgp-gpg",
|
# "pgp-gpg",
|
||||||
|
@ -37,8 +38,9 @@ notmuch = ["email-lib/notmuch"]
|
||||||
smtp = ["email-lib/smtp"]
|
smtp = ["email-lib/smtp"]
|
||||||
sendmail = ["email-lib/sendmail"]
|
sendmail = ["email-lib/sendmail"]
|
||||||
|
|
||||||
account-discovery = ["email-lib/account-discovery"]
|
keyring = ["email-lib/keyring", "secret-lib?/keyring-tokio"]
|
||||||
account-sync = ["email-lib/account-sync", "maildir"]
|
oauth2 = ["dep:oauth-lib", "email-lib/oauth2"]
|
||||||
|
wizard = ["dep:secret-lib", "email-lib/autoconfig"]
|
||||||
|
|
||||||
pgp = []
|
pgp = []
|
||||||
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "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]
|
[dependencies]
|
||||||
ariadne = "0.2"
|
ariadne = "0.2"
|
||||||
async-trait = "0.1"
|
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_complete = "4.4"
|
||||||
clap_mangen = "0.2"
|
clap_mangen = "0.2"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
|
@ -56,19 +58,19 @@ comfy-table = "7.1.1"
|
||||||
console = "0.15.2"
|
console = "0.15.2"
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
dirs = "4"
|
dirs = "4"
|
||||||
email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "tracing"] }
|
email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "thread", "tracing"] }
|
||||||
email_address = "0.2.4"
|
email_address = "0.2.4"
|
||||||
erased-serde = "0.3"
|
erased-serde = "0.3"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
inquire = "0.7.4"
|
inquire = "0.7.4"
|
||||||
mail-builder = "0.3"
|
mail-builder = "0.3"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
mml-lib = { version = "=1.0.12", default-features = false, features = ["derive"] }
|
mml-lib = { version = "=1.0.13", default-features = false, features = ["derive"] }
|
||||||
oauth-lib = "=0.1.1"
|
oauth-lib = { version = "=0.1.1", optional = true }
|
||||||
once_cell = "1.16"
|
once_cell = "1.16"
|
||||||
petgraph = "0.6"
|
petgraph = "0.6"
|
||||||
process-lib = { version = "=0.4.2", features = ["derive"] }
|
process-lib = { version = "=0.4.2", features = ["derive"] }
|
||||||
secret-lib = { version = "=0.4.4", features = ["derive"] }
|
secret-lib = { version = "=0.4.5", default-features = false, features = ["command", "derive"], optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde-toml-merge = "0.3"
|
serde-toml-merge = "0.3"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
@ -86,8 +88,6 @@ url = "2.2"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# WIP: transition from `imap` to `imap-{types,codec,client}`
|
email-lib = { path = "/home/soywod/sourcehut/pimalaya/email" }
|
||||||
email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
|
secret-lib = { path = "/home/soywod/sourcehut/pimalaya/secret" }
|
||||||
imap-client = { git = "https://github.com/soywod/imap-client.git" }
|
mml-lib = { path = "/home/soywod/sourcehut/pimalaya/mml" }
|
||||||
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…")?;
|
printer.log("Checking configuration integrity…")?;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config.clone().into_account_configs(account)?;
|
||||||
account,
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
true,
|
|
||||||
)?;
|
|
||||||
let used_backends = toml_account_config.get_used_backends();
|
let used_backends = toml_account_config.get_used_backends();
|
||||||
|
|
||||||
printer.log("Checking backend context integrity…")?;
|
printer.log("Checking backend context integrity…")?;
|
||||||
|
@ -76,7 +72,7 @@ impl AccountCheckUpCommand {
|
||||||
|
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
{
|
{
|
||||||
printer.print("Checking Notmuch integrity…")?;
|
printer.log("Checking Notmuch integrity…")?;
|
||||||
|
|
||||||
let notmuch = ctx_builder
|
let notmuch = ctx_builder
|
||||||
.notmuch
|
.notmuch
|
||||||
|
|
|
@ -42,6 +42,7 @@ impl AccountConfigureCommand {
|
||||||
if let Some(ref config) = account_config.imap {
|
if let Some(ref config) = account_config.imap {
|
||||||
let reset = match &config.auth {
|
let reset = match &config.auth {
|
||||||
ImapAuthConfig::Passwd(config) => config.reset().await,
|
ImapAuthConfig::Passwd(config) => config.reset().await,
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
ImapAuthConfig::OAuth2(config) => config.reset().await,
|
ImapAuthConfig::OAuth2(config) => config.reset().await,
|
||||||
};
|
};
|
||||||
if let Err(err) = reset {
|
if let Err(err) = reset {
|
||||||
|
@ -54,6 +55,7 @@ impl AccountConfigureCommand {
|
||||||
if let Some(ref config) = account_config.smtp {
|
if let Some(ref config) = account_config.smtp {
|
||||||
let reset = match &config.auth {
|
let reset = match &config.auth {
|
||||||
SmtpAuthConfig::Passwd(config) => config.reset().await,
|
SmtpAuthConfig::Passwd(config) => config.reset().await,
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
SmtpAuthConfig::OAuth2(config) => config.reset().await,
|
SmtpAuthConfig::OAuth2(config) => config.reset().await,
|
||||||
};
|
};
|
||||||
if let Err(err) = reset {
|
if let Err(err) = reset {
|
||||||
|
@ -74,6 +76,7 @@ impl AccountConfigureCommand {
|
||||||
ImapAuthConfig::Passwd(config) => {
|
ImapAuthConfig::Passwd(config) => {
|
||||||
config.configure(|| prompt::passwd("IMAP password")).await
|
config.configure(|| prompt::passwd("IMAP password")).await
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
ImapAuthConfig::OAuth2(config) => {
|
ImapAuthConfig::OAuth2(config) => {
|
||||||
config
|
config
|
||||||
.configure(|| prompt::secret("IMAP OAuth 2.0 client secret"))
|
.configure(|| prompt::secret("IMAP OAuth 2.0 client secret"))
|
||||||
|
@ -88,6 +91,7 @@ impl AccountConfigureCommand {
|
||||||
SmtpAuthConfig::Passwd(config) => {
|
SmtpAuthConfig::Passwd(config) => {
|
||||||
config.configure(|| prompt::passwd("SMTP password")).await
|
config.configure(|| prompt::passwd("SMTP password")).await
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
SmtpAuthConfig::OAuth2(config) => {
|
SmtpAuthConfig::OAuth2(config) => {
|
||||||
config
|
config
|
||||||
.configure(|| prompt::secret("SMTP OAuth 2.0 client secret"))
|
.configure(|| prompt::secret("SMTP OAuth 2.0 client secret"))
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
mod check_up;
|
mod check_up;
|
||||||
mod configure;
|
mod configure;
|
||||||
mod list;
|
mod list;
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
mod sync;
|
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use color_eyre::Result;
|
||||||
|
|
||||||
use crate::{config::TomlConfig, printer::Printer};
|
use crate::{config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use self::sync::AccountSyncCommand;
|
|
||||||
use self::{
|
use self::{
|
||||||
check_up::AccountCheckUpCommand, configure::AccountConfigureCommand, list::AccountListCommand,
|
check_up::AccountCheckUpCommand, configure::AccountConfigureCommand, list::AccountListCommand,
|
||||||
};
|
};
|
||||||
|
@ -30,10 +26,6 @@ pub enum AccountSubcommand {
|
||||||
|
|
||||||
#[command(alias = "lst")]
|
#[command(alias = "lst")]
|
||||||
List(AccountListCommand),
|
List(AccountListCommand),
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(alias = "synchronize", alias = "synchronise")]
|
|
||||||
Sync(AccountSyncCommand),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountSubcommand {
|
impl AccountSubcommand {
|
||||||
|
@ -43,8 +35,6 @@ impl AccountSubcommand {
|
||||||
Self::CheckUp(cmd) => cmd.execute(printer, config).await,
|
Self::CheckUp(cmd) => cmd.execute(printer, config).await,
|
||||||
Self::Configure(cmd) => cmd.execute(printer, config).await,
|
Self::Configure(cmd) => cmd.execute(printer, config).await,
|
||||||
Self::List(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,
|
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.
|
/// Represents all existing kind of account config.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
|
@ -55,8 +36,6 @@ pub struct TomlAccountConfig {
|
||||||
pub downloads_dir: Option<PathBuf>,
|
pub downloads_dir: Option<PathBuf>,
|
||||||
pub backend: Option<BackendKind>,
|
pub backend: Option<BackendKind>,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
pub sync: Option<SyncConfig>,
|
|
||||||
#[cfg(feature = "pgp")]
|
#[cfg(feature = "pgp")]
|
||||||
pub pgp: Option<PgpConfig>,
|
pub pgp: Option<PgpConfig>,
|
||||||
|
|
||||||
|
@ -79,13 +58,6 @@ pub struct TomlAccountConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn add_folder_kind(&self) -> Option<&BackendKind> {
|
||||||
self.folder
|
self.folder
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -150,14 +122,6 @@ impl TomlAccountConfig {
|
||||||
.or(self.backend.as_ref())
|
.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> {
|
pub fn add_flags_kind(&self) -> Option<&BackendKind> {
|
||||||
self.flag
|
self.flag
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod arg;
|
pub mod arg;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
||||||
use comfy_table::{presets, Attribute, Cell, Color, ContentArrangement, Row, Table};
|
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};
|
use color_eyre::{eyre::OptionExt, Result};
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use email_address::EmailAddress;
|
use email_address::EmailAddress;
|
||||||
use inquire::validator::{ErrorMessage, Validation};
|
use inquire::validator::{ErrorMessage, Validation};
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
#[cfg(feature = "account-discovery")]
|
|
||||||
use crate::wizard_warn;
|
use crate::wizard_warn;
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{self, config::BackendConfig, BackendKind},
|
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();
|
let addr = EmailAddress::from_str(&config.email).unwrap();
|
||||||
|
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
let autoconfig_email = config.email.to_owned();
|
let autoconfig_email = config.email.to_owned();
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
let autoconfig = tokio::spawn(async move {
|
let autoconfig =
|
||||||
email::account::discover::from_addr(&autoconfig_email)
|
tokio::spawn(async move { email::autoconfig::from_addr(&autoconfig_email).await.ok() });
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
let account_name = inquire::Text::new("Account name: ")
|
let account_name = inquire::Text::new("Account name: ")
|
||||||
.with_default(
|
.with_default(
|
||||||
|
@ -65,12 +58,12 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
||||||
));
|
));
|
||||||
|
|
||||||
let email = &config.email;
|
let email = &config.email;
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
let autoconfig = autoconfig.await?;
|
let autoconfig = autoconfig.await?;
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
let autoconfig = autoconfig.as_ref();
|
let autoconfig = autoconfig.as_ref();
|
||||||
|
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
if let Some(config) = autoconfig {
|
if let Some(config) = autoconfig {
|
||||||
if config.is_gmail() {
|
if config.is_gmail() {
|
||||||
println!();
|
println!();
|
||||||
|
@ -83,7 +76,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
||||||
match backend::wizard::configure(
|
match backend::wizard::configure(
|
||||||
&account_name,
|
&account_name,
|
||||||
email,
|
email,
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
autoconfig,
|
autoconfig,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -109,7 +102,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
||||||
match backend::wizard::configure_sender(
|
match backend::wizard::configure_sender(
|
||||||
&account_name,
|
&account_name,
|
||||||
email,
|
email,
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
autoconfig,
|
autoconfig,
|
||||||
)
|
)
|
||||||
.await?
|
.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)))
|
Ok(Some((account_name, config)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use std::{fmt::Display, ops::Deref, sync::Arc};
|
use std::{fmt::Display, ops::Deref, sync::Arc};
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
use email::imap::{ImapContextBuilder, ImapContextSync};
|
use email::imap::{ImapContextBuilder, ImapContextSync};
|
||||||
|
@ -24,7 +27,6 @@ use email::{
|
||||||
get::GetEnvelope,
|
get::GetEnvelope,
|
||||||
list::{ListEnvelopes, ListEnvelopesOptions},
|
list::{ListEnvelopes, ListEnvelopesOptions},
|
||||||
thread::ThreadEnvelopes,
|
thread::ThreadEnvelopes,
|
||||||
watch::WatchEnvelopes,
|
|
||||||
Id, SingleId,
|
Id, SingleId,
|
||||||
},
|
},
|
||||||
flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags, Flag, Flags},
|
flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags, Flag, Flags},
|
||||||
|
@ -59,8 +61,6 @@ pub enum BackendKind {
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Imap,
|
Imap,
|
||||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
|
||||||
ImapCache,
|
|
||||||
|
|
||||||
#[cfg(feature = "maildir")]
|
#[cfg(feature = "maildir")]
|
||||||
Maildir,
|
Maildir,
|
||||||
|
@ -85,8 +85,6 @@ impl Display for BackendKind {
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Self::Imap => "IMAP",
|
Self::Imap => "IMAP",
|
||||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
|
||||||
Self::ImapCache => "IMAP cache",
|
|
||||||
|
|
||||||
#[cfg(feature = "maildir")]
|
#[cfg(feature = "maildir")]
|
||||||
Self::Maildir => "Maildir",
|
Self::Maildir => "Maildir",
|
||||||
|
@ -112,9 +110,6 @@ pub struct BackendContextBuilder {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
pub imap: Option<ImapContextBuilder>,
|
pub imap: Option<ImapContextBuilder>,
|
||||||
|
|
||||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
|
||||||
pub imap_cache: Option<MaildirContextBuilder>,
|
|
||||||
|
|
||||||
#[cfg(feature = "maildir")]
|
#[cfg(feature = "maildir")]
|
||||||
pub maildir: Option<MaildirContextBuilder>,
|
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")]
|
#[cfg(feature = "maildir")]
|
||||||
maildir: toml_account_config
|
maildir: toml_account_config
|
||||||
.maildir
|
.maildir
|
||||||
|
@ -227,11 +202,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.add_folder_kind() {
|
match self.toml_account_config.add_folder_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.add_folder_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.add_folder_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.add_folder_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -244,11 +214,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.list_folders_kind() {
|
match self.toml_account_config.list_folders_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.list_folders_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.list_folders_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.list_folders_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -261,11 +226,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.expunge_folder_kind() {
|
match self.toml_account_config.expunge_folder_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.expunge_folder_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.expunge_folder_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.expunge_folder_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -278,11 +238,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.purge_folder_kind() {
|
match self.toml_account_config.purge_folder_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.purge_folder_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.purge_folder_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.purge_folder_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -295,11 +250,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.delete_folder_kind() {
|
match self.toml_account_config.delete_folder_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.delete_folder_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.delete_folder_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.delete_folder_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -312,11 +262,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.get_envelope_kind() {
|
match self.toml_account_config.get_envelope_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.get_envelope_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.get_envelope_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.get_envelope_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -329,11 +274,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.list_envelopes_kind() {
|
match self.toml_account_config.list_envelopes_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.list_envelopes_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.list_envelopes_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.list_envelopes_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -346,11 +286,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.thread_envelopes_kind() {
|
match self.toml_account_config.thread_envelopes_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.thread_envelopes_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[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>> {
|
fn add_flags(&self) -> Option<BackendFeature<Self::Context, dyn AddFlags>> {
|
||||||
match self.toml_account_config.add_flags_kind() {
|
match self.toml_account_config.add_flags_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.add_flags_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.add_flags_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.add_flags_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -397,11 +310,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.set_flags_kind() {
|
match self.toml_account_config.set_flags_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.set_flags_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.set_flags_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.set_flags_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -414,11 +322,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.remove_flags_kind() {
|
match self.toml_account_config.remove_flags_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.remove_flags_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.remove_flags_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.remove_flags_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -431,11 +334,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.add_message_kind() {
|
match self.toml_account_config.add_message_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.add_message_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.add_message_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.add_message_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -458,11 +356,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.peek_messages_kind() {
|
match self.toml_account_config.peek_messages_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.peek_messages_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.peek_messages_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.peek_messages_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -475,11 +368,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.get_messages_kind() {
|
match self.toml_account_config.get_messages_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.get_messages_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.get_messages_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.get_messages_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -492,11 +380,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.copy_messages_kind() {
|
match self.toml_account_config.copy_messages_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.copy_messages_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.copy_messages_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.copy_messages_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -509,11 +392,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.move_messages_kind() {
|
match self.toml_account_config.move_messages_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.move_messages_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.move_messages_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.move_messages_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -526,11 +404,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
match self.toml_account_config.delete_messages_kind() {
|
match self.toml_account_config.delete_messages_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendKind::Imap) => self.delete_messages_with_some(&self.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")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendKind::Maildir) => self.delete_messages_with_some(&self.maildir),
|
Some(BackendKind::Maildir) => self.delete_messages_with_some(&self.maildir),
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
@ -547,11 +420,6 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
ctx.imap = Some(imap.build().await?);
|
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")]
|
#[cfg(feature = "maildir")]
|
||||||
if let Some(maildir) = self.maildir {
|
if let Some(maildir) = self.maildir {
|
||||||
ctx.maildir = Some(maildir.build().await?);
|
ctx.maildir = Some(maildir.build().await?);
|
||||||
|
@ -581,9 +449,6 @@ pub struct BackendContext {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
pub imap: Option<ImapContextSync>,
|
pub imap: Option<ImapContextSync>,
|
||||||
|
|
||||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
|
||||||
pub imap_cache: Option<MaildirContextSync>,
|
|
||||||
|
|
||||||
#[cfg(feature = "maildir")]
|
#[cfg(feature = "maildir")]
|
||||||
pub maildir: Option<MaildirContextSync>,
|
pub maildir: Option<MaildirContextSync>,
|
||||||
|
|
||||||
|
@ -663,37 +528,28 @@ impl Backend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(self))]
|
||||||
fn build_id_mapper(
|
fn build_id_mapper(
|
||||||
&self,
|
&self,
|
||||||
folder: &str,
|
folder: &str,
|
||||||
backend_kind: Option<&BackendKind>,
|
backend_kind: Option<&BackendKind>,
|
||||||
) -> Result<IdMapper> {
|
) -> Result<IdMapper> {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut id_mapper = IdMapper::Dummy;
|
#[cfg(feature = "maildir")]
|
||||||
|
if let Some(BackendKind::Maildir) = backend_kind {
|
||||||
match backend_kind {
|
if let Some(_) = &self.toml_account_config.maildir {
|
||||||
#[cfg(feature = "maildir")]
|
return Ok(IdMapper::new(&self.backend.account_config, folder)?);
|
||||||
Some(BackendKind::Maildir) => {
|
|
||||||
if let Some(_) = &self.toml_account_config.maildir {
|
|
||||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
#[cfg(feature = "notmuch")]
|
||||||
Some(BackendKind::ImapCache) => {
|
if let Some(BackendKind::Notmuch) = backend_kind {
|
||||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
if let Some(_) = &self.toml_account_config.notmuch {
|
||||||
|
return Ok(IdMapper::new(&self.backend.account_config, folder)?);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "notmuch")]
|
Ok(IdMapper::Dummy)
|
||||||
Some(BackendKind::Notmuch) => {
|
|
||||||
if let Some(_) = &self.toml_account_config.notmuch {
|
|
||||||
id_mapper = IdMapper::new(&self.backend.account_config, folder)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(id_mapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_envelopes(
|
pub async fn list_envelopes(
|
||||||
|
@ -868,11 +724,6 @@ impl Backend {
|
||||||
self.backend.send_message_then_save_copy(msg).await?;
|
self.backend.send_message_then_save_copy(msg).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_envelopes(&self, folder: &str) -> Result<()> {
|
|
||||||
self.backend.watch_envelopes(folder).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Backend {
|
impl Deref for Backend {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
#[cfg(feature = "account-discovery")]
|
use email::autoconfig::config::AutoConfig;
|
||||||
use email::account::discover::config::AutoConfig;
|
|
||||||
use inquire::Select;
|
use inquire::Select;
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
|
@ -35,7 +34,7 @@ const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
||||||
pub(crate) async fn configure(
|
pub(crate) async fn configure(
|
||||||
account_name: &str,
|
account_name: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
#[cfg(feature = "account-discovery")] autoconfig: Option<&AutoConfig>,
|
autoconfig: Option<&AutoConfig>,
|
||||||
) -> Result<Option<BackendConfig>> {
|
) -> Result<Option<BackendConfig>> {
|
||||||
let kind = Select::new("Default email backend", DEFAULT_BACKEND_KINDS.to_vec())
|
let kind = Select::new("Default email backend", DEFAULT_BACKEND_KINDS.to_vec())
|
||||||
.with_starting_cursor(0)
|
.with_starting_cursor(0)
|
||||||
|
@ -47,7 +46,7 @@ pub(crate) async fn configure(
|
||||||
imap::wizard::configure(
|
imap::wizard::configure(
|
||||||
account_name,
|
account_name,
|
||||||
email,
|
email,
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
autoconfig,
|
autoconfig,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
@ -65,7 +64,7 @@ pub(crate) async fn configure(
|
||||||
pub(crate) async fn configure_sender(
|
pub(crate) async fn configure_sender(
|
||||||
account_name: &str,
|
account_name: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
#[cfg(feature = "account-discovery")] autoconfig: Option<&AutoConfig>,
|
autoconfig: Option<&AutoConfig>,
|
||||||
) -> Result<Option<BackendConfig>> {
|
) -> Result<Option<BackendConfig>> {
|
||||||
let kind = Select::new(
|
let kind = Select::new(
|
||||||
"Backend for sending messages",
|
"Backend for sending messages",
|
||||||
|
@ -80,7 +79,7 @@ pub(crate) async fn configure_sender(
|
||||||
smtp::wizard::configure(
|
smtp::wizard::configure(
|
||||||
account_name,
|
account_name,
|
||||||
email,
|
email,
|
||||||
#[cfg(feature = "account-discovery")]
|
#[cfg(feature = "wizard")]
|
||||||
autoconfig,
|
autoconfig,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
|
||||||
use color_eyre::{
|
use color_eyre::{
|
||||||
|
@ -7,7 +8,7 @@ use color_eyre::{
|
||||||
use dirs::{config_dir, home_dir};
|
use dirs::{config_dir, home_dir};
|
||||||
use email::{
|
use email::{
|
||||||
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
|
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::{Deserialize, Serialize};
|
||||||
use serde_toml_merge::merge;
|
use serde_toml_merge::merge;
|
||||||
|
@ -16,9 +17,9 @@ use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
||||||
use toml::{self, Value};
|
use toml::{self, Value};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
use crate::account::config::TomlAccountConfig;
|
||||||
use crate::backend::BackendKind;
|
#[cfg(feature = "wizard")]
|
||||||
use crate::{account::config::TomlAccountConfig, wizard_warn};
|
use crate::wizard_warn;
|
||||||
|
|
||||||
/// Represents the user config file.
|
/// Represents the user config file.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
@ -84,9 +85,8 @@ impl TomlConfig {
|
||||||
/// program stops.
|
/// program stops.
|
||||||
///
|
///
|
||||||
/// NOTE: the wizard can only be used with interactive shells.
|
/// NOTE: the wizard can only be used with interactive shells.
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
||||||
use std::process;
|
|
||||||
|
|
||||||
wizard_warn!("Cannot find existing configuration at {path:?}.");
|
wizard_warn!("Cannot find existing configuration at {path:?}.");
|
||||||
|
|
||||||
let confirm = inquire::Confirm::new("Would you like to create one with the wizard? ")
|
let confirm = inquire::Confirm::new("Would you like to create one with the wizard? ")
|
||||||
|
@ -95,10 +95,15 @@ impl TomlConfig {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if !confirm {
|
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.
|
/// Read and parse the TOML configuration from default paths.
|
||||||
|
@ -182,14 +187,14 @@ impl TomlConfig {
|
||||||
.ok_or_else(|| eyre!("cannot find account {name}")),
|
.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() {
|
if let Some(imap_config) = toml_account_config.imap.as_mut() {
|
||||||
imap_config
|
imap_config
|
||||||
.auth
|
.auth
|
||||||
.replace_undefined_keyring_entries(&account_name)?;
|
.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() {
|
if let Some(smtp_config) = toml_account_config.smtp.as_mut() {
|
||||||
smtp_config
|
smtp_config
|
||||||
.auth
|
.auth
|
||||||
|
@ -203,21 +208,8 @@ impl TomlConfig {
|
||||||
pub fn into_account_configs(
|
pub fn into_account_configs(
|
||||||
self,
|
self,
|
||||||
account_name: Option<&str>,
|
account_name: Option<&str>,
|
||||||
#[cfg(feature = "account-sync")] disable_cache: bool,
|
|
||||||
) -> Result<(Arc<TomlAccountConfig>, Arc<AccountConfig>)> {
|
) -> Result<(Arc<TomlAccountConfig>, Arc<AccountConfig>)> {
|
||||||
#[cfg_attr(not(feature = "account-sync"), allow(unused_mut))]
|
let (account_name, toml_account_config) = self.into_toml_account_config(account_name)?;
|
||||||
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 config = Config {
|
let config = Config {
|
||||||
display_name: self.display_name,
|
display_name: self.display_name,
|
||||||
|
@ -239,31 +231,19 @@ impl TomlConfig {
|
||||||
folder: config.folder.map(|c| FolderConfig {
|
folder: config.folder.map(|c| FolderConfig {
|
||||||
aliases: c.alias,
|
aliases: c.alias,
|
||||||
list: c.list.map(|c| c.remote),
|
list: c.list.map(|c| c.remote),
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
sync: c.sync,
|
|
||||||
}),
|
}),
|
||||||
envelope: config.envelope.map(|c| EnvelopeConfig {
|
envelope: config.envelope.map(|c| EnvelopeConfig {
|
||||||
list: c.list.map(|c| c.remote),
|
list: c.list.map(|c| c.remote),
|
||||||
thread: c.thread.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 {
|
message: config.message.map(|c| MessageConfig {
|
||||||
read: c.read.map(|c| c.remote),
|
read: c.read.map(|c| c.remote),
|
||||||
write: c.write.map(|c| c.remote),
|
write: c.write.map(|c| c.remote),
|
||||||
send: c.send.map(|c| c.remote),
|
send: c.send.map(|c| c.remote),
|
||||||
delete: c.delete.map(Into::into),
|
delete: c.delete.map(Into::into),
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
sync: c.sync,
|
|
||||||
}),
|
}),
|
||||||
template: config.template,
|
template: config.template,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
sync: config.sync.map(Into::into),
|
|
||||||
#[cfg(feature = "pgp")]
|
#[cfg(feature = "pgp")]
|
||||||
pgp: config.pgp,
|
pgp: config.pgp,
|
||||||
},
|
},
|
||||||
|
|
|
@ -152,9 +152,6 @@ fn pretty_serialize(config: &TomlConfig) -> Result<String> {
|
||||||
#[cfg(feature = "sendmail")]
|
#[cfg(feature = "sendmail")]
|
||||||
set_table_dotted(item, "sendmail");
|
set_table_dotted(item, "sendmail");
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
set_table_dotted(item, "sync");
|
|
||||||
|
|
||||||
#[cfg(feature = "pgp")]
|
#[cfg(feature = "pgp")]
|
||||||
set_table_dotted(item, "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")]
|
// #[cfg(feature = "imap")]
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn pretty_serialize_imap_passwd_cmd() {
|
// fn pretty_serialize_imap_passwd_cmd() {
|
||||||
|
|
|
@ -8,8 +8,6 @@ use email::{
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
envelope::EnvelopesTable, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
envelope::EnvelopesTable, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||||
|
@ -37,10 +35,6 @@ pub struct ListEnvelopesCommand {
|
||||||
#[arg(long, short = 's', value_name = "NUMBER")]
|
#[arg(long, short = 's', value_name = "NUMBER")]
|
||||||
pub page_size: Option<usize>,
|
pub page_size: Option<usize>,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
|
|
||||||
|
@ -130,8 +124,6 @@ impl Default for ListEnvelopesCommand {
|
||||||
folder: Default::default(),
|
folder: Default::default(),
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: Default::default(),
|
page_size: Default::default(),
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
cache: Default::default(),
|
|
||||||
account: Default::default(),
|
account: Default::default(),
|
||||||
query: Default::default(),
|
query: Default::default(),
|
||||||
table_max_width: 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<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing list envelopes command");
|
info!("executing list envelopes command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let page = 1.max(self.page) - 1;
|
let page = 1.max(self.page) - 1;
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub mod watch;
|
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
|
||||||
use crate::{config::TomlConfig, printer::Printer};
|
use crate::{config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
use self::{
|
use self::{list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand};
|
||||||
list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand, watch::WatchEnvelopesCommand,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Manage envelopes.
|
/// Manage envelopes.
|
||||||
///
|
///
|
||||||
|
@ -24,9 +21,6 @@ pub enum EnvelopeSubcommand {
|
||||||
|
|
||||||
#[command()]
|
#[command()]
|
||||||
Thread(ThreadEnvelopesCommand),
|
Thread(ThreadEnvelopesCommand),
|
||||||
|
|
||||||
#[command()]
|
|
||||||
Watch(WatchEnvelopesCommand),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnvelopeSubcommand {
|
impl EnvelopeSubcommand {
|
||||||
|
@ -35,7 +29,6 @@ impl EnvelopeSubcommand {
|
||||||
match self {
|
match self {
|
||||||
Self::List(cmd) => cmd.execute(printer, config).await,
|
Self::List(cmd) => cmd.execute(printer, config).await,
|
||||||
Self::Thread(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 std::process::exit;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
envelope::EnvelopesTree, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
envelope::EnvelopesTree, folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||||
|
@ -24,10 +22,6 @@ pub struct ThreadEnvelopesCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub folder: FolderNameOptionalFlag,
|
pub folder: FolderNameOptionalFlag,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
|
|
||||||
|
@ -43,11 +37,9 @@ impl ThreadEnvelopesCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing thread envelopes command");
|
info!("executing thread envelopes command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let thread_envelopes_kind = toml_account_config.thread_envelopes_kind();
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
@ -9,10 +7,7 @@ use crate::backend::BackendKind;
|
||||||
pub struct EnvelopeConfig {
|
pub struct EnvelopeConfig {
|
||||||
pub list: Option<ListEnvelopesConfig>,
|
pub list: Option<ListEnvelopesConfig>,
|
||||||
pub thread: Option<ThreadEnvelopesConfig>,
|
pub thread: Option<ThreadEnvelopesConfig>,
|
||||||
pub watch: Option<WatchEnvelopesConfig>,
|
|
||||||
pub get: Option<GetEnvelopeConfig>,
|
pub get: Option<GetEnvelopeConfig>,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
pub sync: Option<EnvelopeSyncConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnvelopeConfig {
|
impl EnvelopeConfig {
|
||||||
|
@ -23,10 +18,6 @@ impl EnvelopeConfig {
|
||||||
kinds.extend(list.get_used_backends());
|
kinds.extend(list.get_used_backends());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(watch) = &self.watch {
|
|
||||||
kinds.extend(watch.get_used_backends());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(get) = &self.get {
|
if let Some(get) = &self.get {
|
||||||
kinds.extend(get.get_used_backends());
|
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)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct GetEnvelopeConfig {
|
pub struct GetEnvelopeConfig {
|
||||||
pub backend: Option<BackendKind>,
|
pub backend: Option<BackendKind>,
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -26,10 +24,6 @@ pub struct FlagAddCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub args: IdsAndFlagsArgs,
|
pub args: IdsAndFlagsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl FlagAddCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_flags_kind = toml_account_config.add_flags_kind();
|
let add_flags_kind = toml_account_config.add_flags_kind();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -26,10 +24,6 @@ pub struct FlagRemoveCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub args: IdsAndFlagsArgs,
|
pub args: IdsAndFlagsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl FlagRemoveCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let remove_flags_kind = toml_account_config.remove_flags_kind();
|
let remove_flags_kind = toml_account_config.remove_flags_kind();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -26,10 +24,6 @@ pub struct FlagSetCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub args: IdsAndFlagsArgs,
|
pub args: IdsAndFlagsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl FlagSetCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
let (ids, flags) = into_tuple(&self.args.ids_and_flags);
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let set_flags_kind = toml_account_config.set_flags_kind();
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
@ -10,8 +8,6 @@ pub struct FlagConfig {
|
||||||
pub add: Option<FlagAddConfig>,
|
pub add: Option<FlagAddConfig>,
|
||||||
pub set: Option<FlagSetConfig>,
|
pub set: Option<FlagSetConfig>,
|
||||||
pub remove: Option<FlagRemoveConfig>,
|
pub remove: Option<FlagRemoveConfig>,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
pub sync: Option<FlagSyncConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlagConfig {
|
impl FlagConfig {
|
||||||
|
|
|
@ -5,8 +5,6 @@ use std::{fs, path::PathBuf};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||||
|
@ -25,10 +23,6 @@ pub struct AttachmentDownloadCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub envelopes: EnvelopeIdsArgs,
|
pub envelopes: EnvelopeIdsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl AttachmentDownloadCommand {
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let ids = &self.envelopes.ids;
|
let ids = &self.envelopes.ids;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -26,10 +24,6 @@ pub struct MessageCopyCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub envelopes: EnvelopeIdsArgs,
|
pub envelopes: EnvelopeIdsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -42,11 +36,9 @@ impl MessageCopyCommand {
|
||||||
let target = &self.target_folder.name;
|
let target = &self.target_folder.name;
|
||||||
let ids = &self.envelopes.ids;
|
let ids = &self.envelopes.ids;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let copy_messages_kind = toml_account_config.copy_messages_kind();
|
let copy_messages_kind = toml_account_config.copy_messages_kind();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
envelope::arg::ids::EnvelopeIdsArgs, folder::arg::name::FolderNameOptionalFlag,
|
||||||
|
@ -25,10 +23,6 @@ pub struct MessageDeleteCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub envelopes: EnvelopeIdsArgs,
|
pub envelopes: EnvelopeIdsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl MessageDeleteCommand {
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let ids = &self.envelopes.ids;
|
let ids = &self.envelopes.ids;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let delete_messages_kind = toml_account_config.delete_messages_kind();
|
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 email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -36,10 +34,6 @@ pub struct MessageForwardCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: MessageRawBodyArg,
|
pub body: MessageRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -50,11 +44,9 @@ impl MessageForwardCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
let add_message_kind = toml_account_config.add_message_kind();
|
||||||
let send_message_kind = toml_account_config.send_message_kind();
|
let send_message_kind = toml_account_config.send_message_kind();
|
||||||
|
|
|
@ -5,8 +5,6 @@ use mail_builder::MessageBuilder;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig, printer::Printer,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig, printer::Printer,
|
||||||
ui::editor,
|
ui::editor,
|
||||||
|
@ -24,10 +22,6 @@ pub struct MessageMailtoCommand {
|
||||||
#[arg()]
|
#[arg()]
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -36,8 +30,6 @@ impl MessageMailtoCommand {
|
||||||
pub fn new(url: &str) -> Result<Self> {
|
pub fn new(url: &str) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
url: Url::parse(url)?,
|
url: Url::parse(url)?,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
cache: Default::default(),
|
|
||||||
account: Default::default(),
|
account: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -45,11 +37,9 @@ impl MessageMailtoCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing mailto message command");
|
info!("executing mailto message command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
let add_message_kind = toml_account_config.add_message_kind();
|
||||||
let send_message_kind = toml_account_config.send_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 email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
|
@ -27,10 +25,6 @@ pub struct MessageMoveCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub envelopes: EnvelopeIdsArgs,
|
pub envelopes: EnvelopeIdsArgs,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -43,11 +37,9 @@ impl MessageMoveCommand {
|
||||||
let target = &self.target_folder.name;
|
let target = &self.target_folder.name;
|
||||||
let ids = &self.envelopes.ids;
|
let ids = &self.envelopes.ids;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let move_messages_kind = toml_account_config.move_messages_kind();
|
let move_messages_kind = toml_account_config.move_messages_kind();
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
||||||
use mml::message::FilterParts;
|
use mml::message::FilterParts;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
|
@ -70,10 +68,6 @@ pub struct MessageReadCommand {
|
||||||
#[arg(conflicts_with = "no_headers")]
|
#[arg(conflicts_with = "no_headers")]
|
||||||
pub headers: Vec<String>,
|
pub headers: Vec<String>,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -85,11 +79,9 @@ impl MessageReadCommand {
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let ids = &self.envelopes.ids;
|
let ids = &self.envelopes.ids;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
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 email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -39,10 +37,6 @@ pub struct MessageReplyCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: MessageRawBodyArg,
|
pub body: MessageRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -52,11 +46,9 @@ impl MessageReplyCommand {
|
||||||
info!("executing reply message command");
|
info!("executing reply message command");
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
let add_message_kind = toml_account_config.add_message_kind();
|
||||||
let send_message_kind = toml_account_config.send_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 std::io::{self, BufRead, IsTerminal};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
|
@ -23,10 +21,6 @@ pub struct MessageSaveCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub message: MessageRawArg,
|
pub message: MessageRawArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -37,11 +31,9 @@ impl MessageSaveCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
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 std::io::{self, BufRead, IsTerminal};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
message::arg::MessageRawArg, printer::Printer,
|
message::arg::MessageRawArg, printer::Printer,
|
||||||
|
@ -20,10 +18,6 @@ pub struct MessageSendCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub message: MessageRawArg,
|
pub message: MessageRawArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -32,11 +26,9 @@ impl MessageSendCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing send message command");
|
info!("executing send message command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
||||||
toml_account_config
|
toml_account_config
|
||||||
|
|
|
@ -4,8 +4,6 @@ use email::backend::feature::BackendFeatureSource;
|
||||||
use mml::message::FilterParts;
|
use mml::message::FilterParts;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::envelope::arg::ids::EnvelopeIdArg;
|
use crate::envelope::arg::ids::EnvelopeIdArg;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -71,10 +69,6 @@ pub struct MessageThreadCommand {
|
||||||
#[arg(conflicts_with = "no_headers")]
|
#[arg(conflicts_with = "no_headers")]
|
||||||
pub headers: Vec<String>,
|
pub headers: Vec<String>,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -86,11 +80,9 @@ impl MessageThreadCommand {
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let id = &self.envelope.id;
|
let id = &self.envelope.id;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
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 email::{backend::feature::BackendFeatureSource, message::Message};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -28,10 +26,6 @@ pub struct MessageWriteCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: MessageRawBodyArg,
|
pub body: MessageRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -40,11 +34,9 @@ impl MessageWriteCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing write message command");
|
info!("executing write message command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
let add_message_kind = toml_account_config.add_message_kind();
|
||||||
let send_message_kind = toml_account_config.send_message_kind();
|
let send_message_kind = toml_account_config.send_message_kind();
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use email::message::delete::config::DeleteMessageStyle;
|
use email::message::delete::config::DeleteMessageStyle;
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use email::message::sync::config::MessageSyncConfig;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
@ -15,8 +13,6 @@ pub struct MessageConfig {
|
||||||
pub copy: Option<MessageCopyConfig>,
|
pub copy: Option<MessageCopyConfig>,
|
||||||
pub r#move: Option<MessageMoveConfig>,
|
pub r#move: Option<MessageMoveConfig>,
|
||||||
pub delete: Option<DeleteMessageConfig>,
|
pub delete: Option<DeleteMessageConfig>,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
pub sync: Option<MessageSyncConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageConfig {
|
impl MessageConfig {
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::{eyre::eyre, Result};
|
||||||
use email::backend::feature::BackendFeatureSource;
|
use email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -34,10 +32,6 @@ pub struct TemplateForwardCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: MessageRawBodyArg,
|
pub body: MessageRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -48,11 +42,9 @@ impl TemplateForwardCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
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 email::backend::feature::BackendFeatureSource;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -38,10 +36,6 @@ pub struct TemplateReplyCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: MessageRawBodyArg,
|
pub body: MessageRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -53,11 +47,9 @@ impl TemplateReplyCommand {
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let id = self.envelope.id;
|
let id = self.envelope.id;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let get_messages_kind = toml_account_config.get_messages_kind();
|
let get_messages_kind = toml_account_config.get_messages_kind();
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ use mml::MmlCompilerBuilder;
|
||||||
use std::io::{self, BufRead, IsTerminal};
|
use std::io::{self, BufRead, IsTerminal};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
email::template::arg::TemplateRawArg, folder::arg::name::FolderNameOptionalFlag,
|
email::template::arg::TemplateRawArg, folder::arg::name::FolderNameOptionalFlag,
|
||||||
|
@ -27,10 +25,6 @@ pub struct TemplateSaveCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub template: TemplateRawArg,
|
pub template: TemplateRawArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -41,11 +35,9 @@ impl TemplateSaveCommand {
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_message_kind = toml_account_config.add_message_kind();
|
let add_message_kind = toml_account_config.add_message_kind();
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ use mml::MmlCompilerBuilder;
|
||||||
use std::io::{self, BufRead, IsTerminal};
|
use std::io::{self, BufRead, IsTerminal};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
email::template::arg::TemplateRawArg, printer::Printer,
|
email::template::arg::TemplateRawArg, printer::Printer,
|
||||||
|
@ -23,10 +21,6 @@ pub struct TemplateSendCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub template: TemplateRawArg,
|
pub template: TemplateRawArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -35,11 +29,9 @@ impl TemplateSendCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing send template command");
|
info!("executing send template command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
let send_message_kind = toml_account_config.send_message_kind().into_iter().chain(
|
||||||
toml_account_config
|
toml_account_config
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::message::Message;
|
use email::message::Message;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, config::TomlConfig,
|
account::arg::name::AccountNameFlag, config::TomlConfig,
|
||||||
email::template::arg::body::TemplateRawBodyArg, message::arg::header::HeaderRawArgs,
|
email::template::arg::body::TemplateRawBodyArg, message::arg::header::HeaderRawArgs,
|
||||||
|
@ -23,10 +21,6 @@ pub struct TemplateWriteCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub body: TemplateRawBodyArg,
|
pub body: TemplateRawBodyArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -35,11 +29,9 @@ impl TemplateWriteCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing write template command");
|
info!("executing write template command");
|
||||||
|
|
||||||
let (_, account_config) = config.clone().into_account_configs(
|
let (_, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let tpl = Message::new_tpl_builder(account_config)
|
let tpl = Message::new_tpl_builder(account_config)
|
||||||
.with_headers(self.headers.raw)
|
.with_headers(self.headers.raw)
|
||||||
|
|
|
@ -3,8 +3,6 @@ use color_eyre::Result;
|
||||||
use email::{backend::feature::BackendFeatureSource, folder::add::AddFolder};
|
use email::{backend::feature::BackendFeatureSource, folder::add::AddFolder};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
folder::arg::name::FolderNameArg, printer::Printer,
|
folder::arg::name::FolderNameArg, printer::Printer,
|
||||||
|
@ -19,10 +17,6 @@ pub struct AddFolderCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub folder: FolderNameArg,
|
pub folder: FolderNameArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -32,11 +26,9 @@ impl AddFolderCommand {
|
||||||
info!("executing create folder command");
|
info!("executing create folder command");
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let add_folder_kind = toml_account_config.add_folder_kind();
|
let add_folder_kind = toml_account_config.add_folder_kind();
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ use inquire::Confirm;
|
||||||
use std::process;
|
use std::process;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
folder::arg::name::FolderNameArg, printer::Printer,
|
folder::arg::name::FolderNameArg, printer::Printer,
|
||||||
|
@ -21,10 +19,6 @@ pub struct FolderDeleteCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub folder: FolderNameArg,
|
pub folder: FolderNameArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -42,11 +36,9 @@ impl FolderDeleteCommand {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let delete_folder_kind = toml_account_config.delete_folder_kind();
|
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 email::{backend::feature::BackendFeatureSource, folder::expunge::ExpungeFolder};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
folder::arg::name::FolderNameArg, printer::Printer,
|
folder::arg::name::FolderNameArg, printer::Printer,
|
||||||
|
@ -20,10 +18,6 @@ pub struct FolderExpungeCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub folder: FolderNameArg,
|
pub folder: FolderNameArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -33,11 +27,9 @@ impl FolderExpungeCommand {
|
||||||
info!("executing expunge folder command");
|
info!("executing expunge folder command");
|
||||||
|
|
||||||
let folder = &self.folder.name;
|
let folder = &self.folder.name;
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let expunge_folder_kind = toml_account_config.expunge_folder_kind();
|
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 email::{backend::feature::BackendFeatureSource, folder::list::ListFolders};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag,
|
account::arg::name::AccountNameFlag,
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
|
@ -18,10 +16,6 @@ use crate::{
|
||||||
/// This command allows you to list all exsting folders.
|
/// This command allows you to list all exsting folders.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct FolderListCommand {
|
pub struct FolderListCommand {
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
|
|
||||||
|
@ -38,11 +32,9 @@ impl FolderListCommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
info!("executing list folders command");
|
info!("executing list folders command");
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let list_folders_kind = toml_account_config.list_folders_kind();
|
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 std::process;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
use crate::cache::arg::disable::CacheDisableFlag;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
folder::arg::name::FolderNameArg, printer::Printer,
|
folder::arg::name::FolderNameArg, printer::Printer,
|
||||||
|
@ -20,10 +18,6 @@ pub struct FolderPurgeCommand {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub folder: FolderNameArg,
|
pub folder: FolderNameArg,
|
||||||
|
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
#[command(flatten)]
|
|
||||||
pub cache: CacheDisableFlag,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub account: AccountNameFlag,
|
pub account: AccountNameFlag,
|
||||||
}
|
}
|
||||||
|
@ -42,11 +36,9 @@ impl FolderPurgeCommand {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
let (toml_account_config, account_config) = config
|
||||||
self.account.name.as_deref(),
|
.clone()
|
||||||
#[cfg(feature = "account-sync")]
|
.into_account_configs(self.account.name.as_deref())?;
|
||||||
self.cache.disable,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let purge_folder_kind = toml_account_config.purge_folder_kind();
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
@ -14,8 +12,6 @@ pub struct FolderConfig {
|
||||||
pub expunge: Option<FolderExpungeConfig>,
|
pub expunge: Option<FolderExpungeConfig>,
|
||||||
pub purge: Option<FolderPurgeConfig>,
|
pub purge: Option<FolderPurgeConfig>,
|
||||||
pub delete: Option<FolderDeleteConfig>,
|
pub delete: Option<FolderDeleteConfig>,
|
||||||
#[cfg(feature = "account-sync")]
|
|
||||||
pub sync: Option<FolderSyncConfig>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderConfig {
|
impl FolderConfig {
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
#[cfg(feature = "account-discovery")]
|
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
||||||
use email::account::discover::config::{AuthenticationType, AutoConfig, SecurityType, ServerType};
|
#[cfg(feature = "oauth2")]
|
||||||
use email::{
|
use email::{
|
||||||
account::config::{
|
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||||
oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
autoconfig::config::AuthenticationType,
|
||||||
passwd::PasswdConfig,
|
};
|
||||||
},
|
use email::{
|
||||||
|
account::config::passwd::PasswdConfig,
|
||||||
imap::config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
|
imap::config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
|
||||||
};
|
};
|
||||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
||||||
use secret::Secret;
|
use secret::Secret;
|
||||||
|
|
||||||
use crate::{backend::config::BackendConfig, ui::prompt, wizard_log};
|
use crate::{backend::config::BackendConfig, ui::prompt};
|
||||||
|
|
||||||
const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
||||||
ImapEncryptionKind::Tls,
|
ImapEncryptionKind::Tls,
|
||||||
|
@ -20,11 +22,13 @@ const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
||||||
ImapEncryptionKind::None,
|
ImapEncryptionKind::None,
|
||||||
];
|
];
|
||||||
|
|
||||||
const XOAUTH2: &str = "XOAUTH2";
|
const SECRETS: &[&str] = &[
|
||||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
#[cfg(feature = "keyring")]
|
||||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
KEYRING,
|
||||||
|
RAW,
|
||||||
const SECRETS: &[&str] = &[KEYRING, RAW, CMD];
|
CMD,
|
||||||
|
];
|
||||||
|
#[cfg(feature = "keyring")]
|
||||||
const KEYRING: &str = "Ask my password, then save it in my system's global 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 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";
|
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(
|
pub(crate) async fn configure(
|
||||||
account_name: &str,
|
account_name: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
autoconfig: Option<&AutoConfig>,
|
autoconfig: Option<&AutoConfig>,
|
||||||
) -> Result<BackendConfig> {
|
) -> Result<BackendConfig> {
|
||||||
use color_eyre::eyre::OptionExt as _;
|
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| {
|
let autoconfig_server = autoconfig.and_then(|c| {
|
||||||
c.email_provider()
|
c.email_provider()
|
||||||
.incoming_servers()
|
.incoming_servers()
|
||||||
|
@ -93,7 +95,7 @@ pub(crate) async fn configure(
|
||||||
ImapEncryptionKind::None => 2,
|
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)
|
.with_starting_cursor(default_encryption_idx)
|
||||||
.prompt_skippable()?;
|
.prompt_skippable()?;
|
||||||
|
|
||||||
|
@ -101,28 +103,28 @@ pub(crate) async fn configure(
|
||||||
.and_then(|s| s.port())
|
.and_then(|s| s.port())
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_else(|| match &autoconfig_encryption {
|
.unwrap_or_else(|| match &autoconfig_encryption {
|
||||||
ImapEncryptionKind::Tls => 993,
|
ImapEncryptionKind::Tls => 465,
|
||||||
ImapEncryptionKind::StartTls => 143,
|
ImapEncryptionKind::StartTls => 587,
|
||||||
ImapEncryptionKind::None => 143,
|
ImapEncryptionKind::None => 25,
|
||||||
});
|
});
|
||||||
|
|
||||||
let (encryption, default_port) = match encryption_idx {
|
let (encryption, default_port) = match encryption_kind {
|
||||||
Some(enc_kind)
|
Some(idx)
|
||||||
if &enc_kind
|
if &idx
|
||||||
== ENCRYPTIONS.get(default_encryption_idx).ok_or_eyre(
|
== 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(autoconfig_encryption), autoconfig_port)
|
||||||
}
|
}
|
||||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 993),
|
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 465),
|
||||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 143),
|
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 587),
|
||||||
_ => (Some(ImapEncryptionKind::None), 143),
|
_ => (Some(ImapEncryptionKind::None), 25),
|
||||||
};
|
};
|
||||||
|
|
||||||
let port = Text::new("IMAP port")
|
let port = Text::new("IMAP port")
|
||||||
.with_validators(&[
|
.with_validators(&[
|
||||||
Box::new(MinLengthValidator::new(1)),
|
Box::new(validator::MinLengthValidator::new(1)),
|
||||||
Box::new(U16Validator {}),
|
Box::new(U16Validator {}),
|
||||||
])
|
])
|
||||||
.with_default(&default_port.to_string())
|
.with_default(&default_port.to_string())
|
||||||
|
@ -141,173 +143,167 @@ pub(crate) async fn configure(
|
||||||
.with_default(&default_login)
|
.with_default(&default_login)
|
||||||
.prompt()?;
|
.prompt()?;
|
||||||
|
|
||||||
let default_oauth2_enabled = autoconfig_server
|
#[cfg(feature = "oauth2")]
|
||||||
.and_then(|imap| {
|
let auth = {
|
||||||
imap.authentication_type()
|
use inquire::{Confirm, Password};
|
||||||
.into_iter()
|
|
||||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
|
||||||
})
|
|
||||||
.filter(|_| autoconfig_oauth2.is_some())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
const XOAUTH2: &str = "XOAUTH2";
|
||||||
.with_default(default_oauth2_enabled)
|
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||||
.prompt_skippable()?
|
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let auth = if oauth2_enabled {
|
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||||
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())
|
let default_oauth2_enabled = autoconfig_server
|
||||||
.with_starting_cursor(0)
|
.and_then(|imap| {
|
||||||
.prompt_skippable()?;
|
imap.authentication_type()
|
||||||
|
.into_iter()
|
||||||
config.method = match method_idx {
|
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||||
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())
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
};
|
.filter(|_| autoconfig_oauth2.is_some())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("IMAP OAuth 2.0 main scope")? {
|
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||||
config.scopes = OAuth2Scopes::Scope(scope);
|
.with_default(default_oauth2_enabled)
|
||||||
}
|
.prompt_skippable()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let confirm_additional_scope = || -> Result<bool> {
|
if oauth2_enabled {
|
||||||
let confirm = Confirm::new("Would you like to add more IMAP OAuth 2.0 scopes?")
|
let mut config = OAuth2Config::default();
|
||||||
.with_default(false)
|
let redirect_host = OAuth2Config::LOCALHOST;
|
||||||
.prompt_skippable()?
|
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
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()? {
|
config.method = match method_idx {
|
||||||
let mut scopes = match config.scopes {
|
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
||||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
||||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
_ => OAuth2Method::XOAuth2,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
config.client_id = Text::new("IMAP OAuth 2.0 client id").prompt()?;
|
||||||
scopes.push(scope)
|
|
||||||
|
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?")
|
Ok(confirm)
|
||||||
.with_default(true)
|
};
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or(true);
|
|
||||||
|
|
||||||
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(
|
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
||||||
config.client_id.clone(),
|
scopes.push(scope)
|
||||||
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()
|
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_host(redirect_host.to_owned())
|
||||||
.with_redirect_port(redirect_port);
|
.with_redirect_port(redirect_port)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
if config.pkce {
|
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||||
auth_code_grant = auth_code_grant.with_pkce();
|
.with_redirect_host(redirect_host.to_owned())
|
||||||
}
|
.with_redirect_port(redirect_port);
|
||||||
|
|
||||||
for scope in config.scopes.clone() {
|
if config.pkce {
|
||||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
auth_code_grant = auth_code_grant.with_pkce();
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
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 {
|
let config = ImapConfig {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@ -320,191 +316,30 @@ pub(crate) async fn configure(
|
||||||
Ok(BackendConfig::Imap(config))
|
Ok(BackendConfig::Imap(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "account-discovery"))]
|
pub(crate) async fn configure_passwd(account_name: &str) -> Result<ImapAuthConfig> {
|
||||||
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> {
|
use inquire::{Select, Text};
|
||||||
use inquire::{
|
|
||||||
validator::MinLengthValidator, Confirm, Password, PasswordDisplayMode, Select, Text,
|
|
||||||
};
|
|
||||||
|
|
||||||
let default_host = format!("imap.{}", email.rsplit_once('@').unwrap().1);
|
let secret_idx = Select::new("IMAP authentication strategy", SECRETS.to_vec())
|
||||||
|
|
||||||
let host = Text::new("IMAP hostname")
|
|
||||||
.with_default(&default_host)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let encryption_idx = Select::new("IMAP encryption", ENCRYPTIONS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
.with_starting_cursor(0)
|
||||||
.prompt_skippable()?;
|
.prompt_skippable()?;
|
||||||
|
|
||||||
let (encryption, default_port) = match encryption_idx {
|
let secret = match secret_idx {
|
||||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 993),
|
#[cfg(feature = "keyring")]
|
||||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 143),
|
Some(sec) if sec == KEYRING => {
|
||||||
_ => (Some(ImapEncryptionKind::None), 143),
|
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")
|
Ok(ImapAuthConfig::Passwd(PasswdConfig(secret)))
|
||||||
.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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
pub(crate) mod wizard;
|
pub(crate) mod wizard;
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
#[cfg(feature = "account-discovery")]
|
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
||||||
use email::account::discover::config::{AuthenticationType, AutoConfig, SecurityType, ServerType};
|
#[cfg(feature = "oauth2")]
|
||||||
use email::{
|
use email::{
|
||||||
account::config::{
|
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
||||||
oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
autoconfig::config::AuthenticationType,
|
||||||
passwd::PasswdConfig,
|
};
|
||||||
},
|
use email::{
|
||||||
|
account::config::passwd::PasswdConfig,
|
||||||
smtp::config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
|
smtp::config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
|
||||||
};
|
};
|
||||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
||||||
|
#[cfg(feature = "oauth2")]
|
||||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
||||||
use secret::Secret;
|
use secret::Secret;
|
||||||
|
|
||||||
use crate::{backend::config::BackendConfig, ui::prompt, wizard_log};
|
use crate::{backend::config::BackendConfig, ui::prompt};
|
||||||
|
|
||||||
const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
||||||
SmtpEncryptionKind::Tls,
|
SmtpEncryptionKind::Tls,
|
||||||
|
@ -20,11 +22,13 @@ const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
||||||
SmtpEncryptionKind::None,
|
SmtpEncryptionKind::None,
|
||||||
];
|
];
|
||||||
|
|
||||||
const XOAUTH2: &str = "XOAUTH2";
|
const SECRETS: &[&str] = &[
|
||||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
#[cfg(feature = "keyring")]
|
||||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
KEYRING,
|
||||||
|
RAW,
|
||||||
const SECRETS: &[&str] = &[KEYRING, RAW, CMD];
|
CMD,
|
||||||
|
];
|
||||||
|
#[cfg(feature = "keyring")]
|
||||||
const KEYRING: &str = "Ask my password, then save it in my system's global 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 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";
|
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(
|
pub(crate) async fn configure(
|
||||||
account_name: &str,
|
account_name: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
autoconfig: Option<&AutoConfig>,
|
autoconfig: Option<&AutoConfig>,
|
||||||
) -> Result<BackendConfig> {
|
) -> Result<BackendConfig> {
|
||||||
use color_eyre::eyre::OptionExt as _;
|
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| {
|
let autoconfig_server = autoconfig.and_then(|c| {
|
||||||
c.email_provider()
|
c.email_provider()
|
||||||
.outgoing_servers()
|
.outgoing_servers()
|
||||||
|
@ -141,173 +143,167 @@ pub(crate) async fn configure(
|
||||||
.with_default(&default_login)
|
.with_default(&default_login)
|
||||||
.prompt()?;
|
.prompt()?;
|
||||||
|
|
||||||
let default_oauth2_enabled = autoconfig_server
|
#[cfg(feature = "oauth2")]
|
||||||
.and_then(|smtp| {
|
let auth = {
|
||||||
smtp.authentication_type()
|
use inquire::{Confirm, Password};
|
||||||
.into_iter()
|
|
||||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
|
||||||
})
|
|
||||||
.filter(|_| autoconfig_oauth2.is_some())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
const XOAUTH2: &str = "XOAUTH2";
|
||||||
.with_default(default_oauth2_enabled)
|
const OAUTHBEARER: &str = "OAUTHBEARER";
|
||||||
.prompt_skippable()?
|
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let auth = if oauth2_enabled {
|
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
||||||
let mut config = OAuth2Config::default();
|
|
||||||
let redirect_host = OAuth2Config::LOCALHOST;
|
|
||||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
|
||||||
|
|
||||||
let method_idx = Select::new("SMTP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
let default_oauth2_enabled = autoconfig_server
|
||||||
.with_starting_cursor(0)
|
.and_then(|smtp| {
|
||||||
.prompt_skippable()?;
|
smtp.authentication_type()
|
||||||
|
.into_iter()
|
||||||
config.method = match method_idx {
|
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
||||||
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()),
|
|
||||||
})
|
})
|
||||||
};
|
.filter(|_| autoconfig_oauth2.is_some())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("SMTP OAuth 2.0 main scope")? {
|
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
||||||
config.scopes = OAuth2Scopes::Scope(scope);
|
.with_default(default_oauth2_enabled)
|
||||||
}
|
.prompt_skippable()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let confirm_additional_scope = || -> Result<bool> {
|
if oauth2_enabled {
|
||||||
let confirm = Confirm::new("Would you like to add more SMTP OAuth 2.0 scopes?")
|
let mut config = OAuth2Config::default();
|
||||||
.with_default(false)
|
let redirect_host = OAuth2Config::LOCALHOST;
|
||||||
.prompt_skippable()?
|
let redirect_port = OAuth2Config::get_first_available_port()?;
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
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()? {
|
config.method = match method_idx {
|
||||||
let mut scopes = match config.scopes {
|
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
||||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
||||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
_ => OAuth2Method::XOAuth2,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
config.client_id = Text::new("SMTP OAuth 2.0 client id").prompt()?;
|
||||||
scopes.push(scope)
|
|
||||||
|
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?")
|
Ok(confirm)
|
||||||
.with_default(true)
|
};
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or(true);
|
|
||||||
|
|
||||||
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(
|
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
||||||
config.client_id.clone(),
|
scopes.push(scope)
|
||||||
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()
|
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_host(redirect_host.to_owned())
|
||||||
.with_redirect_port(redirect_port);
|
.with_redirect_port(redirect_port)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
if config.pkce {
|
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
||||||
auth_code_grant = auth_code_grant.with_pkce();
|
.with_redirect_host(redirect_host.to_owned())
|
||||||
}
|
.with_redirect_port(redirect_port);
|
||||||
|
|
||||||
for scope in config.scopes.clone() {
|
if config.pkce {
|
||||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
auth_code_grant = auth_code_grant.with_pkce();
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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 {
|
let config = SmtpConfig {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@ -319,188 +315,30 @@ pub(crate) async fn configure(
|
||||||
Ok(BackendConfig::Smtp(config))
|
Ok(BackendConfig::Smtp(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "account-discovery"))]
|
pub(crate) async fn configure_passwd(account_name: &str) -> Result<SmtpAuthConfig> {
|
||||||
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> {
|
use inquire::{Select, Text};
|
||||||
use inquire::{validator::MinLengthValidator, Confirm, Password, Select, Text};
|
|
||||||
|
|
||||||
let default_host = format!("smtp.{}", email.rsplit_once('@').unwrap().1);
|
let secret_idx = Select::new("SMTP authentication strategy", SECRETS.to_vec())
|
||||||
|
|
||||||
let host = Text::new("SMTP hostname")
|
|
||||||
.with_default(&default_host)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let encryption_idx = Select::new("SMTP encryption", ENCRYPTIONS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
.with_starting_cursor(0)
|
||||||
.prompt_skippable()?;
|
.prompt_skippable()?;
|
||||||
|
|
||||||
let (encryption, default_port) = match encryption_idx {
|
let secret = match secret_idx {
|
||||||
Some(SmtpEncryptionKind::Tls) => (Some(SmtpEncryptionKind::Tls), 465),
|
#[cfg(feature = "keyring")]
|
||||||
Some(SmtpEncryptionKind::StartTls) => (Some(SmtpEncryptionKind::StartTls), 587),
|
Some(sec) if sec == KEYRING => {
|
||||||
_ => (Some(SmtpEncryptionKind::None), 25),
|
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")
|
Ok(SmtpAuthConfig::Passwd(PasswdConfig(secret)))
|
||||||
.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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
pub(crate) fn secret(prompt: &str) -> io::Result<String> {
|
||||||
inquire::Password::new(prompt)
|
inquire::Password::new(prompt)
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||||
|
|
Loading…
Reference in a new issue