fix config and oauth2

This commit is contained in:
Clément DOUIN 2023-12-04 16:25:56 +01:00
parent c54ada730b
commit ea9c28b9d7
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
9 changed files with 453 additions and 177 deletions

View file

@ -12,17 +12,17 @@ use crate::{config::wizard::THEME, maildir, sendmail};
use super::{config::BackendConfig, BackendKind}; use super::{config::BackendConfig, BackendKind};
const DEFAULT_BACKEND_KINDS: &[BackendKind] = &[ const DEFAULT_BACKEND_KINDS: &[BackendKind] = &[
BackendKind::Maildir,
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
BackendKind::Imap, BackendKind::Imap,
BackendKind::Maildir,
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
BackendKind::Notmuch, BackendKind::Notmuch,
]; ];
const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[ const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
BackendKind::Sendmail,
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
BackendKind::Smtp, BackendKind::Smtp,
BackendKind::Sendmail,
]; ];
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Option<BackendConfig>> { pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Option<BackendConfig>> {
@ -52,7 +52,7 @@ pub(crate) async fn configure_sender(
email: &str, email: &str,
) -> Result<Option<BackendConfig>> { ) -> Result<Option<BackendConfig>> {
let kind = Select::with_theme(&*THEME) let kind = Select::with_theme(&*THEME)
.with_prompt("Default email backend") .with_prompt("Backend for sending messages")
.items(SEND_MESSAGE_BACKEND_KINDS) .items(SEND_MESSAGE_BACKEND_KINDS)
.default(0) .default(0)
.interact_opt()? .interact_opt()?

View file

@ -44,11 +44,19 @@ pub struct TomlConfig {
pub email_listing_datetime_fmt: Option<String>, pub email_listing_datetime_fmt: Option<String>,
pub email_listing_datetime_local_tz: Option<bool>, pub email_listing_datetime_local_tz: Option<bool>,
pub email_reading_headers: Option<Vec<String>>, pub email_reading_headers: Option<Vec<String>>,
#[serde(default, with = "OptionEmailTextPlainFormatDef")] #[serde(
default,
with = "OptionEmailTextPlainFormatDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_format: Option<EmailTextPlainFormat>, pub email_reading_format: Option<EmailTextPlainFormat>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
pub email_sending_save_copy: Option<bool>, pub email_sending_save_copy: Option<bool>,
#[serde(default, with = "OptionEmailHooksDef")] #[serde(
default,
with = "OptionEmailHooksDef",
skip_serializing_if = "Option::is_none"
)]
pub email_hooks: Option<EmailHooks>, pub email_hooks: Option<EmailHooks>,
#[serde(flatten)] #[serde(flatten)]
@ -78,7 +86,7 @@ impl TomlConfig {
let confirm = Confirm::new() let confirm = Confirm::new()
.with_prompt(wizard_prompt!( .with_prompt(wizard_prompt!(
"Would you like to create it with the wizard?" "Would you like to create one with the wizard?"
)) ))
.default(true) .default(true)
.interact_opt()? .interact_opt()?
@ -88,7 +96,7 @@ impl TomlConfig {
process::exit(0); process::exit(0);
} }
wizard::configure().await wizard::configure(path).await
} }
/// Read and parse the TOML configuration from default paths. /// Read and parse the TOML configuration from default paths.

View file

