diff --git a/Cargo.lock b/Cargo.lock index 663cb49..181a135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,7 +272,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -307,7 +307,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -679,7 +679,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -898,7 +898,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1192,9 +1192,9 @@ dependencies = [ [[package]] name = "email-lib" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3f51881639d657795ea95cd2fb065b8d9249912100b297730133ffbf38e0a5" +checksum = "743371f76482a94403ce0ab49da129065fc84cbcf9ed126524882c6ed5389efc" dependencies = [ "advisory-lock", "anyhow", @@ -1225,6 +1225,7 @@ dependencies = [ "rustls 0.22.1", "rustls-native-certs", "secret-lib", + "serde", "shellexpand-utils", "thiserror", "tokio", @@ -1269,7 +1270,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1290,7 +1291,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -1557,7 +1558,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -2734,15 +2735,17 @@ dependencies = [ [[package]] name = "keyring-lib" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2271300047bf0f2f4c4b6c51cbf7f848621aa373a3e80742bbdc0148110578" +checksum = "2d5fa6d6a6a7d27d09c3e5e6c0371d97dd4cc3b0208eaba9f26dae728f1ae072" dependencies = [ "keyring", "log", "once_cell", "secret-service", + "serde", "thiserror", + "tokio", ] [[package]] @@ -3011,9 +3014,9 @@ dependencies = [ [[package]] name = "mml-lib" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bda7bfafd584f65bc7bb85a60be2a6ccf6f384f1e1d27b697d8b6524869213" +checksum = "a5498df8f63c5c204deae46529a6cbddbe7f5cfe70723165e6fde42e8e2490df" dependencies = [ "async-recursion", "chumsky", @@ -3026,6 +3029,7 @@ dependencies = [ "pgp-lib", "process-lib", "secret-lib", + "serde", "shellexpand-utils", "thiserror", "tree_magic_mini", @@ -3129,7 +3133,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -3641,12 +3645,13 @@ dependencies = [ [[package]] name = "process-lib" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "651bb0e9f091cf2ff67fbc6151d92c97bb2af864bfdbb85def0442250e10562b" +checksum = "76cb01de71b99c9d36dacf51218b950e03f80a86ee5f910ff723a1693796bad3" dependencies = [ "log", "once_cell", + "serde", "thiserror", "tokio", ] @@ -4152,12 +4157,13 @@ dependencies = [ [[package]] name = "secret-lib" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5064a00687ed7f19a36e30a8b2695739d60f3d423f229097c8d110f9f76a8a96" +checksum = "b06c6eda723fc17a853234defb5784ec2b3377d9bce8a6577c89788f1b6dc117" dependencies = [ "keyring-lib", "process-lib", + "serde", "thiserror", ] @@ -4226,7 +4232,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -4258,7 +4264,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -4490,9 +4496,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" dependencies = [ "proc-macro2", "quote", @@ -4588,7 +4594,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -4664,7 +4670,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -4786,7 +4792,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -5014,7 +5020,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-shared", ] @@ -5048,7 +5054,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5487,7 +5493,7 @@ checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] @@ -5507,7 +5513,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.40", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7133954..0394966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,20 +50,20 @@ clap_mangen = "0.2" console = "0.15.2" dialoguer = "0.10.2" dirs = "4.0" -email-lib = { version = "=0.16.0", default-features = false } +email-lib = { version = "=0.17.1", default-features = false } email_address = "0.2.4" env_logger = "0.8" erased-serde = "0.3" indicatif = "0.17" -keyring-lib = "=0.2.0" +keyring-lib = "=0.3.0" log = "0.4" mail-builder = "0.3" md5 = "0.7.0" -mml-lib = { version = "=1.0.2", default-features = false } +mml-lib = { version = "=1.0.3", default-features = false } oauth-lib = "=0.1.0" once_cell = "1.16" -process-lib = "=0.2.0" -secret-lib = "=0.2.0" +process-lib = "=0.3.0" +secret-lib = "=0.3.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" shellexpand-utils = "=0.2.0" diff --git a/README.md b/README.md index fa85dc8..0cbd544 100644 --- a/README.md +++ b/README.md @@ -85,35 +85,22 @@ Please read the [documentation](https://pimalaya.org/himalaya/cli/configuration/ ## Contributing -If you find a **bug** that [does not exist yet](https://todo.sr.ht/~soywod/pimalaya), please send an email at [~soywod/pimalaya@todo.sr.ht](mailto:~soywod/pimalaya@todo.sr.ht). +If you want to **report a bug** that [does not exist yet](https://todo.sr.ht/~soywod/pimalaya), please send an email at [~soywod/pimalaya@todo.sr.ht](mailto:~soywod/pimalaya@todo.sr.ht). -If you have a **question**, please send an email at [~soywod/pimalaya@lists.sr.ht](mailto:~soywod/pimalaya@lists.sr.ht). +If you want to **propose a feature** or **fix a bug**, please send a patch at [~soywod/pimalaya@lists.sr.ht](mailto:~soywod/pimalaya@lists.sr.ht) using [git send-email](https://git-scm.com/docs/git-send-email). Follow [this guide](https://git-send-email.io/) to configure git properly. -If you want to **propose a feature** or **fix a bug**, please send a patch at [~soywod/pimalaya@lists.sr.ht](mailto:~soywod/pimalaya@lists.sr.ht) using [git send-email](https://git-scm.com/docs/git-send-email) (see [this guide](https://git-send-email.io/) on how to configure it). +If you just want to **discuss** about the project, feel free to join the [Matrix](https://matrix.org/) workspace [#pimalaya.general](https://matrix.to/#/#pimalaya.general:matrix.org) or contact me directly [@soywod](https://matrix.to/#/@soywod:matrix.org). You can also use the mailing list [[send an email](mailto:~soywod/pimalaya@lists.sr.ht)|[subscribe](mailto:~soywod/pimalaya+subscribe@lists.sr.ht)|[unsubscribe](mailto:~soywod/pimalaya+unsubscribe@lists.sr.ht)]. -If you want to **subscribe** to the mailing list, please send an email at [~soywod/pimalaya+subscribe@lists.sr.ht](mailto:~soywod/pimalaya+subscribe@lists.sr.ht). - -If you want to **unsubscribe** to the mailing list, please send an email at [~soywod/pimalaya+unsubscribe@lists.sr.ht](mailto:~soywod/pimalaya+unsubscribe@lists.sr.ht). - -If you want to **discuss** about the project, feel free to join the [Matrix](https://matrix.org/) workspace [#pimalaya.himalaya](https://matrix.to/#/#pimalaya.himalaya:matrix.org) or contact me directly [@soywod](https://matrix.to/#/@soywod:matrix.org). - -## Credits +## Sponsoring [![nlnet](https://nlnet.nl/logo/banner-160x60.png)](https://nlnet.nl/project/Himalaya/index.html) -Special thanks to the [nlnet](https://nlnet.nl/project/Himalaya/index.html) foundation that helped Himalaya to receive financial support from the [NGI Assure](https://www.ngi.eu/ngi-projects/ngi-assure/) program of the European Commission in September, 2022. +Special thanks to the [NLnet foundation](https://nlnet.nl/project/Himalaya/index.html) and the [European Commission](https://www.ngi.eu/) that helped the project to receive financial support from: -- [IMAP RFC3501](https://tools.ietf.org/html/rfc3501) -- [Iris](https://github.com/soywod/iris.vim), the himalaya predecessor -- [isync](https://isync.sourceforge.io/), an email synchronizer for offline usage -- [NeoMutt](https://neomutt.org/), an email terminal user interface -- [Alpine](http://alpine.x10host.com/alpine/alpine-info/), an other email terminal user interface -- [mutt-wizard](https://github.com/LukeSmithxyz/mutt-wizard), a tool over NeoMutt and isync -- [rust-imap](https://github.com/jonhoo/rust-imap), a Rust IMAP library -- [lettre](https://github.com/lettre/lettre), a Rust mailer library -- [mailparse](https://github.com/staktrace/mailparse), a Rust MIME email parser. +- [NGI Assure](https://nlnet.nl/assure/) in 2022 +- [NGI Zero Untrust](https://nlnet.nl/entrust/) in 2023 -## Sponsoring +If you appreciate the project, feel free to donate using one of the following providers: [![GitHub](https://img.shields.io/badge/-GitHub%20Sponsors-fafbfc?logo=GitHub%20Sponsors)](https://github.com/sponsors/soywod) [![PayPal](https://img.shields.io/badge/-PayPal-0079c1?logo=PayPal&logoColor=ffffff)](https://www.paypal.com/paypalme/soywod) diff --git a/config.sample.toml b/config.sample.toml index ca17825..31da751 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -1,30 +1,58 @@ [example] +# Make this account the default one to use when no account is given to +# commands. default = true -# The display-name and email are used to build the full email address: -# "My example account" +# The display-name and the email are used to build the full email +# address: "My example account" display-name = "My example account" email = "example@localhost" -sync = true -sync-dir = "/tmp/himalaya-sync-example" +# The signature can be a string or a path to a file. +signature = "Regards," +signature-delim = "-- \n" -# The default backend used for all the features like adding folders, +# Enable the synchronization for this account. Running the command +# `account sync example` will synchronize all folders and all emails +# to a local Maildir at `$XDG_DATA_HOME/himalaya/example`. +sync.enable = true + +# Override the default Maildir path for synchronization. +sync.dir = "/tmp/himalaya-sync-example" + +# Default backend used for all the features like adding folders, # listing envelopes or copying messages. backend = "imap" +envelope.list.page-size = 10 +envelope.list.datetime-fmt = "%F %R%:z" + +# Date are converted to the user's local timezone. +envelope.list.datetime-local-tz = true + +# Override the backend used for listing envelopes. +envelope.list.backend = "imap" + +# Override the backend used for sending messages. +message.send.backend = "smtp" + # IMAP config imap.host = "localhost" imap.port = 3143 imap.login = "example@localhost" imap.ssl = false imap.starttls = false -imap.insecure = true -imap.auth = "passwd" -imap.passwd.keyring = "example-passwd" +imap.auth = "passwd" # or oauth2 -# Override the backend used for sending messages. -message.send.backend = "smtp" +# Get password from the raw string (not safe) +# imap.passwd.raw = "password" + +# Get password from a shell command +imap.passwd.cmd = ["echo example-imap-password", "cat"] + +# Get password from your system keyring using secret service +# Keyring secrets can be (re)set with the command `account configure example` +# imap.passwd.keyring = "example-imap-password" # SMTP config smtp.host = "localhost" @@ -32,25 +60,9 @@ smtp.port = 3025 smtp.login = "example@localhost" smtp.ssl = false smtp.starttls = false -smtp.insecure = true smtp.auth = "passwd" -smtp.passwd.raw = "example" +smtp.passwd.raw = "password" -[example-2] -display-name = "My example account 2" -email = "example2@localhost" - -backend = "imap" - -imap.host = "localhost" -imap.port = 3143 -imap.login = "example2@localhost" -imap.ssl = false -imap.starttls = false -imap.insecure = true -imap.auth = "passwd" -imap.passwd.raw = "example" - -message.send.backend = "sendmail" - -sendmail.cmd = "sendmail" +# PGP needs to be enabled with one of those cargo feature: +# pgp-commands, pgp-gpg or pgp-native +# pgp.backend = "gpg" diff --git a/src/account/command/configure.rs b/src/account/command/configure.rs index 616e97c..49012d9 100644 --- a/src/account/command/configure.rs +++ b/src/account/command/configure.rs @@ -44,8 +44,8 @@ impl AccountConfigureCommand { #[cfg(feature = "imap")] if let Some(ref config) = account_config.imap { let reset = match &config.auth { - ImapAuthConfig::Passwd(config) => config.reset(), - ImapAuthConfig::OAuth2(config) => config.reset(), + ImapAuthConfig::Passwd(config) => config.reset().await, + ImapAuthConfig::OAuth2(config) => config.reset().await, }; if let Err(err) = reset { warn!("error while resetting imap secrets: {err}"); @@ -56,8 +56,8 @@ impl AccountConfigureCommand { #[cfg(feature = "smtp")] if let Some(ref config) = account_config.smtp { let reset = match &config.auth { - SmtpAuthConfig::Passwd(config) => config.reset(), - SmtpAuthConfig::OAuth2(config) => config.reset(), + SmtpAuthConfig::Passwd(config) => config.reset().await, + SmtpAuthConfig::OAuth2(config) => config.reset().await, }; if let Err(err) = reset { warn!("error while resetting smtp secrets: {err}"); @@ -67,7 +67,7 @@ impl AccountConfigureCommand { #[cfg(feature = "pgp")] if let Some(ref config) = account_config.pgp { - account_config.pgp.reset().await?; + config.reset().await?; } } @@ -100,10 +100,11 @@ impl AccountConfigureCommand { } #[cfg(feature = "pgp")] - if let Some(ref config) = config.pgp { + if let Some(ref config) = account_config.pgp { config - .pgp - .configure(&config.email, || prompt_passwd("PGP secret key password")) + .configure(&account_config.email, || { + prompt_passwd("PGP secret key password") + }) .await?; } diff --git a/src/account/command/list.rs b/src/account/command/list.rs index 5b6a72a..1c8e4ce 100644 --- a/src/account/command/list.rs +++ b/src/account/command/list.rs @@ -28,10 +28,7 @@ impl AccountListCommand { printer.print_table( Box::new(accounts), PrintTableOpts { - format: config - .email_reading_format - .as_ref() - .unwrap_or(&Default::default()), + format: &Default::default(), max_width: self.table.max_width, }, ) diff --git a/src/account/config.rs b/src/account/config.rs index a916dbb..85078be 100644 --- a/src/account/config.rs +++ b/src/account/config.rs @@ -4,26 +4,21 @@ //! account in the accounts section of the user configuration file. #[cfg(feature = "pgp")] -use email::account::PgpConfig; +use email::account::config::pgp::PgpConfig; #[cfg(feature = "imap")] use email::imap::config::ImapConfig; #[cfg(feature = "smtp")] use email::smtp::config::SmtpConfig; use email::{ - email::config::{EmailHooks, EmailTextPlainFormat}, - folder::sync::FolderSyncStrategy, - maildir::config::MaildirConfig, + account::sync::config::SyncConfig, maildir::config::MaildirConfig, sendmail::config::SendmailConfig, }; use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - path::PathBuf, -}; +use std::{collections::HashSet, path::PathBuf}; use crate::{ - backend::BackendKind, config::prelude::*, envelope::config::EnvelopeConfig, - flag::config::FlagConfig, folder::config::FolderConfig, message::config::MessageConfig, + backend::BackendKind, envelope::config::EnvelopeConfig, flag::config::FlagConfig, + folder::config::FolderConfig, message::config::MessageConfig, }; /// Represents all existing kind of account config. @@ -34,93 +29,29 @@ pub struct TomlAccountConfig { pub email: String, pub display_name: Option, - pub signature_delim: Option, pub signature: Option, + pub signature_delim: Option, pub downloads_dir: Option, - pub folder_listing_page_size: Option, - pub folder_aliases: Option>, - - pub email_listing_page_size: Option, - pub email_listing_datetime_fmt: Option, - pub email_listing_datetime_local_tz: Option, - pub email_reading_headers: Option>, - #[serde( - default, - with = "OptionEmailTextPlainFormatDef", - skip_serializing_if = "Option::is_none" - )] - pub email_reading_format: Option, - pub email_writing_headers: Option>, - pub email_sending_save_copy: Option, - #[serde( - default, - with = "OptionEmailHooksDef", - skip_serializing_if = "Option::is_none" - )] - pub email_hooks: Option, - - pub sync: Option, - pub sync_dir: Option, - #[serde( - default, - with = "OptionFolderSyncStrategyDef", - skip_serializing_if = "Option::is_none" - )] - pub sync_folders_strategy: Option, - - pub backend: Option, - + pub sync: Option, pub folder: Option, pub envelope: Option, pub flag: Option, pub message: Option, - - #[cfg(feature = "imap")] - #[serde( - default, - with = "OptionImapConfigDef", - skip_serializing_if = "Option::is_none" - )] - pub imap: Option, - - #[serde( - default, - with = "OptionMaildirConfigDef", - skip_serializing_if = "Option::is_none" - )] - pub maildir: Option, - - #[cfg(feature = "notmuch")] - #[serde( - default, - with = "OptionNotmuchConfigDef", - skip_serializing_if = "Option::is_none" - )] - pub notmuch: Option, - - #[cfg(feature = "smtp")] - #[serde( - default, - with = "OptionSmtpConfigDef", - skip_serializing_if = "Option::is_none" - )] - pub smtp: Option, - - #[serde( - default, - with = "OptionSendmailConfigDef", - skip_serializing_if = "Option::is_none" - )] - pub sendmail: Option, - #[cfg(feature = "pgp")] - #[serde( - default, - with = "OptionPgpConfigDef", - skip_serializing_if = "Option::is_none" - )] pub pgp: Option, + + pub backend: Option, + #[cfg(feature = "maildir")] + pub maildir: Option, + #[cfg(feature = "imap")] + pub imap: Option, + #[cfg(feature = "notmuch")] + pub notmuch: Option, + #[cfg(feature = "smtp")] + pub smtp: Option, + #[cfg(feature = "sendmail")] + pub sendmail: Option, } impl TomlAccountConfig { @@ -207,7 +138,7 @@ impl TomlAccountConfig { pub fn add_raw_message_kind(&self) -> Option<&BackendKind> { self.message .as_ref() - .and_then(|msg| msg.add.as_ref()) + .and_then(|msg| msg.write.as_ref()) .and_then(|add| add.backend.as_ref()) .or_else(|| self.backend.as_ref()) } @@ -223,7 +154,7 @@ impl TomlAccountConfig { pub fn get_messages_kind(&self) -> Option<&BackendKind> { self.message .as_ref() - .and_then(|message| message.get.as_ref()) + .and_then(|message| message.read.as_ref()) .and_then(|get| get.backend.as_ref()) .or_else(|| self.backend.as_ref()) } diff --git a/src/account/wizard.rs b/src/account/wizard.rs index cb31df9..aff4bf4 100644 --- a/src/account/wizard.rs +++ b/src/account/wizard.rs @@ -1,5 +1,6 @@ use anyhow::{bail, Result}; use dialoguer::{Confirm, Input}; +use email::account::sync::config::SyncConfig; use email_address::EmailAddress; use crate::{ @@ -87,15 +88,20 @@ pub(crate) async fn configure() -> Result> { _ => (), }; - config.sync = Some( - Confirm::new() - .with_prompt(wizard_prompt!( - "Do you need an offline access to your account?" - )) - .default(false) - .interact_opt()? - .unwrap_or_default(), - ); + let should_configure_sync = Confirm::new() + .with_prompt(wizard_prompt!( + "Do you need an offline access to your account?" + )) + .default(false) + .interact_opt()? + .unwrap_or_default(); + + if should_configure_sync { + config.sync = Some(SyncConfig { + enable: Some(true), + ..Default::default() + }); + } Ok(Some((account_name, config))) } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index cae0b9b..05efa54 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -173,7 +173,7 @@ impl BackendBuilder { MaildirSessionBuilder::new(account_config.clone(), mdir_config.clone()) }), maildir_for_sync: Some(MaildirConfig { - root_dir: account_config.sync_dir()?, + root_dir: account_config.get_sync_dir()?, }) .filter(|_| is_maildir_for_sync_used) .map(|mdir_config| MaildirSessionBuilder::new(account_config.clone(), mdir_config)), @@ -691,7 +691,7 @@ impl Backend { id_mapper = IdMapper::new( &self.backend.account_config, folder, - self.backend.account_config.sync_dir()?, + self.backend.account_config.get_sync_dir()?, )?; } #[cfg(feature = "notmuch")] diff --git a/src/config/mod.rs b/src/config/mod.rs index 3d96fb1..3cd780d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,14 +1,12 @@ pub mod args; -pub mod prelude; pub mod wizard; use anyhow::{anyhow, Context, Result}; use dialoguer::Confirm; use dirs::{config_dir, home_dir}; use email::{ - account::config::AccountConfig, - config::Config, - email::config::{EmailHooks, EmailTextPlainFormat}, + account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig, + folder::config::FolderConfig, message::config::MessageConfig, }; use serde::{Deserialize, Serialize}; use shellexpand_utils::{canonicalize, expand}; @@ -20,10 +18,7 @@ use std::{ }; use toml; -use crate::{ - account::config::TomlAccountConfig, backend::BackendKind, config::prelude::*, wizard_prompt, - wizard_warn, -}; +use crate::{account::config::TomlAccountConfig, backend::BackendKind, wizard_prompt, wizard_warn}; /// Represents the user config file. #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] @@ -31,32 +26,10 @@ use crate::{ pub struct TomlConfig { #[serde(alias = "name")] pub display_name: Option, - pub signature_delim: Option, pub signature: Option, + pub signature_delim: Option, pub downloads_dir: Option, - pub folder_listing_page_size: Option, - pub folder_aliases: Option>, - - pub email_listing_page_size: Option, - pub email_listing_datetime_fmt: Option, - pub email_listing_datetime_local_tz: Option, - pub email_reading_headers: Option>, - #[serde( - default, - with = "OptionEmailTextPlainFormatDef", - skip_serializing_if = "Option::is_none" - )] - pub email_reading_format: Option, - pub email_writing_headers: Option>, - pub email_sending_save_copy: Option, - #[serde( - default, - with = "OptionEmailHooksDef", - skip_serializing_if = "Option::is_none" - )] - pub email_hooks: Option, - #[serde(flatten)] pub accounts: HashMap, } @@ -203,7 +176,7 @@ impl TomlConfig { let (account_name, mut toml_account_config) = self.into_toml_account_config(account_name)?; - if let Some(true) = toml_account_config.sync { + if let Some(true) = toml_account_config.sync.as_ref().and_then(|c| c.enable) { if !disable_cache { toml_account_config.backend = Some(BackendKind::MaildirForSync); } @@ -211,22 +184,10 @@ impl TomlConfig { let config = Config { display_name: self.display_name, - signature_delim: self.signature_delim, signature: self.signature, + signature_delim: self.signature_delim, downloads_dir: self.downloads_dir, - folder_listing_page_size: self.folder_listing_page_size, - folder_aliases: self.folder_aliases, - - email_listing_page_size: self.email_listing_page_size, - email_listing_datetime_fmt: self.email_listing_datetime_fmt, - email_listing_datetime_local_tz: self.email_listing_datetime_local_tz, - email_reading_headers: self.email_reading_headers, - email_reading_format: self.email_reading_format, - email_writing_headers: self.email_writing_headers, - email_sending_save_copy: self.email_sending_save_copy, - email_hooks: self.email_hooks, - accounts: HashMap::from_iter(self.accounts.clone().into_iter().map( |(name, config)| { ( @@ -235,27 +196,23 @@ impl TomlConfig { name, email: config.email, display_name: config.display_name, - signature_delim: config.signature_delim, signature: config.signature, + signature_delim: config.signature_delim, downloads_dir: config.downloads_dir, - folder_listing_page_size: config.folder_listing_page_size, - folder_aliases: config.folder_aliases.unwrap_or_default(), - - email_listing_page_size: config.email_listing_page_size, - email_listing_datetime_fmt: config.email_listing_datetime_fmt, - email_listing_datetime_local_tz: config.email_listing_datetime_local_tz, - - email_reading_headers: config.email_reading_headers, - email_reading_format: config.email_reading_format.unwrap_or_default(), - email_writing_headers: config.email_writing_headers, - email_sending_save_copy: config.email_sending_save_copy, - email_hooks: config.email_hooks.unwrap_or_default(), - - sync: config.sync.unwrap_or_default(), - sync_dir: config.sync_dir, - sync_folders_strategy: config.sync_folders_strategy.unwrap_or_default(), - + folder: config.folder.map(|c| FolderConfig { + aliases: c.remote.aliases, + list: c.list.map(|c| c.remote), + }), + envelope: config.envelope.map(|c| EnvelopeConfig { + list: c.list.map(|c| c.remote), + }), + message: config.message.map(|c| MessageConfig { + read: c.read.map(|c| c.remote), + write: c.write.map(|c| c.remote), + send: c.send.map(|c| c.remote), + }), + sync: config.sync, #[cfg(feature = "pgp")] pgp: config.pgp, }, diff --git a/src/email/envelope/command/list.rs b/src/email/envelope/command/list.rs index 5528e00..a1a0721 100644 --- a/src/email/envelope/command/list.rs +++ b/src/email/envelope/command/list.rs @@ -58,7 +58,7 @@ impl EnvelopeListCommand { let page_size = self .page_size - .unwrap_or(account_config.email_listing_page_size()); + .unwrap_or(account_config.get_envelope_list_page_size()); let page = 1.max(self.page) - 1; let envelopes = backend.list_envelopes(folder, page_size, page).await?; @@ -66,7 +66,7 @@ impl EnvelopeListCommand { printer.print_table( Box::new(envelopes), PrintTableOpts { - format: &account_config.email_reading_format, + format: &account_config.get_message_read_format(), max_width: self.table.max_width, }, )?; diff --git a/src/email/envelope/config.rs b/src/email/envelope/config.rs index fa7f5d8..20855f8 100644 --- a/src/email/envelope/config.rs +++ b/src/email/envelope/config.rs @@ -28,6 +28,9 @@ impl EnvelopeConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct EnvelopeListConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::envelope::list::config::EnvelopeListConfig, } impl EnvelopeListConfig { diff --git a/src/email/message/attachment/command/download.rs b/src/email/message/attachment/command/download.rs index cb599dc..c1d3b5c 100644 --- a/src/email/message/attachment/command/download.rs +++ b/src/email/message/attachment/command/download.rs @@ -65,12 +65,11 @@ impl AttachmentDownloadCommand { ))?; for attachment in attachments { - let filename = attachment + let filename: PathBuf = attachment .filename - .map(PathBuf::from) - .and_then(|f| f.file_name().map(|f| f.to_string_lossy().to_string())) - .unwrap_or_else(|| Uuid::new_v4().to_string()); - let filepath = account_config.download_fpath(&filename)?; + .unwrap_or_else(|| Uuid::new_v4().to_string()) + .into(); + let filepath = account_config.get_download_file_path(&filename)?; printer.print_log(format!("Downloading {:?}…", filepath))?; fs::write(&filepath, &attachment.body) .with_context(|| format!("cannot save attachment at {filepath:?}"))?; diff --git a/src/email/message/command/mailto.rs b/src/email/message/command/mailto.rs index a58f0ca..7a100fa 100644 --- a/src/email/message/command/mailto.rs +++ b/src/email/message/command/mailto.rs @@ -60,7 +60,7 @@ impl MessageMailtoCommand { } } - match account_config.signature() { + match account_config.find_full_signature() { Ok(Some(ref signature)) => builder = builder.text_body(body + "\n\n" + signature), Ok(None) => builder = builder.text_body(body), Err(err) => { @@ -71,7 +71,7 @@ impl MessageMailtoCommand { let tpl = account_config .generate_tpl_interpreter() - .with_show_only_headers(account_config.email_writing_headers()) + .with_show_only_headers(account_config.get_message_write_headers()) .build() .from_msg_builder(builder) .await?; diff --git a/src/email/message/command/send.rs b/src/email/message/command/send.rs index dc87267..1efceca 100644 --- a/src/email/message/command/send.rs +++ b/src/email/message/command/send.rs @@ -35,7 +35,7 @@ impl MessageSendCommand { let (toml_account_config, account_config) = config.clone().into_account_configs(account, cache)?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; - let folder = account_config.sent_folder_alias()?; + let folder = account_config.get_sent_folder_alias()?; let is_tty = io::stdin().is_terminal(); let is_json = printer.is_json(); @@ -52,7 +52,7 @@ impl MessageSendCommand { backend.send_raw_message(msg.as_bytes()).await?; - if account_config.email_sending_save_copy.unwrap_or_default() { + if account_config.should_save_copy_sent_message() { backend .add_raw_message_with_flag(&folder, msg.as_bytes(), Flag::Seen) .await?; diff --git a/src/email/message/config.rs b/src/email/message/config.rs index 0e3f080..4d95ef4 100644 --- a/src/email/message/config.rs +++ b/src/email/message/config.rs @@ -5,12 +5,12 @@ use crate::backend::BackendKind; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct MessageConfig { - pub add: Option, + pub write: Option, pub send: Option, pub peek: Option, - pub get: Option, + pub read: Option, pub copy: Option, - #[serde(default, rename = "move", skip_serializing_if = "Option::is_none")] + #[serde(rename = "move")] pub move_: Option, } @@ -18,7 +18,7 @@ impl MessageConfig { pub fn get_used_backends(&self) -> HashSet<&BackendKind> { let mut kinds = HashSet::default(); - if let Some(add) = &self.add { + if let Some(add) = &self.write { kinds.extend(add.get_used_backends()); } @@ -30,7 +30,7 @@ impl MessageConfig { kinds.extend(peek.get_used_backends()); } - if let Some(get) = &self.get { + if let Some(get) = &self.read { kinds.extend(get.get_used_backends()); } @@ -49,6 +49,9 @@ impl MessageConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct MessageAddConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::message::add_raw::config::MessageWriteConfig, } impl MessageAddConfig { @@ -66,6 +69,9 @@ impl MessageAddConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct MessageSendConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::message::send_raw::config::MessageSendConfig, } impl MessageSendConfig { @@ -100,6 +106,9 @@ impl MessagePeekConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct MessageGetConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::message::get::config::MessageReadConfig, } impl MessageGetConfig { diff --git a/src/email/message/template/command/save.rs b/src/email/message/template/command/save.rs index c95a8c7..b893c12 100644 --- a/src/email/message/template/command/save.rs +++ b/src/email/message/template/command/save.rs @@ -60,7 +60,7 @@ impl TemplateSaveCommand { let mut compiler = MmlCompilerBuilder::new(); #[cfg(feature = "pgp")] - compiler.set_some_pgp(config.pgp.clone()); + compiler.set_some_pgp(account_config.pgp.clone()); let msg = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; backend.add_raw_message(folder, &msg).await?; diff --git a/src/email/message/template/command/send.rs b/src/email/message/template/command/send.rs index 917affa..f732212 100644 --- a/src/email/message/template/command/send.rs +++ b/src/email/message/template/command/send.rs @@ -38,7 +38,7 @@ impl TemplateSendCommand { let (toml_account_config, account_config) = config.clone().into_account_configs(account, cache)?; let backend = Backend::new(toml_account_config, account_config.clone(), true).await?; - let folder = account_config.sent_folder_alias()?; + let folder = account_config.get_sent_folder_alias()?; let is_tty = io::stdin().is_terminal(); let is_json = printer.is_json(); @@ -57,13 +57,13 @@ impl TemplateSendCommand { let mut compiler = MmlCompilerBuilder::new(); #[cfg(feature = "pgp")] - compiler.set_some_pgp(config.pgp.clone()); + compiler.set_some_pgp(account_config.pgp.clone()); let msg = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; backend.send_raw_message(&msg).await?; - if account_config.email_sending_save_copy.unwrap_or_default() { + if account_config.should_save_copy_sent_message() { backend .add_raw_message_with_flag(&folder, &msg, Flag::Seen) .await?; diff --git a/src/folder/command/list.rs b/src/folder/command/list.rs index 8809b92..accf287 100644 --- a/src/folder/command/list.rs +++ b/src/folder/command/list.rs @@ -42,7 +42,7 @@ impl FolderListCommand { printer.print_table( Box::new(folders), PrintTableOpts { - format: &account_config.email_reading_format, + format: &account_config.get_message_read_format(), max_width: self.table.max_width, }, )?; diff --git a/src/folder/config.rs b/src/folder/config.rs index 16250f9..5f4bed4 100644 --- a/src/folder/config.rs +++ b/src/folder/config.rs @@ -10,6 +10,9 @@ pub struct FolderConfig { pub expunge: Option, pub purge: Option, pub delete: Option, + + #[serde(flatten)] + pub remote: email::folder::config::FolderConfig, } impl FolderConfig { @@ -60,6 +63,9 @@ impl FolderAddConfig { #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct FolderListConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::folder::list::config::FolderListConfig, } impl FolderListConfig { diff --git a/src/imap/wizard.rs b/src/imap/wizard.rs index 5efccfa..a3cacd8 100644 --- a/src/imap/wizard.rs +++ b/src/imap/wizard.rs @@ -89,7 +89,8 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result { Secret::new_keyring_entry(format!("{account_name}-imap-passwd")) - .set_keyring_entry_secret(prompt_passwd("IMAP password")?)?; + .set_keyring_entry_secret(prompt_passwd("IMAP password")?) + .await?; PasswdConfig::default() } Some(idx) if SECRETS[idx] == RAW => PasswdConfig { @@ -130,7 +131,8 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result Result Result { Secret::new_keyring_entry(format!("{account_name}-smtp-passwd")) - .set_keyring_entry_secret(prompt_passwd("SMTP password")?)?; + .set_keyring_entry_secret(prompt_passwd("SMTP password")?) + .await?; PasswdConfig::default() } Some(idx) if SECRETS[idx] == RAW => PasswdConfig { @@ -130,7 +131,8 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result Result( backend.send_raw_message(&email).await?; - if config.email_sending_save_copy.unwrap_or_default() { - let sent_folder = config.sent_folder_alias()?; + if config.should_save_copy_sent_message() { + let sent_folder = config.get_sent_folder_alias()?; printer.print_log(format!("Adding email to the {} folder…", sent_folder))?; backend .add_raw_message_with_flag(&sent_folder, &email, Flag::Seen)