From c254f64569e756a3e8f6cf68952feb8da7aec809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 8 Jun 2023 23:35:36 +0200 Subject: [PATCH] refactor builders and sync --- Cargo.lock | 65 ++++++-- Cargo.toml | 11 +- flake.lock | 24 +-- src/cache/id_mapper.rs | 21 ++- src/config/prelude.rs | 63 +++++--- src/domain/account/args.rs | 12 +- src/domain/account/config.rs | 26 ++-- src/domain/account/handlers.rs | 186 +++++++++++------------ src/domain/backend/imap/handlers.rs | 12 +- src/domain/backend/imap/wizard.rs | 39 +++-- src/domain/folder/handlers.rs | 46 +++--- src/domain/sender/mod.rs | 1 + src/domain/sender/smtp/wizard.rs | 39 +++-- src/main.rs | 224 ++++++++++++---------------- 14 files changed, 405 insertions(+), 364 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e06587..f08a486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,6 +1009,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.25" @@ -1016,6 +1031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1024,6 +1040,17 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.25" @@ -1074,6 +1101,7 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1187,7 +1215,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "himalaya" -version = "0.8.1" +version = "0.8.1-beta" dependencies = [ "anyhow", "atty", @@ -1217,6 +1245,7 @@ dependencies = [ "tempfile", "termcolor", "terminal_size", + "tokio", "toml", "toml_edit", "unicode-width", @@ -2170,15 +2199,16 @@ dependencies = [ [[package]] name = "pimalaya-email" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e85cdc815af8f35c67941eaac74dc2e6a8d01c3ed498b2d36eaaf9bc8a517e6" +checksum = "6b881982050e29d8f9141a57f0be3f87471394462a2f1695471d5a81f44b4b3a" dependencies = [ "advisory-lock", "ammonia", "chrono", "convert_case", "dirs", + "futures", "html-escape", "imap", "imap-proto", @@ -2214,9 +2244,9 @@ dependencies = [ [[package]] name = "pimalaya-email-tpl" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25da44b70885d9636ddcb89181a2f0155acff3816697ec7af07a5f6f17cdc0e" +checksum = "f640b701926112e28b025cea9c9d50a2bfe329862eeb4acbf2d62edeb53fb19b" dependencies = [ "chumsky 0.9.0", "log", @@ -2231,9 +2261,9 @@ dependencies = [ [[package]] name = "pimalaya-keyring" -version = "0.0.1" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5fed5fff1897b4964a5e8efbcd5e41e4492fe7947f827745fe9a14a555fe94" +checksum = "cef72189d57c09a3c682769e7c99d44772bb1164173e77e77cef837ba8dda1b4" dependencies = [ "keyring", "log", @@ -2242,9 +2272,9 @@ dependencies = [ [[package]] name = "pimalaya-oauth2" -version = "0.0.2" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9bec4262b62b6b14ffa244727e3d86d69e608e664e162e2c73332bed3b3f8a1" +checksum = "6a28b3da9e56304f14bd46f769bfae52de32bd3fc2795946079e66838a178ce8" dependencies = [ "log", "oauth2", @@ -2265,13 +2295,12 @@ dependencies = [ [[package]] name = "pimalaya-secret" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b585f585653ac7f957a608d8cdffd81be6561c2ad92fa82a1e72ed62a1bb31e0" +checksum = "9338dc84e5ec9fc25f3a36d82ed68ffe4888ad728ca1aa42228e27648ddff01f" dependencies = [ "log", "pimalaya-keyring", - "pimalaya-oauth2", "pimalaya-process", "thiserror", ] @@ -3153,9 +3182,21 @@ dependencies = [ "num_cpus", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.42.0", ] +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.104", +] + [[package]] name = "tokio-rustls" version = "0.23.4" diff --git a/Cargo.toml b/Cargo.toml index 3a0821d..9377951 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "himalaya" description = "CLI to manage your emails." -version = "0.8.1" +version = "0.8.1-beta" authors = ["soywod "] edition = "2021" license = "MIT" @@ -44,16 +44,17 @@ indicatif = "0.17" log = "0.4" md5 = "0.7.0" once_cell = "1.16.0" -pimalaya-email = "=0.9.0" -pimalaya-keyring = "=0.0.1" -pimalaya-oauth2 = "=0.0.2" +pimalaya-email = { version = "=0.10.0", default-features = false } +pimalaya-keyring = "=0.0.4" +pimalaya-oauth2 = "=0.0.3" pimalaya-process = "=0.0.2" -pimalaya-secret = "=0.0.1" +pimalaya-secret = "=0.0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" shellexpand = "2.1" termcolor = "1.1" terminal_size = "0.1" +tokio = { version = "1.23", default-features = false, features = ["macros"] } toml = "0.7.4" toml_edit = "0.19.8" unicode-width = "0.1" diff --git a/flake.lock b/flake.lock index d9de53a..b7bca61 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1682835640, - "narHash": "sha256-rAYEOd4nZFLjDlrF9KNlcopPKNVtr1svSXcEValVRMY=", + "lastModified": 1686032467, + "narHash": "sha256-KUCS237H0G1QGx5ehhEmh5yKtcDGCxvVXVtz8xEDAKE=", "owner": "nix-community", "repo": "fenix", - "rev": "006b429d3c493f4c5b1743a94f71ad961c7693ab", + "rev": "1a3e0f661119a7435099b118912d65bdbbf3bb11", "type": "github" }, "original": { @@ -42,11 +42,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "type": "github" }, "original": { @@ -97,11 +97,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1682817260, - "narHash": "sha256-kFMXzKNj4d/0Iqbm5l57rHSLyUeyCLMuvlROZIuuhvk=", + "lastModified": 1685883127, + "narHash": "sha256-zPDaPNrAtBnO24rNqjHLINHsqTdRbgWy1c/TL3EdwlM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "db1e4eeb0f9a9028bcb920e00abbc1409dd3ef36", + "rev": "d4a9ff82fc18723219b60c66fb2ccb0734c460eb", "type": "github" }, "original": { @@ -124,11 +124,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1682792082, - "narHash": "sha256-1nuP2rqipsdB8IJ3N5ws3FQm4dX3mKIueIrCUSu1bWw=", + "lastModified": 1685984106, + "narHash": "sha256-dOEuU1AuASOWdXT/SbVpD8uX7JjiW3lCp08SbviHuww=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "7bcb4c2ef23e151a639ff918fbb8ab9d521eabb9", + "rev": "d42d55feaafa71e14521bbfe6e7011fbb41980f0", "type": "github" }, "original": { diff --git a/src/cache/id_mapper.rs b/src/cache/id_mapper.rs index 4e5e251..f770e0b 100644 --- a/src/cache/id_mapper.rs +++ b/src/cache/id_mapper.rs @@ -16,10 +16,7 @@ pub enum IdMapper { } impl IdMapper { - fn find_closest_db_path(dir: D) -> PathBuf - where - D: AsRef, - { + pub fn find_closest_db_path(dir: impl AsRef) -> PathBuf { let mut db_path = dir.as_ref().join(ID_MAPPER_DB_FILE_NAME); let mut db_parent_dir = dir.as_ref().parent(); @@ -40,20 +37,20 @@ impl IdMapper { } pub fn new(backend: &dyn Backend, account: &str, folder: &str) -> Result { - let mut db_path = PathBuf::default(); - - if let Some(backend) = backend.as_any().downcast_ref::() { - db_path = Self::find_closest_db_path(&backend.path()); - } - #[cfg(feature = "imap-backend")] if backend.as_any().is::() { - return Ok(Self::Dummy); + return Ok(IdMapper::Dummy); + } + + let mut db_path = PathBuf::new(); + + if let Some(backend) = backend.as_any().downcast_ref::() { + db_path = Self::find_closest_db_path(backend.path()) } #[cfg(feature = "notmuch-backend")] if let Some(backend) = backend.as_any().downcast_ref::() { - db_path = Self::find_closest_db_path(&backend.path()); + db_path = Self::find_closest_db_path(backend.path()) } let digest = md5::compute(account.to_string() + folder); diff --git a/src/config/prelude.rs b/src/config/prelude.rs index da025db..d0cfb2e 100644 --- a/src/config/prelude.rs +++ b/src/config/prelude.rs @@ -1,12 +1,13 @@ -#[cfg(feature = "imap-backend")] -use pimalaya_email::ImapConfig; #[cfg(feature = "notmuch-backend")] use pimalaya_email::NotmuchConfig; use pimalaya_email::{ - folder::sync::Strategy as SyncFoldersStrategy, BackendConfig, EmailHooks, EmailTextPlainFormat, - ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig, - SenderConfig, SendmailConfig, SmtpAuthConfig, SmtpConfig, + BackendConfig, EmailHooks, EmailTextPlainFormat, FolderSyncStrategy, MaildirConfig, + OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig, SenderConfig, SendmailConfig, }; +#[cfg(feature = "imap-backend")] +use pimalaya_email::{ImapAuthConfig, ImapConfig}; +#[cfg(feature = "smtp-sender")] +use pimalaya_email::{SmtpAuthConfig, SmtpConfig}; use pimalaya_keyring::Entry; use pimalaya_process::{Cmd, Pipeline, SingleCmd}; use pimalaya_secret::Secret; @@ -74,14 +75,16 @@ impl From for Option { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(remote = "Secret", rename_all = "kebab-case")] pub enum SecretDef { Raw(String), #[serde(with = "CmdDef")] Cmd(Cmd), - #[serde(with = "EntryDef")] - Keyring(Entry), + #[serde(with = "EntryDef", rename = "keyring")] + KeyringEntry(Entry), + #[default] + Undefined, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -134,6 +137,7 @@ pub struct ImapConfigDef { pub watch_cmds: Option>, } +#[cfg(feature = "imap-backend")] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(remote = "ImapAuthConfig", tag = "imap-auth")] pub enum ImapAuthConfigDef { @@ -150,7 +154,7 @@ pub struct ImapPasswdConfigDef { rename = "imap-passwd", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub passwd: Secret, } @@ -166,7 +170,7 @@ pub struct ImapOAuth2ConfigDef { rename = "imap-oauth2-client-secret", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub client_secret: Secret, #[serde(rename = "imap-oauth2-auth-url")] @@ -177,20 +181,30 @@ pub struct ImapOAuth2ConfigDef { rename = "imap-oauth2-access-token", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub access_token: Secret, #[serde( rename = "imap-oauth2-refresh-token", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub refresh_token: Secret, #[serde(flatten, with = "ImapOAuth2ScopesDef")] pub scopes: OAuth2Scopes, #[serde(rename = "imap-oauth2-pkce", default)] pub pkce: bool, + #[serde( + rename = "imap-oauth2-redirect-host", + default = "OAuth2Config::default_redirect_host" + )] + pub redirect_host: String, + #[serde( + rename = "imap-oauth2-redirect-port", + default = "OAuth2Config::default_redirect_port" + )] + pub redirect_port: u16, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -236,12 +250,14 @@ pub enum EmailTextPlainFormatDef { pub enum SenderConfigDef { #[default] None, + #[cfg(feature = "smtp-sender")] #[serde(with = "SmtpConfigDef")] Smtp(SmtpConfig), #[serde(with = "SendmailConfigDef")] Sendmail(SendmailConfig), } +#[cfg(feature = "smtp-sender")] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(remote = "SmtpConfig")] struct SmtpConfigDef { @@ -261,6 +277,7 @@ struct SmtpConfigDef { pub auth: SmtpAuthConfig, } +#[cfg(feature = "smtp-sender")] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(remote = "SmtpAuthConfig", tag = "smtp-auth")] pub enum SmtpAuthConfigDef { @@ -277,7 +294,7 @@ pub struct SmtpPasswdConfigDef { rename = "smtp-passwd", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub passwd: Secret, } @@ -293,7 +310,7 @@ pub struct SmtpOAuth2ConfigDef { rename = "smtp-oauth2-client-secret", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub client_secret: Secret, #[serde(rename = "smtp-oauth2-auth-url")] @@ -304,20 +321,30 @@ pub struct SmtpOAuth2ConfigDef { rename = "smtp-oauth2-access-token", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub access_token: Secret, #[serde( rename = "smtp-oauth2-refresh-token", with = "SecretDef", default, - skip_serializing_if = "Secret::is_undefined_entry" + skip_serializing_if = "Secret::is_undefined" )] pub refresh_token: Secret, #[serde(flatten, with = "SmtpOAuth2ScopesDef")] pub scopes: OAuth2Scopes, #[serde(rename = "smtp-oauth2-pkce", default)] pub pkce: bool, + #[serde( + rename = "imap-oauth2-redirect-host", + default = "OAuth2Config::default_redirect_host" + )] + pub redirect_host: String, + #[serde( + rename = "imap-oauth2-redirect-port", + default = "OAuth2Config::default_redirect_port" + )] + pub redirect_port: u16, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -347,8 +374,8 @@ pub struct EmailHooksDef { } #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(remote = "SyncFoldersStrategy", rename_all = "kebab-case")] -pub enum SyncFoldersStrategyDef { +#[serde(remote = "FolderSyncStrategy", rename_all = "kebab-case")] +pub enum FolderSyncStrategyDef { #[default] All, #[serde(alias = "only")] diff --git a/src/domain/account/args.rs b/src/domain/account/args.rs index 694cf7a..baa4507 100644 --- a/src/domain/account/args.rs +++ b/src/domain/account/args.rs @@ -3,7 +3,7 @@ use anyhow::Result; use clap::{Arg, ArgAction, ArgMatches, Command}; use log::info; -use pimalaya_email::folder::sync::Strategy as SyncFoldersStrategy; +use pimalaya_email::FolderSyncStrategy; use std::collections::HashSet; use crate::{folder, ui::table}; @@ -25,7 +25,7 @@ pub enum Cmd { /// Represents the list accounts command. List(table::args::MaxTableWidth), /// Represents the sync account command. - Sync(Option, DryRun), + Sync(Option, DryRun), /// Configure the current selected account. Configure(Reset), } @@ -39,15 +39,15 @@ pub fn matches(m: &ArgMatches) -> Result> { let include = folder::args::parse_include_arg(m); let exclude = folder::args::parse_exclude_arg(m); let folders_strategy = if let Some(folder) = folder::args::parse_source_arg(m) { - Some(SyncFoldersStrategy::Include(HashSet::from_iter([ + Some(FolderSyncStrategy::Include(HashSet::from_iter([ folder.to_owned() ]))) } else if !include.is_empty() { - Some(SyncFoldersStrategy::Include(include.to_owned())) + Some(FolderSyncStrategy::Include(include.to_owned())) } else if !exclude.is_empty() { - Some(SyncFoldersStrategy::Exclude(exclude)) + Some(FolderSyncStrategy::Exclude(exclude)) } else if folder::args::parse_all_arg(m) { - Some(SyncFoldersStrategy::All) + Some(FolderSyncStrategy::All) } else { None }; diff --git a/src/domain/account/config.rs b/src/domain/account/config.rs index 674ab4f..350a1d5 100644 --- a/src/domain/account/config.rs +++ b/src/domain/account/config.rs @@ -8,8 +8,8 @@ use pimalaya_email::ImapAuthConfig; #[cfg(feature = "smtp-sender")] use pimalaya_email::SmtpAuthConfig; use pimalaya_email::{ - folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, BackendConfig, EmailHooks, - EmailTextPlainFormat, SenderConfig, + AccountConfig, BackendConfig, EmailHooks, EmailTextPlainFormat, FolderSyncStrategy, + SenderConfig, }; use pimalaya_process::Cmd; use serde::{Deserialize, Serialize}; @@ -76,10 +76,10 @@ pub struct DeserializedAccountConfig { pub sync_dir: Option, #[serde( default, - with = "SyncFoldersStrategyDef", - skip_serializing_if = "SyncFoldersStrategy::is_default" + with = "FolderSyncStrategyDef", + skip_serializing_if = "FolderSyncStrategy::is_default" )] - pub sync_folders_strategy: SyncFoldersStrategy, + pub sync_folders_strategy: FolderSyncStrategy, #[serde(flatten, with = "BackendConfigDef")] pub backend: BackendConfig, @@ -197,16 +197,16 @@ impl DeserializedAccountConfig { if let BackendConfig::Imap(config) = &mut backend { match &mut config.auth { ImapAuthConfig::Passwd(secret) => { - secret.replace_undefined_entry_with(format!("{name}-imap-passwd")); + secret.set_keyring_entry_if_undefined(format!("{name}-imap-passwd")); } ImapAuthConfig::OAuth2(config) => { - config.client_secret.replace_undefined_entry_with(format!( + config.client_secret.set_keyring_entry_if_undefined(format!( "{name}-imap-oauth2-client-secret" )); - config.access_token.replace_undefined_entry_with(format!( + config.access_token.set_keyring_entry_if_undefined(format!( "{name}-imap-oauth2-access-token" )); - config.refresh_token.replace_undefined_entry_with(format!( + config.refresh_token.set_keyring_entry_if_undefined(format!( "{name}-imap-oauth2-refresh-token" )); } @@ -222,16 +222,16 @@ impl DeserializedAccountConfig { if let SenderConfig::Smtp(config) = &mut sender { match &mut config.auth { SmtpAuthConfig::Passwd(secret) => { - secret.replace_undefined_entry_with(format!("{name}-smtp-passwd")); + secret.set_keyring_entry_if_undefined(format!("{name}-smtp-passwd")); } SmtpAuthConfig::OAuth2(config) => { - config.client_secret.replace_undefined_entry_with(format!( + config.client_secret.set_keyring_entry_if_undefined(format!( "{name}-smtp-oauth2-client-secret" )); - config.access_token.replace_undefined_entry_with(format!( + config.access_token.set_keyring_entry_if_undefined(format!( "{name}-smtp-oauth2-access-token" )); - config.refresh_token.replace_undefined_entry_with(format!( + config.refresh_token.set_keyring_entry_if_undefined(format!( "{name}-smtp-oauth2-refresh-token" )); } diff --git a/src/domain/account/handlers.rs b/src/domain/account/handlers.rs index 44c0872..e4dc93b 100644 --- a/src/domain/account/handlers.rs +++ b/src/domain/account/handlers.rs @@ -3,12 +3,17 @@ //! This module gathers all account actions triggered by the CLI. use anyhow::Result; -use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle}; use log::{info, trace, warn}; +use once_cell::sync::Lazy; +#[cfg(feature = "imap-backend")] +use pimalaya_email::ImapAuthConfig; +#[cfg(feature = "smtp-sender")] +use pimalaya_email::SmtpAuthConfig; use pimalaya_email::{ - folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendConfig, - BackendSyncBuilder, BackendSyncProgressEvent, ImapAuthConfig, SenderConfig, SmtpAuthConfig, + AccountConfig, BackendConfig, BackendSyncBuilder, BackendSyncProgressEvent, SenderConfig, }; +use std::{collections::HashMap, sync::Mutex}; use crate::{ config::{ @@ -19,6 +24,21 @@ use crate::{ Accounts, }; +const MAIN_PROGRESS_STYLE: Lazy = Lazy::new(|| { + ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap() +}); + +const SUB_PROGRESS_STYLE: Lazy = Lazy::new(|| { + ProgressStyle::with_template( + " {prefix:.bold} — {wide_msg:.dim} \n {wide_bar:.black/black} {percent}% ", + ) + .unwrap() +}); + +const SUB_PROGRESS_DONE_STYLE: Lazy = Lazy::new(|| { + ProgressStyle::with_template(" {prefix:.bold} \n {wide_bar:.green} {percent}% ").unwrap() +}); + /// Configure the current selected account pub fn configure(config: &AccountConfig, reset: bool) -> Result<()> { info!("entering the configure account handler"); @@ -103,25 +123,16 @@ pub fn list<'a, P: Printer>( /// Synchronizes the account defined using argument `-a|--account`. If /// no account given, synchronizes the default one. -pub fn sync( - account_config: &AccountConfig, +pub fn sync<'a, P: Printer>( printer: &mut P, - backend: &dyn Backend, - folders_strategy: Option, + sync_builder: BackendSyncBuilder<'a>, dry_run: bool, ) -> Result<()> { info!("entering the sync accounts handler"); trace!("dry run: {dry_run}"); - trace!("folders strategy: {folders_strategy:#?}"); - - let mut sync_builder = BackendSyncBuilder::new(account_config); - - if let Some(strategy) = folders_strategy { - sync_builder = sync_builder.folders_strategy(strategy); - } if dry_run { - let report = sync_builder.dry_run(true).sync(backend)?; + let report = sync_builder.sync()?; let mut hunks_count = report.folders_patch.len(); if !report.folders_patch.is_empty() { @@ -142,98 +153,77 @@ pub fn sync( } printer.print(format!( - "Estimated patch length for account {} to be synchronized: {hunks_count}", - backend.name(), + "Estimated patch length for account to be synchronized: {hunks_count}", ))?; } else if printer.is_json() { - sync_builder.sync(backend)?; - printer.print(format!( - "Account {} successfully synchronized!", - backend.name() - ))?; + sync_builder.sync()?; + printer.print("Account successfully synchronized!")?; } else { let multi = MultiProgress::new(); - let progress = multi.add( - ProgressBar::new(0).with_style( - ProgressStyle::with_template( - " {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} {pos}/{len} ", - ) - .unwrap(), - ), + let sub_progresses = Mutex::new(HashMap::new()); + let main_progress = multi.add( + ProgressBar::new(100) + .with_style(MAIN_PROGRESS_STYLE.clone()) + .with_message("Synchronizing folders…"), ); + // Force the progress bar to show + main_progress.set_position(0); + let report = sync_builder - .on_progress(|evt| { + .with_on_progress(move |evt| { use BackendSyncProgressEvent::*; Ok(match evt { - GetLocalCachedFolders => { - progress.set_length(4); - progress.set_position(0); - progress.set_message("Getting local cached folders…"); + ApplyFolderPatches(..) => { + main_progress.inc(3); } - GetLocalFolders => { - progress.inc(1); - progress.set_message("Getting local maildir folders…"); + ApplyEnvelopePatches(patches) => { + let mut envelopes_progresses = sub_progresses.lock().unwrap(); + let patches_len = patches.values().fold(0, |sum, patch| sum + patch.len()); + main_progress.set_length((110 * patches_len / 100) as u64); + main_progress.set_position((5 * patches_len / 100) as u64); + main_progress.set_message("Synchronizing envelopes…"); + + 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()); + } } - GetRemoteCachedFolders => { - progress.inc(1); - progress.set_message("Getting remote cached folders…"); + ApplyEnvelopeHunk(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}…")); + } + } } - GetRemoteFolders => { - progress.inc(1); - progress.set_message("Getting remote folders…"); + ApplyEnvelopeCachePatch(_patch) => { + main_progress.set_length(100); + main_progress.set_position(95); + main_progress.set_message("Saving cache database…"); } - BuildFoldersPatch => { - progress.inc(1); - progress.set_message("Building patch…"); - } - ProcessFoldersPatch(n) => { - progress.set_length(n as u64); - progress.set_position(0); - progress.set_message("Processing patch…"); - } - ProcessFolderHunk(msg) => { - progress.inc(1); - progress.set_message(msg + "…"); - } - StartEnvelopesSync(folder, n, len) => { - multi.println(format!("[{n:2}/{len}] {folder}")).unwrap(); - progress.reset(); - } - GetLocalCachedEnvelopes => { - progress.set_length(4); - progress.set_message("Getting local cached envelopes…"); - } - GetLocalEnvelopes => { - progress.inc(1); - progress.set_message("Getting local maildir envelopes…"); - } - GetRemoteCachedEnvelopes => { - progress.inc(1); - progress.set_message("Getting remote cached envelopes…"); - } - GetRemoteEnvelopes => { - progress.inc(1); - progress.set_message("Getting remote envelopes…"); - } - BuildEnvelopesPatch => { - progress.inc(1); - progress.set_message("Building patch…"); - } - ProcessEnvelopesPatch(n) => { - progress.set_length(n as u64); - progress.set_position(0); - progress.set_message("Processing patch…"); - } - ProcessEnvelopeHunk(msg) => { - progress.inc(1); - progress.set_message(msg + "…"); + ExpungeFolders(folders) => { + let mut progresses = sub_progresses.lock().unwrap(); + for progress in progresses.values() { + progress.finish_and_clear() + } + progresses.clear(); + + main_progress.set_position(100); + main_progress.set_message(format!("Expunging {} folders…", folders.len())); } + _ => (), }) }) - .sync(backend)?; - - progress.finish_and_clear(); + .sync()?; let folders_patch_err = report .folders_patch @@ -268,18 +258,14 @@ pub fn sync( } } - if !report.envelopes_cache_patch.1.is_empty() { + if let Some(err) = report.envelopes_cache_patch.1 { printer.print_log("")?; - printer.print_log("Error occured while applying the envelopes cache patch:")?; - for err in report.envelopes_cache_patch.1 { - printer.print_log(format!(" - {err}"))?; - } + printer.print_log(format!( + "Error occured while applying the envelopes cache patch: {err}" + ))?; } - printer.print(format!( - "Account {} successfully synchronized!", - backend.name() - ))?; + printer.print("Account successfully synchronized!")?; } Ok(()) diff --git a/src/domain/backend/imap/handlers.rs b/src/domain/backend/imap/handlers.rs index d664d30..1a5b9f4 100644 --- a/src/domain/backend/imap/handlers.rs +++ b/src/domain/backend/imap/handlers.rs @@ -2,13 +2,15 @@ //! //! This module gathers all IMAP handlers triggered by the CLI. -use anyhow::{Context, Result}; +use anyhow::Result; use pimalaya_email::ImapBackend; -pub fn notify(imap: &ImapBackend, folder: &str, keepalive: u64) -> Result<()> { - imap.notify(keepalive, folder).context("cannot imap notify") +pub fn notify(imap: &mut ImapBackend, folder: &str, keepalive: u64) -> Result<()> { + imap.notify(keepalive, folder)?; + Ok(()) } -pub fn watch(imap: &ImapBackend, folder: &str, keepalive: u64) -> Result<()> { - imap.watch(keepalive, folder).context("cannot imap watch") +pub fn watch(imap: &mut ImapBackend, folder: &str, keepalive: u64) -> Result<()> { + imap.watch(keepalive, folder)?; + Ok(()) } diff --git a/src/domain/backend/imap/wizard.rs b/src/domain/backend/imap/wizard.rs index 685d985..32162c3 100644 --- a/src/domain/backend/imap/wizard.rs +++ b/src/domain/backend/imap/wizard.rs @@ -4,7 +4,7 @@ use pimalaya_email::{ BackendConfig, ImapAuthConfig, ImapConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig, }; -use pimalaya_oauth2::AuthorizationCodeGrant; +use pimalaya_oauth2::{AuthorizationCodeGrant, Client}; use pimalaya_secret::Secret; use crate::{ @@ -84,8 +84,8 @@ pub(crate) fn configure(account_name: &str, email: &str) -> Result { - Secret::new_keyring(format!("{account_name}-imap-passwd")) - .set(prompt_passwd("IMAP password")?)?; + Secret::new_keyring_entry(format!("{account_name}-imap-passwd")) + .set_keyring_entry_secret(prompt_passwd("IMAP password")?)?; PasswdConfig::default() } Some(idx) if SECRETS[idx] == RAW => PasswdConfig { @@ -125,8 +125,8 @@ pub(crate) fn configure(account_name: &str, email: &str) -> Result Result String { unimplemented!(); } - fn add_folder(&self, _: &str) -> backend::Result<()> { + fn add_folder(&mut self, _: &str) -> backend::Result<()> { unimplemented!(); } - fn list_folders(&self) -> backend::Result { + fn list_folders(&mut self) -> backend::Result { Ok(Folders::from_iter([ Folder { delim: "/".into(), @@ -152,23 +152,28 @@ mod tests { }, ])) } - fn expunge_folder(&self, _: &str) -> backend::Result<()> { + fn expunge_folder(&mut self, _: &str) -> backend::Result<()> { unimplemented!(); } - fn purge_folder(&self, _: &str) -> backend::Result<()> { + fn purge_folder(&mut self, _: &str) -> backend::Result<()> { unimplemented!(); } - fn delete_folder(&self, _: &str) -> backend::Result<()> { + fn delete_folder(&mut self, _: &str) -> backend::Result<()> { unimplemented!(); } - fn get_envelope(&self, _: &str, _: &str) -> backend::Result { + fn get_envelope(&mut self, _: &str, _: &str) -> backend::Result { unimplemented!(); } - fn list_envelopes(&self, _: &str, _: usize, _: usize) -> backend::Result { + fn list_envelopes( + &mut self, + _: &str, + _: usize, + _: usize, + ) -> backend::Result { unimplemented!() } fn search_envelopes( - &self, + &mut self, _: &str, _: &str, _: &str, @@ -177,35 +182,38 @@ mod tests { ) -> backend::Result { unimplemented!() } - fn add_email(&self, _: &str, _: &[u8], _: &Flags) -> backend::Result { + fn add_email(&mut self, _: &str, _: &[u8], _: &Flags) -> backend::Result { unimplemented!() } - fn get_emails(&self, _: &str, _: Vec<&str>) -> backend::Result { + fn get_emails(&mut self, _: &str, _: Vec<&str>) -> backend::Result { unimplemented!() } - fn preview_emails(&self, _: &str, _: Vec<&str>) -> backend::Result { + fn preview_emails(&mut self, _: &str, _: Vec<&str>) -> backend::Result { unimplemented!() } - fn copy_emails(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> { + fn copy_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> { unimplemented!() } - fn move_emails(&self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> { + fn move_emails(&mut self, _: &str, _: &str, _: Vec<&str>) -> backend::Result<()> { unimplemented!() } - fn delete_emails(&self, _: &str, _: Vec<&str>) -> backend::Result<()> { + fn delete_emails(&mut self, _: &str, _: Vec<&str>) -> backend::Result<()> { unimplemented!() } - fn add_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { + fn add_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { unimplemented!() } - fn set_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { + fn set_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { unimplemented!() } - fn remove_flags(&self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { + fn remove_flags(&mut self, _: &str, _: Vec<&str>, _: &Flags) -> backend::Result<()> { unimplemented!() } - fn as_any(&self) -> &(dyn Any) { - self + fn try_clone(&self) -> backend::Result> { + unimplemented!() + } + fn as_any(&self) -> &dyn Any { + unimplemented!() } } diff --git a/src/domain/sender/mod.rs b/src/domain/sender/mod.rs index 0d6be82..4a237dc 100644 --- a/src/domain/sender/mod.rs +++ b/src/domain/sender/mod.rs @@ -1,3 +1,4 @@ pub mod sendmail; +#[cfg(feature = "smtp-sender")] pub mod smtp; pub(crate) mod wizard; diff --git a/src/domain/sender/smtp/wizard.rs b/src/domain/sender/smtp/wizard.rs index 2572524..a041630 100644 --- a/src/domain/sender/smtp/wizard.rs +++ b/src/domain/sender/smtp/wizard.rs @@ -4,7 +4,7 @@ use pimalaya_email::{ OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig, SenderConfig, SmtpAuthConfig, SmtpConfig, }; -use pimalaya_oauth2::AuthorizationCodeGrant; +use pimalaya_oauth2::{AuthorizationCodeGrant, Client}; use pimalaya_secret::Secret; use crate::{ @@ -84,8 +84,8 @@ pub(crate) fn configure(account_name: &str, email: &str) -> Result let config = match secret { Some(idx) if SECRETS[idx] == KEYRING => { - Secret::new_keyring(format!("{account_name}-smtp-passwd")) - .set(prompt_passwd("SMTP password")?)?; + Secret::new_keyring_entry(format!("{account_name}-smtp-passwd")) + .set_keyring_entry_secret(prompt_passwd("SMTP password")?)?; PasswdConfig::default() } Some(idx) if SECRETS[idx] == RAW => PasswdConfig { @@ -125,8 +125,8 @@ pub(crate) fn configure(account_name: &str, email: &str) -> Result let client_secret: String = Input::with_theme(&*THEME) .with_prompt("SMTP OAuth 2.0 client secret") .interact()?; - Secret::new_keyring(format!("{account_name}-smtp-oauth2-client-secret")) - .set(&client_secret)?; + Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret")) + .set_keyring_entry_secret(&client_secret)?; config.auth_url = Input::with_theme(&*THEME) .with_prompt("SMTP OAuth 2.0 authorization URL") @@ -174,35 +174,42 @@ pub(crate) fn configure(account_name: &str, email: &str) -> Result wizard_log!("To complete your OAuth 2.0 setup, click on the following link:"); - let mut builder = AuthorizationCodeGrant::new( + let client = Client::new( config.client_id.clone(), client_secret, config.auth_url.clone(), config.token_url.clone(), - )?; + )? + .with_redirect_host(config.redirect_host.clone()) + .with_redirect_port(config.redirect_port) + .build()?; + + let mut auth_code_grant = AuthorizationCodeGrant::new() + .with_redirect_host(config.redirect_host.clone()) + .with_redirect_port(config.redirect_port); if config.pkce { - builder = builder.with_pkce(); + auth_code_grant = auth_code_grant.with_pkce(); } for scope in config.scopes.clone() { - builder = builder.with_scope(scope); + auth_code_grant = auth_code_grant.with_scope(scope); } - let client = builder.get_client()?; - let (redirect_url, csrf_token) = builder.get_redirect_url(&client); + let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client); println!("{}", redirect_url.to_string()); println!(""); - let (access_token, refresh_token) = builder.wait_for_redirection(client, csrf_token)?; + let (access_token, refresh_token) = + auth_code_grant.wait_for_redirection(&client, csrf_token)?; - Secret::new_keyring(format!("{account_name}-smtp-oauth2-access-token")) - .set(access_token)?; + Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token")) + .set_keyring_entry_secret(access_token)?; if let Some(refresh_token) = &refresh_token { - Secret::new_keyring(format!("{account_name}-smtp-oauth2-refresh-token")) - .set(refresh_token)?; + Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token")) + .set_keyring_entry_secret(refresh_token)?; } SmtpAuthConfig::OAuth2(config) diff --git a/src/main.rs b/src/main.rs index 7e7707d..2b48251 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,15 @@ use anyhow::{anyhow, Context, Result}; use clap::Command; +#[cfg(feature = "imap-backend")] +use pimalaya_email::ImapBackend; use pimalaya_email::{ - BackendBuilder, BackendConfig, ImapBackend, SenderBuilder, DEFAULT_INBOX_FOLDER, + BackendBuilder, BackendConfig, BackendSyncBuilder, SenderBuilder, DEFAULT_INBOX_FOLDER, }; use std::env; use url::Url; +#[cfg(feature = "imap-backend")] +use himalaya::imap; use himalaya::{ account, cache, compl, config::{self, DeserializedConfig}, @@ -14,9 +18,6 @@ use himalaya::{ tpl, IdMapper, }; -#[cfg(feature = "imap-backend")] -use himalaya::imap; - fn create_app() -> Command { let app = Command::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) @@ -42,7 +43,8 @@ fn create_app() -> Command { } #[allow(clippy::single_match)] -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { let default_env_filter = env_logger::DEFAULT_FILTER_ENV; env_logger::init_from_env(env_logger::Env::default().filter_or(default_env_filter, "off")); @@ -52,8 +54,8 @@ fn main() -> Result<()> { let url = Url::parse(&raw_args[1])?; let config = DeserializedConfig::from_opt_path(None)?; let account_config = config.to_account_config(None)?; - let mut backend = BackendBuilder::new().build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; + let mut backend = BackendBuilder::new(account_config.clone()).build()?; + let mut sender = SenderBuilder::new(account_config.clone()).build()?; let mut printer = StdoutPrinter::default(); return email::handlers::mailto( @@ -68,7 +70,7 @@ fn main() -> Result<()> { let app = create_app(); let m = app.get_matches(); - // checks completion command before configs + // check completion command before configs // https://github.com/soywod/himalaya/issues/115 match compl::args::matches(&m)? { Some(compl::args::Cmd::Generate(shell)) => { @@ -77,7 +79,7 @@ fn main() -> Result<()> { _ => (), } - // also checks man command before configs + // check also man command before configs match man::args::matches(&m)? { Some(man::args::Cmd::GenerateAll(dir)) => { return man::handlers::generate(dir, create_app()); @@ -85,53 +87,47 @@ fn main() -> Result<()> { _ => (), } - // inits config let config = DeserializedConfig::from_opt_path(config::args::parse_arg(&m))?; let account_config = config.to_account_config(account::args::parse_arg(&m))?; + let account_name = account_config.name.clone(); let folder = folder::args::parse_source_arg(&m); + let disable_cache = cache::args::parse_disable_cache_flag(&m); + + // FIXME: find why account config cannot be borrowed + // let backend_builder = + // BackendBuilder::new(Cow::Borrowed(&account_config)).with_cache_disabled(disable_cache); + let backend_builder = + BackendBuilder::new(account_config.clone()).with_cache_disabled(disable_cache); + let sender_builder = SenderBuilder::new(account_config.clone()); + let mut printer = StdoutPrinter::try_from(&m)?; - // checks IMAP commands #[cfg(feature = "imap-backend")] if let BackendConfig::Imap(imap_config) = &account_config.backend { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - - // FIXME: find a way to downcast `backend` instead of - // recreating an instance. match imap::args::matches(&m)? { Some(imap::args::Cmd::Notify(keepalive)) => { - let imap = ImapBackend::new(account_config.clone(), imap_config.clone())?; - return imap::handlers::notify(&imap, &folder, keepalive); + let mut backend = + ImapBackend::new(account_config.clone(), imap_config.clone(), None)?; + return imap::handlers::notify(&mut backend, &folder, keepalive); } Some(imap::args::Cmd::Watch(keepalive)) => { - let imap = ImapBackend::new(account_config.clone(), imap_config.clone())?; - return imap::handlers::watch(&imap, &folder, keepalive); + let mut backend = + ImapBackend::new(account_config.clone(), imap_config.clone(), None)?; + return imap::handlers::watch(&mut backend, &folder, keepalive); } _ => (), } } - // inits services - let disable_cache = cache::args::parse_disable_cache_flag(&m); - let mut printer = StdoutPrinter::try_from(&m)?; - - // checks account commands match account::args::matches(&m)? { Some(account::args::Cmd::List(max_width)) => { return account::handlers::list(max_width, &account_config, &config, &mut printer); } - Some(account::args::Cmd::Sync(folders_strategy, dry_run)) => { - let backend = BackendBuilder::new() - .sessions_pool_size(8) - .disable_cache(true) - .build(&account_config)?; - account::handlers::sync( - &account_config, - &mut printer, - backend.as_ref(), - folders_strategy, - dry_run, - )?; - backend.close()?; + Some(account::args::Cmd::Sync(strategy, dry_run)) => { + let sync_builder = BackendSyncBuilder::new(account_config, backend_builder)? + .with_some_folders_strategy(strategy) + .with_dry_run(dry_run); + account::handlers::sync(&mut printer, sync_builder, dry_run)?; return Ok(()); } Some(account::args::Cmd::Configure(reset)) => { @@ -147,15 +143,11 @@ fn main() -> Result<()> { .ok_or_else(|| anyhow!("the folder argument is missing")) .context("cannot create folder")?; let folder = account_config.folder_alias(folder)?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; + let mut backend = backend_builder.build()?; return folder::handlers::create(&mut printer, backend.as_mut(), &folder); } Some(folder::args::Cmd::List(max_width)) => { - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; + let mut backend = backend_builder.build()?; return folder::handlers::list( &account_config, &mut printer, @@ -165,9 +157,7 @@ fn main() -> Result<()> { } Some(folder::args::Cmd::Expunge) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; + let mut backend = backend_builder.build()?; return folder::handlers::expunge(&mut printer, backend.as_mut(), &folder); } Some(folder::args::Cmd::Delete) => { @@ -175,9 +165,7 @@ fn main() -> Result<()> { .ok_or_else(|| anyhow!("the folder argument is missing")) .context("cannot delete folder")?; let folder = account_config.folder_alias(folder)?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; + let mut backend = backend_builder.build()?; return folder::handlers::delete(&mut printer, backend.as_mut(), &folder); } _ => (), @@ -187,10 +175,8 @@ fn main() -> Result<()> { match email::args::matches(&m)? { Some(email::args::Cmd::Attachments(ids)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; return email::handlers::attachments( &account_config, &mut printer, @@ -202,10 +188,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Copy(ids, to_folder)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::copy( &account_config, &mut printer, @@ -218,10 +203,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Delete(ids)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::delete( &account_config, &mut printer, @@ -233,11 +217,10 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Forward(id, headers, body)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let mut sender = sender_builder.build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::forward( &account_config, &mut printer, @@ -252,10 +235,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::List(max_width, page_size, page)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::list( &account_config, &mut printer, @@ -269,10 +251,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Move(ids, to_folder)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::move_( &account_config, &mut printer, @@ -285,10 +266,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Read(ids, text_mime, raw, headers)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::read( &account_config, &mut printer, @@ -303,11 +283,10 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Reply(id, all, headers, body)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let mut sender = sender_builder.build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::reply( &account_config, &mut printer, @@ -323,10 +302,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Save(raw_email)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::save( &account_config, &mut printer, @@ -338,10 +316,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Search(query, max_width, page_size, page)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::search( &account_config, &mut printer, @@ -356,10 +333,9 @@ fn main() -> Result<()> { } Some(email::args::Cmd::Sort(criteria, query, max_width, page_size, page)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return email::handlers::sort( &account_config, &mut printer, @@ -374,10 +350,8 @@ fn main() -> Result<()> { ); } Some(email::args::Cmd::Send(raw_email)) => { - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; + let mut backend = backend_builder.build()?; + let mut sender = sender_builder.build()?; return email::handlers::send( &account_config, &mut printer, @@ -389,10 +363,9 @@ fn main() -> Result<()> { Some(email::args::Cmd::Flag(m)) => match m { Some(flag::args::Cmd::Set(ids, ref flags)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return flag::handlers::set( &mut printer, &id_mapper, @@ -404,10 +377,9 @@ fn main() -> Result<()> { } Some(flag::args::Cmd::Add(ids, ref flags)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return flag::handlers::add( &mut printer, &id_mapper, @@ -419,10 +391,9 @@ fn main() -> Result<()> { } Some(flag::args::Cmd::Remove(ids, ref flags)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return flag::handlers::remove( &mut printer, &id_mapper, @@ -437,10 +408,9 @@ fn main() -> Result<()> { Some(email::args::Cmd::Tpl(m)) => match m { Some(tpl::args::Cmd::Forward(id, headers, body)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return tpl::handlers::forward( &account_config, &mut printer, @@ -457,10 +427,9 @@ fn main() -> Result<()> { } Some(tpl::args::Cmd::Reply(id, all, headers, body)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return tpl::handlers::reply( &account_config, &mut printer, @@ -475,10 +444,9 @@ fn main() -> Result<()> { } Some(tpl::args::Cmd::Save(tpl)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let id_mapper = IdMapper::new(backend.as_ref(), &account_config.name, &folder)?; + let mut backend = backend_builder.clone().into_build()?; + let id_mapper = IdMapper::new(backend.as_ref(), &account_name, &folder)?; + return tpl::handlers::save( &account_config, &mut printer, @@ -490,10 +458,8 @@ fn main() -> Result<()> { } Some(tpl::args::Cmd::Send(tpl)) => { let folder = account_config.folder_alias(folder.unwrap_or(DEFAULT_INBOX_FOLDER))?; - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; + let mut backend = backend_builder.clone().into_build()?; + let mut sender = sender_builder.build()?; return tpl::handlers::send( &account_config, &mut printer, @@ -506,10 +472,8 @@ fn main() -> Result<()> { _ => (), }, Some(email::args::Cmd::Write(headers, body)) => { - let mut backend = BackendBuilder::new() - .disable_cache(disable_cache) - .build(&account_config)?; - let mut sender = SenderBuilder::new().build(&account_config)?; + let mut backend = backend_builder.build()?; + let mut sender = sender_builder.build()?; return email::handlers::write( &account_config, &mut printer,