@ -61,27 +61,38 @@ pub enum CmdDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<Cmd>", from = "OptionCmd")] #[serde(remote = "Option<Cmd>", from = "OptionCmd", into = "OptionCmd")]
pub struct OptionCmdDef; pub struct OptionCmdDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionCmd {
pub enum OptionCmd { #[serde(default, skip)]
#[default] is_some: bool,
#[serde(skip_serializing)] #[serde(flatten, with = "CmdDef")]
None, inner: Cmd,
#[serde(with = "SingleCmdDef")]
SingleCmd(SingleCmd),
#[serde(with = "PipelineDef")]
Pipeline(Pipeline),
} }
impl From<OptionCmd> for Option<Cmd> { impl From<OptionCmd> for Option<Cmd> {
fn from(cmd: OptionCmd) -> Option<Cmd> { fn from(cmd: OptionCmd) -> Option<Cmd> {
match cmd { if cmd.is_some {
OptionCmd::None => None, Some(cmd.inner)
OptionCmd::SingleCmd(cmd) => Some(Cmd::SingleCmd(cmd)), } else {
OptionCmd::Pipeline(pipeline) => Some(Cmd::Pipeline(pipeline)), None
}
}
}
impl Into<OptionCmd> for Option<Cmd> {
fn into(self) -> OptionCmd {
match self {
Some(cmd) => OptionCmd {
is_some: true,
inner: cmd,
},
None => OptionCmd {
is_some: false,
inner: Default::default(),
},
} }
} }
} }
@ -108,7 +119,11 @@ pub enum OAuth2MethodDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<ImapConfig>", from = "OptionImapConfig")] #[serde(
remote = "Option<ImapConfig>",
from = "OptionImapConfig",
into = "OptionImapConfig"
)]
pub struct OptionImapConfigDef; pub struct OptionImapConfigDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
@ -129,6 +144,21 @@ impl From<OptionImapConfig> for Option<ImapConfig> {
} }
} }
impl Into<OptionImapConfig> for Option<ImapConfig> {
fn into(self) -> OptionImapConfig {
match self {
Some(config) => OptionImapConfig {
is_none: false,
inner: config,
},
None => OptionImapConfig {
is_none: true,
inner: Default::default(),
},
}
}
}
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "ImapConfig", rename_all = "kebab-case")] #[serde(remote = "ImapConfig", rename_all = "kebab-case")]
@ -226,23 +256,42 @@ pub enum ImapOAuth2ScopesDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<MaildirConfig>", from = "OptionMaildirConfig")] #[serde(
remote = "Option<MaildirConfig>",
from = "OptionMaildirConfig",
into = "OptionMaildirConfig"
)]
pub struct OptionMaildirConfigDef; pub struct OptionMaildirConfigDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionMaildirConfig {
pub enum OptionMaildirConfig { #[serde(default, skip)]
#[default] is_none: bool,
#[serde(skip_serializing)] #[serde(flatten, with = "MaildirConfigDef")]
None, inner: MaildirConfig,
Some(#[serde(with = "MaildirConfigDef")] MaildirConfig),
} }
impl From<OptionMaildirConfig> for Option<MaildirConfig> { impl From<OptionMaildirConfig> for Option<MaildirConfig> {
fn from(config: OptionMaildirConfig) -> Option<MaildirConfig> { fn from(config: OptionMaildirConfig) -> Option<MaildirConfig> {
match config { if config.is_none {
OptionMaildirConfig::None => None, None
OptionMaildirConfig::Some(config) => Some(config), } else {
Some(config.inner)
}
}
}
impl Into<OptionMaildirConfig> for Option<MaildirConfig> {
fn into(self) -> OptionMaildirConfig {
match self {
Some(config) => OptionMaildirConfig {
is_none: false,
inner: config,
},
None => OptionMaildirConfig {
is_none: true,
inner: Default::default(),
},
} }
} }
} }
@ -256,25 +305,45 @@ pub struct MaildirConfigDef {
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<NotmuchConfig>", from = "OptionNotmuchConfig")] #[serde(
remote = "Option<NotmuchConfig>",
from = "OptionNotmuchConfig",
into = "OptionNotmuchConfig"
)]
pub struct OptionNotmuchConfigDef; pub struct OptionNotmuchConfigDef;
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionNotmuchConfig {
pub enum OptionNotmuchConfig { #[serde(default, skip)]
#[default] is_none: bool,
#[serde(skip_serializing)] #[serde(flatten, with = "NotmuchConfigDef")]
None, inner: NotmuchConfig,
Some(#[serde(with = "NotmuchConfigDef")] NotmuchConfig),
} }
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
impl From<OptionNotmuchConfig> for Option<NotmuchConfig> { impl From<OptionNotmuchConfig> for Option<NotmuchConfig> {
fn from(config: OptionNotmuchConfig) -> Option<NotmuchConfig> { fn from(config: OptionNotmuchConfig) -> Option<NotmuchConfig> {
match config { if config.is_none {
OptionNotmuchConfig::None => None, None
OptionNotmuchConfig::Some(config) => Some(config), } else {
Some(config.inner)
}
}
}
#[cfg(feature = "notmuch-backend")]
impl Into<OptionNotmuchConfig> for Option<NotmuchConfig> {
fn into(self) -> OptionNotmuchConfig {
match self {
Some(config) => OptionNotmuchConfig {
is_none: false,
inner: config,
},
None => OptionNotmuchConfig {
is_none: true,
inner: Default::default(),
},
} }
} }
} }
@ -290,28 +359,40 @@ pub struct NotmuchConfigDef {
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde( #[serde(
remote = "Option<EmailTextPlainFormat>", remote = "Option<EmailTextPlainFormat>",
from = "OptionEmailTextPlainFormat" from = "OptionEmailTextPlainFormat",
into = "OptionEmailTextPlainFormat"
)] )]
pub struct OptionEmailTextPlainFormatDef; pub struct OptionEmailTextPlainFormatDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionEmailTextPlainFormat {
pub enum OptionEmailTextPlainFormat { #[serde(default, skip)]
#[serde(skip_serializing)] is_none: bool,
None, #[serde(flatten, with = "EmailTextPlainFormatDef")]
#[default] inner: EmailTextPlainFormat,
Auto,
Flowed,
Fixed(usize),
} }
impl From<OptionEmailTextPlainFormat> for Option<EmailTextPlainFormat> { impl From<OptionEmailTextPlainFormat> for Option<EmailTextPlainFormat> {
fn from(fmt: OptionEmailTextPlainFormat) -> Option<EmailTextPlainFormat> { fn from(fmt: OptionEmailTextPlainFormat) -> Option<EmailTextPlainFormat> {
match fmt { if fmt.is_none {
OptionEmailTextPlainFormat::None => None, None
OptionEmailTextPlainFormat::Auto => Some(EmailTextPlainFormat::Auto), } else {
OptionEmailTextPlainFormat::Flowed => Some(EmailTextPlainFormat::Flowed), Some(fmt.inner)
OptionEmailTextPlainFormat::Fixed(size) => Some(EmailTextPlainFormat::Fixed(size)), }
}
}
impl Into<OptionEmailTextPlainFormat> for Option<EmailTextPlainFormat> {
fn into(self) -> OptionEmailTextPlainFormat {
match self {
Some(config) => OptionEmailTextPlainFormat {
is_none: false,
inner: config,
},
None => OptionEmailTextPlainFormat {
is_none: true,
inner: Default::default(),
},
} }
} }
} }
@ -331,7 +412,11 @@ pub enum EmailTextPlainFormatDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<SmtpConfig>", from = "OptionSmtpConfig")] #[serde(
remote = "Option<SmtpConfig>",
from = "OptionSmtpConfig",
into = "OptionSmtpConfig"
)]
pub struct OptionSmtpConfigDef; pub struct OptionSmtpConfigDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
@ -352,6 +437,21 @@ impl From<OptionSmtpConfig> for Option<SmtpConfig> {
} }
} }
impl Into<OptionSmtpConfig> for Option<SmtpConfig> {
fn into(self) -> OptionSmtpConfig {
match self {
Some(config) => OptionSmtpConfig {
is_none: false,
inner: config,
},
None => OptionSmtpConfig {
is_none: true,
inner: Default::default(),
},
}
}
}
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "SmtpConfig")] #[serde(remote = "SmtpConfig")]
@ -434,23 +534,42 @@ pub enum SmtpOAuth2ScopesDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<SendmailConfig>", from = "OptionSendmailConfig")] #[serde(
remote = "Option<SendmailConfig>",
from = "OptionSendmailConfig",
into = "OptionSendmailConfig"
)]
pub struct OptionSendmailConfigDef; pub struct OptionSendmailConfigDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionSendmailConfig {
pub enum OptionSendmailConfig { #[serde(default, skip)]
#[default] is_none: bool,
#[serde(skip_serializing)] #[serde(flatten, with = "SendmailConfigDef")]
None, inner: SendmailConfig,
Some(#[serde(with = "SendmailConfigDef")] SendmailConfig),
} }
impl From<OptionSendmailConfig> for Option<SendmailConfig> { impl From<OptionSendmailConfig> for Option<SendmailConfig> {
fn from(config: OptionSendmailConfig) -> Option<SendmailConfig> { fn from(config: OptionSendmailConfig) -> Option<SendmailConfig> {
match config { if config.is_none {
OptionSendmailConfig::None => None, None
OptionSendmailConfig::Some(config) => Some(config), } else {
Some(config.inner)
}
}
}
impl Into<OptionSendmailConfig> for Option<SendmailConfig> {
fn into(self) -> OptionSendmailConfig {
match self {
Some(config) => OptionSendmailConfig {
is_none: false,
inner: config,
},
None => OptionSendmailConfig {
is_none: true,
inner: Default::default(),
},
} }
} }
} }
@ -467,23 +586,46 @@ fn sendmail_default_cmd() -> Cmd {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<EmailHooks>", from = "OptionEmailHooks")] #[serde(
remote = "Option<EmailHooks>",
from = "OptionEmailHooks",
into = "OptionEmailHooks"
)]
pub struct OptionEmailHooksDef; pub struct OptionEmailHooksDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionEmailHooks {
pub enum OptionEmailHooks { #[serde(default, skip)]
#[default] is_none: bool,
#[serde(skip_serializing)] #[serde(
None, flatten,
Some(#[serde(with = "EmailHooksDef")] EmailHooks), skip_serializing_if = "EmailHooks::is_empty",
with = "EmailHooksDef"
)]
inner: EmailHooks,
} }
impl From<OptionEmailHooks> for Option<EmailHooks> { impl From<OptionEmailHooks> for Option<EmailHooks> {
fn from(fmt: OptionEmailHooks) -> Option<EmailHooks> { fn from(hooks: OptionEmailHooks) -> Option<EmailHooks> {
match fmt { if hooks.is_none {
OptionEmailHooks::None => None, None
OptionEmailHooks::Some(hooks) => Some(hooks), } else {
Some(hooks.inner)
}
}
}
impl Into<OptionEmailHooks> for Option<EmailHooks> {
fn into(self) -> OptionEmailHooks {
match self {
Some(hooks) => OptionEmailHooks {
is_none: false,
inner: hooks,
},
None => OptionEmailHooks {
is_none: true,
inner: Default::default(),
},
} }
} }
} }
@ -501,24 +643,44 @@ pub struct EmailHooksDef {
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde( #[serde(
remote = "Option<FolderSyncStrategy>", remote = "Option<FolderSyncStrategy>",
from = "OptionFolderSyncStrategy" from = "OptionFolderSyncStrategy",
into = "OptionFolderSyncStrategy"
)] )]
pub struct OptionFolderSyncStrategyDef; pub struct OptionFolderSyncStrategyDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)] pub struct OptionFolderSyncStrategy {
pub enum OptionFolderSyncStrategy { #[serde(default, skip)]
#[default] is_some: bool,
#[serde(skip_serializing)] #[serde(
None, flatten,
Some(#[serde(with = "FolderSyncStrategyDef")] FolderSyncStrategy), skip_serializing_if = "FolderSyncStrategy::is_default",
with = "FolderSyncStrategyDef"
)]
inner: FolderSyncStrategy,
} }
impl From<OptionFolderSyncStrategy> for Option<FolderSyncStrategy> { impl From<OptionFolderSyncStrategy> for Option<FolderSyncStrategy> {
fn from(config: OptionFolderSyncStrategy) -> Option<FolderSyncStrategy> { fn from(option: OptionFolderSyncStrategy) -> Option<FolderSyncStrategy> {
match config { if option.is_some {
OptionFolderSyncStrategy::None => None, Some(option.inner)
OptionFolderSyncStrategy::Some(config) => Some(config), } else {
None
}
}
}
impl Into<OptionFolderSyncStrategy> for Option<FolderSyncStrategy> {
fn into(self) -> OptionFolderSyncStrategy {
match self {
Some(strategy) => OptionFolderSyncStrategy {
is_some: true,
inner: strategy,
},
None => OptionFolderSyncStrategy {
is_some: false,
inner: Default::default(),
},
} }
} }
} }

View file

@ -1,10 +1,13 @@
use super::TomlConfig;
use crate::account;
use anyhow::Result; use anyhow::Result;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select}; use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use shellexpand_utils::{shellexpand_path, try_shellexpand_path}; use shellexpand_utils::shellexpand_path;
use std::{env, fs, io, process}; use std::{fs, io, path::PathBuf, process};
use toml_edit::{Document, Item};
use crate::account;
use super::TomlConfig;
#[macro_export] #[macro_export]
macro_rules! wizard_warn { macro_rules! wizard_warn {
@ -31,7 +34,7 @@ macro_rules! wizard_log {
pub(crate) static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default); pub(crate) static THEME: Lazy<ColorfulTheme> = Lazy::new(ColorfulTheme::default);
pub(crate) async fn configure() -> Result<TomlConfig> { pub(crate) async fn configure(path: PathBuf) -> Result<TomlConfig> {
wizard_log!("Configuring your first account:"); wizard_log!("Configuring your first account:");
let mut config = TomlConfig::default(); let mut config = TomlConfig::default();
@ -89,25 +92,86 @@ pub(crate) async fn configure() -> Result<TomlConfig> {
.with_prompt(wizard_prompt!( .with_prompt(wizard_prompt!(
"Where would you like to save your configuration?" "Where would you like to save your configuration?"
)) ))
.default( .default(path.to_string_lossy().to_string())
dirs::config_dir()
.map(|p| p.join("himalaya").join("config.toml"))
.unwrap_or_else(|| env::temp_dir().join("himalaya").join("config.toml"))
.to_string_lossy()
.to_string(),
)
.validate_with(|path: &String| try_shellexpand_path(path).map(|_| ()))
.interact()?; .interact()?;
let path = shellexpand_path(&path); let path = shellexpand_path(&path);
println!("Writing the configuration to {path:?}"); println!("Writing the configuration to {path:?}");
let mut doc = toml::to_string(&config)?.parse::<Document>()?;
doc.iter_mut().for_each(|(_, item)| {
set_table_dotted(item, "folder-aliases");
set_table_dotted(item, "sync-folders-strategy");
set_table_dotted(item, "folder");
get_table_mut(item, "folder").map(|item| {
set_tables_dotted(item, ["add", "list", "expunge", "purge", "delete"]);
});
set_table_dotted(item, "envelope");
get_table_mut(item, "envelope").map(|item| {
set_tables_dotted(item, ["list", "get"]);
});
set_table_dotted(item, "flag");
get_table_mut(item, "flag").map(|item| {
set_tables_dotted(item, ["add", "set", "remove"]);
});
set_table_dotted(item, "message");
get_table_mut(item, "message").map(|item| {
set_tables_dotted(
item,
["add", "send", "peek", "get", "copy", "move", "delete"],
);
});
set_table_dotted(item, "maildir");
#[cfg(feature = "imap-backend")]
{
set_table_dotted(item, "imap");
get_table_mut(item, "imap").map(|item| {
set_tables_dotted(item, ["passwd", "oauth2"]);
});
}
#[cfg(feature = "notmuch-backend")]
set_table_dotted(item, "notmuch");
set_table_dotted(item, "sendmail");
#[cfg(feature = "smtp-sender")]
{
set_table_dotted(item, "smtp");
get_table_mut(item, "smtp").map(|item| {
set_tables_dotted(item, ["passwd", "oauth2"]);
});
}
#[cfg(feature = "pgp")]
set_table_dotted(item, "pgp");
});
fs::create_dir_all(path.parent().unwrap_or(&path))?; fs::create_dir_all(path.parent().unwrap_or(&path))?;
fs::write(path, toml::to_string(&config)?)?; fs::write(path, doc.to_string())?;
Ok(config) Ok(config)
} }
fn get_table_mut<'a>(item: &'a mut Item, key: &'a str) -> Option<&'a mut Item> {
item.get_mut(key).filter(|item| item.is_table())
}
fn set_table_dotted(item: &mut Item, key: &str) {
get_table_mut(item, key)
.and_then(|item| item.as_table_mut())
.map(|table| table.set_dotted(true));
}
fn set_tables_dotted<'a>(item: &'a mut Item, keys: impl IntoIterator<Item = &'a str>) {
for key in keys {
set_table_dotted(item, key)
}
}
pub(crate) fn prompt_passwd(prompt: &str) -> io::Result<String> { pub(crate) fn prompt_passwd(prompt: &str) -> io::Result<String> {
Password::with_theme(&*THEME) Password::with_theme(&*THEME)
.with_prompt(prompt) .with_prompt(prompt)

View file

@ -31,7 +31,7 @@ use crate::{
/// 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(tag = "backend", rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct TomlAccountConfig { pub struct TomlAccountConfig {
pub default: Option<bool>, pub default: Option<bool>,
@ -48,16 +48,28 @@ pub struct TomlAccountConfig {
pub email_listing_datetime_fmt: Option<String>, pub email_listing_datetime_fmt: Option<String>,
pub email_listing_datetime_local_tz: Option<bool>, pub email_listing_datetime_local_tz: Option<bool>,
pub email_reading_headers: Option<Vec<String>>, pub email_reading_headers: Option<Vec<String>>,
#[serde(default, with = "OptionEmailTextPlainFormatDef")] #[serde(
default,
with = "OptionEmailTextPlainFormatDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_format: Option<EmailTextPlainFormat>, pub email_reading_format: Option<EmailTextPlainFormat>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
pub email_sending_save_copy: Option<bool>, pub email_sending_save_copy: Option<bool>,
#[serde(default, with = "OptionEmailHooksDef")] #[serde(
default,
with = "OptionEmailHooksDef",
skip_serializing_if = "Option::is_none"
)]
pub email_hooks: Option<EmailHooks>, pub email_hooks: Option<EmailHooks>,
pub sync: Option<bool>, pub sync: Option<bool>,
pub sync_dir: Option<PathBuf>, pub sync_dir: Option<PathBuf>,
#[serde(default, with = "OptionFolderSyncStrategyDef")] #[serde(
default,
with = "OptionFolderSyncStrategyDef",
skip_serializing_if = "Option::is_none"
)]
pub sync_folders_strategy: Option<FolderSyncStrategy>, pub sync_folders_strategy: Option<FolderSyncStrategy>,
pub backend: Option<BackendKind>, pub backend: Option<BackendKind>,
@ -68,25 +80,49 @@ pub struct TomlAccountConfig {
pub message: Option<MessageConfig>, pub message: Option<MessageConfig>,
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
#[serde(default, with = "OptionImapConfigDef")] #[serde(
default,
with = "OptionImapConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub imap: Option<ImapConfig>, pub imap: Option<ImapConfig>,
#[serde(default, with = "OptionMaildirConfigDef")] #[serde(
default,
with = "OptionMaildirConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub maildir: Option<MaildirConfig>, pub maildir: Option<MaildirConfig>,
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
#[serde(default, with = "OptionNotmuchConfigDef")] #[serde(
default,
with = "OptionNotmuchConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub notmuch: Option<NotmuchConfig>, pub notmuch: Option<NotmuchConfig>,
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
#[serde(default, with = "OptionSmtpConfigDef")] #[serde(
default,
with = "OptionSmtpConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub smtp: Option<SmtpConfig>, pub smtp: Option<SmtpConfig>,
#[serde(default, with = "OptionSendmailConfigDef")] #[serde(
default,
with = "OptionSendmailConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub sendmail: Option<SendmailConfig>, pub sendmail: Option<SendmailConfig>,
#[cfg(feature = "pgp")] #[cfg(feature = "pgp")]
#[serde(default, with = "OptionPgpConfigDef")] #[serde(
default,
with = "OptionPgpConfigDef",
skip_serializing_if = "Option::is_none"
)]
pub pgp: Option<PgpConfig>, pub pgp: Option<PgpConfig>,
} }

View file

@ -2,7 +2,7 @@
//! //!
//! This module gathers all account actions triggered by the CLI. //! This module gathers all account actions triggered by the CLI.
use anyhow::{Context, Result}; use anyhow::Result;
use email::account::{ use email::account::{
sync::{AccountSyncBuilder, AccountSyncProgressEvent}, sync::{AccountSyncBuilder, AccountSyncProgressEvent},
AccountConfig, AccountConfig,
@ -12,7 +12,7 @@ use email::imap::ImapAuthConfig;
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
use email::smtp::SmtpAuthConfig; use email::smtp::SmtpAuthConfig;
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
use log::{info, trace, warn}; use log::{debug, info, trace, warn};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::{collections::HashMap, sync::Mutex}; use std::{collections::HashMap, sync::Mutex};
@ -26,6 +26,8 @@ use crate::{
Accounts, Accounts,
}; };
use super::TomlAccountConfig;
const MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| { const MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap() ProgressStyle::with_template(" {spinner:.dim} {msg:.dim}\n {wide_bar:.cyan/blue} \n").unwrap()
}); });
@ -42,71 +44,75 @@ const SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
}); });
/// Configure the current selected account /// Configure the current selected account
pub async fn configure(config: &AccountConfig, reset: bool) -> Result<()> { pub async fn configure(config: &TomlAccountConfig, reset: bool) -> Result<()> {
info!("entering the configure account handler"); info!("entering the configure account handler");
// if reset { if reset {
// #[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
// if let BackendConfig::Imap(imap_config) = &config.backend { if let Some(ref config) = config.imap {
// let reset = match &imap_config.auth { let reset = match &config.auth {
// ImapAuthConfig::Passwd(passwd) => passwd.reset(), ImapAuthConfig::Passwd(config) => config.reset(),
// ImapAuthConfig::OAuth2(oauth2) => oauth2.reset(), ImapAuthConfig::OAuth2(config) => config.reset(),
// }; };
// if let Err(err) = reset { if let Err(err) = reset {
// warn!("error while resetting imap secrets, skipping it"); warn!("error while resetting imap secrets: {err}");
// warn!("{err}"); debug!("error while resetting imap secrets: {err:?}");
// } }
// } }
// #[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
// if let SenderConfig::Smtp(smtp_config) = &config.sender { if let Some(ref config) = config.smtp {
// let reset = match &smtp_config.auth { let reset = match &config.auth {
// SmtpAuthConfig::Passwd(passwd) => passwd.reset(), SmtpAuthConfig::Passwd(config) => config.reset(),
// SmtpAuthConfig::OAuth2(oauth2) => oauth2.reset(), SmtpAuthConfig::OAuth2(config) => config.reset(),
// }; };
// if let Err(err) = reset { if let Err(err) = reset {
// warn!("error while resetting smtp secrets, skipping it"); warn!("error while resetting smtp secrets: {err}");
// warn!("{err}"); debug!("error while resetting smtp secrets: {err:?}");
// } }
// } }
// #[cfg(feature = "pgp")] #[cfg(feature = "pgp")]
// config.pgp.reset().await?; if let Some(ref config) = config.pgp {
// } config.pgp.reset().await?;
}
}
// #[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
// if let BackendConfig::Imap(imap_config) = &config.backend { if let Some(ref config) = config.imap {
// match &imap_config.auth { match &config.auth {
// ImapAuthConfig::Passwd(passwd) => { ImapAuthConfig::Passwd(config) => {
// passwd.configure(|| prompt_passwd("IMAP password")).await config.configure(|| prompt_passwd("IMAP password")).await
// } }
// ImapAuthConfig::OAuth2(oauth2) => { ImapAuthConfig::OAuth2(config) => {
// oauth2 config
// .configure(|| prompt_secret("IMAP OAuth 2.0 client secret")) .configure(|| prompt_secret("IMAP OAuth 2.0 client secret"))
// .await .await
// } }
// }?; }?;
// } }
// #[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
// if let SenderConfig::Smtp(smtp_config) = &config.sender { if let Some(ref config) = config.smtp {
// match &smtp_config.auth { match &config.auth {
// SmtpAuthConfig::Passwd(passwd) => { SmtpAuthConfig::Passwd(config) => {
// passwd.configure(|| prompt_passwd("SMTP password")).await config.configure(|| prompt_passwd("SMTP password")).await
// } }
// SmtpAuthConfig::OAuth2(oauth2) => { SmtpAuthConfig::OAuth2(config) => {
// oauth2 config
// .configure(|| prompt_secret("SMTP OAuth 2.0 client secret")) .configure(|| prompt_secret("SMTP OAuth 2.0 client secret"))
// .await .await
// } }
// }?; }?;
// } }
// #[cfg(feature = "pgp")] #[cfg(feature = "pgp")]
// config if let Some(ref config) = config.pgp {
// .pgp config
// .configure(&config.email, || prompt_passwd("PGP secret key password")) .pgp
// .await?; .configure(&config.email, || prompt_passwd("PGP secret key password"))
.await?;
}
println!( println!(
"Account successfully {}configured!", "Account successfully {}configured!",

View file

@ -10,7 +10,7 @@ pub struct MessageConfig {
pub peek: Option<MessagePeekConfig>, pub peek: Option<MessagePeekConfig>,
pub get: Option<MessageGetConfig>, pub get: Option<MessageGetConfig>,
pub copy: Option<MessageCopyConfig>, pub copy: Option<MessageCopyConfig>,
#[serde(rename = "move")] #[serde(default, rename = "move", skip_serializing_if = "Option::is_none")]
pub move_: Option<MessageMoveConfig>, pub move_: Option<MessageMoveConfig>,
} }

View file

@ -105,7 +105,7 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
ImapAuthConfig::Passwd(config) ImapAuthConfig::Passwd(config)
} }
Some(idx) if AUTH_MECHANISMS[idx] == OAUTH2 => { Some(idx) if AUTH_MECHANISMS[idx] == OAUTH2 => {
let mut config = OAuth2Config::default(); let mut config = OAuth2Config::new()?;
let method = Select::with_theme(&*THEME) let method = Select::with_theme(&*THEME)
.with_prompt("IMAP OAuth 2.0 mechanism") .with_prompt("IMAP OAuth 2.0 mechanism")

View file

@ -131,10 +131,10 @@ async fn main() -> Result<()> {
return account::handlers::sync(&mut printer, sync_builder, dry_run).await; return account::handlers::sync(&mut printer, sync_builder, dry_run).await;
} }
Some(account::args::Cmd::Configure(reset)) => { Some(account::args::Cmd::Configure(reset)) => {
let (_, account_config) = toml_config let (toml_account_config, _) = toml_config
.clone() .clone()
.into_account_configs(some_account_name, disable_cache)?; .into_account_configs(some_account_name, disable_cache)?;
return account::handlers::configure(&account_config, reset).await; return account::handlers::configure(&toml_account_config, reset).await;
} }
_ => (), _ => (),
} }