mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 12:30:22 +00:00
fix config and oauth2
This commit is contained in:
parent
c54ada730b
commit
ea9c28b9d7
9 changed files with 453 additions and 177 deletions
|
@ -12,17 +12,17 @@ use crate::{config::wizard::THEME, maildir, sendmail};
|
|||
use super::{config::BackendConfig, BackendKind};
|
||||
|
||||
const DEFAULT_BACKEND_KINDS: &[BackendKind] = &[
|
||||
BackendKind::Maildir,
|
||||
#[cfg(feature = "imap-backend")]
|
||||
BackendKind::Imap,
|
||||
BackendKind::Maildir,
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
BackendKind::Notmuch,
|
||||
];
|
||||
|
||||
const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
||||
BackendKind::Sendmail,
|
||||
#[cfg(feature = "smtp-sender")]
|
||||
BackendKind::Smtp,
|
||||
BackendKind::Sendmail,
|
||||
];
|
||||
|
||||
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,
|
||||
) -> Result<Option<BackendConfig>> {
|
||||
let kind = Select::with_theme(&*THEME)
|
||||
.with_prompt("Default email backend")
|
||||
.with_prompt("Backend for sending messages")
|
||||
.items(SEND_MESSAGE_BACKEND_KINDS)
|
||||
.default(0)
|
||||
.interact_opt()?
|
||||
|
|
|
@ -44,11 +44,19 @@ pub struct TomlConfig {
|
|||
pub email_listing_datetime_fmt: Option<String>,
|
||||
pub email_listing_datetime_local_tz: Option<bool>,
|
||||
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_writing_headers: Option<Vec<String>>,
|
||||
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>,
|
||||
|
||||
#[serde(flatten)]
|
||||
|
@ -78,7 +86,7 @@ impl TomlConfig {
|
|||
|
||||
let confirm = Confirm::new()
|
||||
.with_prompt(wizard_prompt!(
|
||||
"Would you like to create it with the wizard?"
|
||||
"Would you like to create one with the wizard?"
|
||||
))
|
||||
.default(true)
|
||||
.interact_opt()?
|
||||
|
@ -88,7 +96,7 @@ impl TomlConfig {
|
|||
process::exit(0);
|
||||
}
|
||||
|
||||
wizard::configure().await
|
||||
wizard::configure(path).await
|
||||
}
|
||||
|
||||
/// Read and parse the TOML configuration from default paths.
|
||||
|
|
|
@ -61,27 +61,38 @@ pub enum CmdDef {
|
|||
}
|
||||
|
||||
#[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;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionCmd {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
#[serde(with = "SingleCmdDef")]
|
||||
SingleCmd(SingleCmd),
|
||||
#[serde(with = "PipelineDef")]
|
||||
Pipeline(Pipeline),
|
||||
pub struct OptionCmd {
|
||||
#[serde(default, skip)]
|
||||
is_some: bool,
|
||||
#[serde(flatten, with = "CmdDef")]
|
||||
inner: Cmd,
|
||||
}
|
||||
|
||||
impl From<OptionCmd> for Option<Cmd> {
|
||||
fn from(cmd: OptionCmd) -> Option<Cmd> {
|
||||
match cmd {
|
||||
OptionCmd::None => None,
|
||||
OptionCmd::SingleCmd(cmd) => Some(Cmd::SingleCmd(cmd)),
|
||||
OptionCmd::Pipeline(pipeline) => Some(Cmd::Pipeline(pipeline)),
|
||||
if cmd.is_some {
|
||||
Some(cmd.inner)
|
||||
} else {
|
||||
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)]
|
||||
#[serde(remote = "Option<ImapConfig>", from = "OptionImapConfig")]
|
||||
#[serde(
|
||||
remote = "Option<ImapConfig>",
|
||||
from = "OptionImapConfig",
|
||||
into = "OptionImapConfig"
|
||||
)]
|
||||
pub struct OptionImapConfigDef;
|
||||
|
||||
#[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")]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(remote = "ImapConfig", rename_all = "kebab-case")]
|
||||
|
@ -226,23 +256,42 @@ pub enum ImapOAuth2ScopesDef {
|
|||
}
|
||||
|
||||
#[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;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionMaildirConfig {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
Some(#[serde(with = "MaildirConfigDef")] MaildirConfig),
|
||||
pub struct OptionMaildirConfig {
|
||||
#[serde(default, skip)]
|
||||
is_none: bool,
|
||||
#[serde(flatten, with = "MaildirConfigDef")]
|
||||
inner: MaildirConfig,
|
||||
}
|
||||
|
||||
impl From<OptionMaildirConfig> for Option<MaildirConfig> {
|
||||
fn from(config: OptionMaildirConfig) -> Option<MaildirConfig> {
|
||||
match config {
|
||||
OptionMaildirConfig::None => None,
|
||||
OptionMaildirConfig::Some(config) => Some(config),
|
||||
if config.is_none {
|
||||
None
|
||||
} 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")]
|
||||
#[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;
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionNotmuchConfig {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
Some(#[serde(with = "NotmuchConfigDef")] NotmuchConfig),
|
||||
pub struct OptionNotmuchConfig {
|
||||
#[serde(default, skip)]
|
||||
is_none: bool,
|
||||
#[serde(flatten, with = "NotmuchConfigDef")]
|
||||
inner: NotmuchConfig,
|
||||
}
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
impl From<OptionNotmuchConfig> for Option<NotmuchConfig> {
|
||||
fn from(config: OptionNotmuchConfig) -> Option<NotmuchConfig> {
|
||||
match config {
|
||||
OptionNotmuchConfig::None => None,
|
||||
OptionNotmuchConfig::Some(config) => Some(config),
|
||||
if config.is_none {
|
||||
None
|
||||
} 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)]
|
||||
#[serde(
|
||||
remote = "Option<EmailTextPlainFormat>",
|
||||
from = "OptionEmailTextPlainFormat"
|
||||
from = "OptionEmailTextPlainFormat",
|
||||
into = "OptionEmailTextPlainFormat"
|
||||
)]
|
||||
pub struct OptionEmailTextPlainFormatDef;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionEmailTextPlainFormat {
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
#[default]
|
||||
Auto,
|
||||
Flowed,
|
||||
Fixed(usize),
|
||||
pub struct OptionEmailTextPlainFormat {
|
||||
#[serde(default, skip)]
|
||||
is_none: bool,
|
||||
#[serde(flatten, with = "EmailTextPlainFormatDef")]
|
||||
inner: EmailTextPlainFormat,
|
||||
}
|
||||
|
||||
impl From<OptionEmailTextPlainFormat> for Option<EmailTextPlainFormat> {
|
||||
fn from(fmt: OptionEmailTextPlainFormat) -> Option<EmailTextPlainFormat> {
|
||||
match fmt {
|
||||
OptionEmailTextPlainFormat::None => None,
|
||||
OptionEmailTextPlainFormat::Auto => Some(EmailTextPlainFormat::Auto),
|
||||
OptionEmailTextPlainFormat::Flowed => Some(EmailTextPlainFormat::Flowed),
|
||||
OptionEmailTextPlainFormat::Fixed(size) => Some(EmailTextPlainFormat::Fixed(size)),
|
||||
if fmt.is_none {
|
||||
None
|
||||
} else {
|
||||
Some(fmt.inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
#[serde(remote = "Option<SmtpConfig>", from = "OptionSmtpConfig")]
|
||||
#[serde(
|
||||
remote = "Option<SmtpConfig>",
|
||||
from = "OptionSmtpConfig",
|
||||
into = "OptionSmtpConfig"
|
||||
)]
|
||||
pub struct OptionSmtpConfigDef;
|
||||
|
||||
#[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")]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(remote = "SmtpConfig")]
|
||||
|
@ -434,23 +534,42 @@ pub enum SmtpOAuth2ScopesDef {
|
|||
}
|
||||
|
||||
#[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;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionSendmailConfig {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
Some(#[serde(with = "SendmailConfigDef")] SendmailConfig),
|
||||
pub struct OptionSendmailConfig {
|
||||
#[serde(default, skip)]
|
||||
is_none: bool,
|
||||
#[serde(flatten, with = "SendmailConfigDef")]
|
||||
inner: SendmailConfig,
|
||||
}
|
||||
|
||||
impl From<OptionSendmailConfig> for Option<SendmailConfig> {
|
||||
fn from(config: OptionSendmailConfig) -> Option<SendmailConfig> {
|
||||
match config {
|
||||
OptionSendmailConfig::None => None,
|
||||
OptionSendmailConfig::Some(config) => Some(config),
|
||||
if config.is_none {
|
||||
None
|
||||
} 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)]
|
||||
#[serde(remote = "Option<EmailHooks>", from = "OptionEmailHooks")]
|
||||
#[serde(
|
||||
remote = "Option<EmailHooks>",
|
||||
from = "OptionEmailHooks",
|
||||
into = "OptionEmailHooks"
|
||||
)]
|
||||
pub struct OptionEmailHooksDef;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionEmailHooks {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
Some(#[serde(with = "EmailHooksDef")] EmailHooks),
|
||||
pub struct OptionEmailHooks {
|
||||
#[serde(default, skip)]
|
||||
is_none: bool,
|
||||
#[serde(
|
||||
flatten,
|
||||
skip_serializing_if = "EmailHooks::is_empty",
|
||||
with = "EmailHooksDef"
|
||||
)]
|
||||
inner: EmailHooks,
|
||||
}
|
||||
|
||||
impl From<OptionEmailHooks> for Option<EmailHooks> {
|
||||
fn from(fmt: OptionEmailHooks) -> Option<EmailHooks> {
|
||||
match fmt {
|
||||
OptionEmailHooks::None => None,
|
||||
OptionEmailHooks::Some(hooks) => Some(hooks),
|
||||
fn from(hooks: OptionEmailHooks) -> Option<EmailHooks> {
|
||||
if hooks.is_none {
|
||||
None
|
||||
} 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)]
|
||||
#[serde(
|
||||
remote = "Option<FolderSyncStrategy>",
|
||||
from = "OptionFolderSyncStrategy"
|
||||
from = "OptionFolderSyncStrategy",
|
||||
into = "OptionFolderSyncStrategy"
|
||||
)]
|
||||
pub struct OptionFolderSyncStrategyDef;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OptionFolderSyncStrategy {
|
||||
#[default]
|
||||
#[serde(skip_serializing)]
|
||||
None,
|
||||
Some(#[serde(with = "FolderSyncStrategyDef")] FolderSyncStrategy),
|
||||
pub struct OptionFolderSyncStrategy {
|
||||
#[serde(default, skip)]
|
||||
is_some: bool,
|
||||
#[serde(
|
||||
flatten,
|
||||
skip_serializing_if = "FolderSyncStrategy::is_default",
|
||||
with = "FolderSyncStrategyDef"
|
||||
)]
|
||||
inner: FolderSyncStrategy,
|
||||
}
|
||||
|
||||
impl From<OptionFolderSyncStrategy> for Option<FolderSyncStrategy> {
|
||||
fn from(config: OptionFolderSyncStrategy) -> Option<FolderSyncStrategy> {
|
||||
match config {
|
||||
OptionFolderSyncStrategy::None => None,
|
||||
OptionFolderSyncStrategy::Some(config) => Some(config),
|
||||
fn from(option: OptionFolderSyncStrategy) -> Option<FolderSyncStrategy> {
|
||||
if option.is_some {
|
||||
Some(option.inner)
|
||||
} 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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use super::TomlConfig;
|
||||
use crate::account;
|
||||
use anyhow::Result;
|
||||
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select};
|
||||
use once_cell::sync::Lazy;
|
||||
use shellexpand_utils::{shellexpand_path, try_shellexpand_path};
|
||||
use std::{env, fs, io, process};
|
||||
use shellexpand_utils::shellexpand_path;
|
||||
use std::{fs, io, path::PathBuf, process};
|
||||
use toml_edit::{Document, Item};
|
||||
|
||||
use crate::account;
|
||||
|
||||
use super::TomlConfig;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! wizard_warn {
|
||||
|
@ -31,7 +34,7 @@ macro_rules! wizard_log {
|
|||
|
||||
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:");
|
||||
|
||||
let mut config = TomlConfig::default();
|
||||
|
@ -89,25 +92,86 @@ pub(crate) async fn configure() -> Result<TomlConfig> {
|
|||
.with_prompt(wizard_prompt!(
|
||||
"Where would you like to save your configuration?"
|
||||
))
|
||||
.default(
|
||||
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(|_| ()))
|
||||
.default(path.to_string_lossy().to_string())
|
||||
.interact()?;
|
||||
let path = shellexpand_path(&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::write(path, toml::to_string(&config)?)?;
|
||||
fs::write(path, doc.to_string())?;
|
||||
|
||||
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> {
|
||||
Password::with_theme(&*THEME)
|
||||
.with_prompt(prompt)
|
||||
|
|
|
@ -31,7 +31,7 @@ use crate::{
|
|||
|
||||
/// Represents all existing kind of account config.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(tag = "backend", rename_all = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct TomlAccountConfig {
|
||||
pub default: Option<bool>,
|
||||
|
||||
|
@ -48,16 +48,28 @@ pub struct TomlAccountConfig {
|
|||
pub email_listing_datetime_fmt: Option<String>,
|
||||
pub email_listing_datetime_local_tz: Option<bool>,
|
||||
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_writing_headers: Option<Vec<String>>,
|
||||
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 sync: Option<bool>,
|
||||
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 backend: Option<BackendKind>,
|
||||
|
@ -68,25 +80,49 @@ pub struct TomlAccountConfig {
|
|||
pub message: Option<MessageConfig>,
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
#[serde(default, with = "OptionImapConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionImapConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub imap: Option<ImapConfig>,
|
||||
|
||||
#[serde(default, with = "OptionMaildirConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionMaildirConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub maildir: Option<MaildirConfig>,
|
||||
|
||||
#[cfg(feature = "notmuch-backend")]
|
||||
#[serde(default, with = "OptionNotmuchConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionNotmuchConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub notmuch: Option<NotmuchConfig>,
|
||||
|
||||
#[cfg(feature = "smtp-sender")]
|
||||
#[serde(default, with = "OptionSmtpConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionSmtpConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
|
||||
#[serde(default, with = "OptionSendmailConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionSendmailConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub sendmail: Option<SendmailConfig>,
|
||||
|
||||
#[cfg(feature = "pgp")]
|
||||
#[serde(default, with = "OptionPgpConfigDef")]
|
||||
#[serde(
|
||||
default,
|
||||
with = "OptionPgpConfigDef",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub pgp: Option<PgpConfig>,
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! This module gathers all account actions triggered by the CLI.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use email::account::{
|
||||
sync::{AccountSyncBuilder, AccountSyncProgressEvent},
|
||||
AccountConfig,
|
||||
|
@ -12,7 +12,7 @@ use email::imap::ImapAuthConfig;
|
|||
#[cfg(feature = "smtp-sender")]
|
||||
use email::smtp::SmtpAuthConfig;
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
|
||||
use log::{info, trace, warn};
|
||||
use log::{debug, info, trace, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
|
||||
|
@ -26,6 +26,8 @@ use crate::{
|
|||
Accounts,
|
||||
};
|
||||
|
||||
use super::TomlAccountConfig;
|
||||
|
||||
const MAIN_PROGRESS_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
|
||||
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
|
||||
pub async fn configure(config: &AccountConfig, reset: bool) -> Result<()> {
|
||||
pub async fn configure(config: &TomlAccountConfig, reset: bool) -> Result<()> {
|
||||
info!("entering the configure account handler");
|
||||
|
||||
// if reset {
|
||||
// #[cfg(feature = "imap-backend")]
|
||||
// if let BackendConfig::Imap(imap_config) = &config.backend {
|
||||
// let reset = match &imap_config.auth {
|
||||
// ImapAuthConfig::Passwd(passwd) => passwd.reset(),
|
||||
// ImapAuthConfig::OAuth2(oauth2) => oauth2.reset(),
|
||||
// };
|
||||
// if let Err(err) = reset {
|
||||
// warn!("error while resetting imap secrets, skipping it");
|
||||
// warn!("{err}");
|
||||
// }
|
||||
// }
|
||||
if reset {
|
||||
#[cfg(feature = "imap-backend")]
|
||||
if let Some(ref config) = config.imap {
|
||||
let reset = match &config.auth {
|
||||
ImapAuthConfig::Passwd(config) => config.reset(),
|
||||
ImapAuthConfig::OAuth2(config) => config.reset(),
|
||||
};
|
||||
if let Err(err) = reset {
|
||||
warn!("error while resetting imap secrets: {err}");
|
||||
debug!("error while resetting imap secrets: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "smtp-sender")]
|
||||
// if let SenderConfig::Smtp(smtp_config) = &config.sender {
|
||||
// let reset = match &smtp_config.auth {
|
||||
// SmtpAuthConfig::Passwd(passwd) => passwd.reset(),
|
||||
// SmtpAuthConfig::OAuth2(oauth2) => oauth2.reset(),
|
||||
// };
|
||||
// if let Err(err) = reset {
|
||||
// warn!("error while resetting smtp secrets, skipping it");
|
||||
// warn!("{err}");
|
||||
// }
|
||||
// }
|
||||
#[cfg(feature = "smtp-sender")]
|
||||
if let Some(ref config) = config.smtp {
|
||||
let reset = match &config.auth {
|
||||
SmtpAuthConfig::Passwd(config) => config.reset(),
|
||||
SmtpAuthConfig::OAuth2(config) => config.reset(),
|
||||
};
|
||||
if let Err(err) = reset {
|
||||
warn!("error while resetting smtp secrets: {err}");
|
||||
debug!("error while resetting smtp secrets: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "pgp")]
|
||||
// config.pgp.reset().await?;
|
||||
// }
|
||||
#[cfg(feature = "pgp")]
|
||||
if let Some(ref config) = config.pgp {
|
||||
config.pgp.reset().await?;
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(feature = "imap-backend")]
|
||||
// if let BackendConfig::Imap(imap_config) = &config.backend {
|
||||
// match &imap_config.auth {
|
||||
// ImapAuthConfig::Passwd(passwd) => {
|
||||
// passwd.configure(|| prompt_passwd("IMAP password")).await
|
||||
// }
|
||||
// ImapAuthConfig::OAuth2(oauth2) => {
|
||||
// oauth2
|
||||
// .configure(|| prompt_secret("IMAP OAuth 2.0 client secret"))
|
||||
// .await
|
||||
// }
|
||||
// }?;
|
||||
// }
|
||||
#[cfg(feature = "imap-backend")]
|
||||
if let Some(ref config) = config.imap {
|
||||
match &config.auth {
|
||||
ImapAuthConfig::Passwd(config) => {
|
||||
config.configure(|| prompt_passwd("IMAP password")).await
|
||||
}
|
||||
ImapAuthConfig::OAuth2(config) => {
|
||||
config
|
||||
.configure(|| prompt_secret("IMAP OAuth 2.0 client secret"))
|
||||
.await
|
||||
}
|
||||
}?;
|
||||
}
|
||||
|
||||
// #[cfg(feature = "smtp-sender")]
|
||||
// if let SenderConfig::Smtp(smtp_config) = &config.sender {
|
||||
// match &smtp_config.auth {
|
||||
// SmtpAuthConfig::Passwd(passwd) => {
|
||||
// passwd.configure(|| prompt_passwd("SMTP password")).await
|
||||
// }
|
||||
// SmtpAuthConfig::OAuth2(oauth2) => {
|
||||
// oauth2
|
||||
// .configure(|| prompt_secret("SMTP OAuth 2.0 client secret"))
|
||||
// .await
|
||||
// }
|
||||
// }?;
|
||||
// }
|
||||
#[cfg(feature = "smtp-sender")]
|
||||
if let Some(ref config) = config.smtp {
|
||||
match &config.auth {
|
||||
SmtpAuthConfig::Passwd(config) => {
|
||||
config.configure(|| prompt_passwd("SMTP password")).await
|
||||
}
|
||||
SmtpAuthConfig::OAuth2(config) => {
|
||||
config
|
||||
.configure(|| prompt_secret("SMTP OAuth 2.0 client secret"))
|
||||
.await
|
||||
}
|
||||
}?;
|
||||
}
|
||||
|
||||
// #[cfg(feature = "pgp")]
|
||||
// config
|
||||
// .pgp
|
||||
// .configure(&config.email, || prompt_passwd("PGP secret key password"))
|
||||
// .await?;
|
||||
#[cfg(feature = "pgp")]
|
||||
if let Some(ref config) = config.pgp {
|
||||
config
|
||||
.pgp
|
||||
.configure(&config.email, || prompt_passwd("PGP secret key password"))
|
||||
.await?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Account successfully {}configured!",
|
||||
|
|
|
@ -10,7 +10,7 @@ pub struct MessageConfig {
|
|||
pub peek: Option<MessagePeekConfig>,
|
||||
pub get: Option<MessageGetConfig>,
|
||||
pub copy: Option<MessageCopyConfig>,
|
||||
#[serde(rename = "move")]
|
||||
#[serde(default, rename = "move", skip_serializing_if = "Option::is_none")]
|
||||
pub move_: Option<MessageMoveConfig>,
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
|
|||
ImapAuthConfig::Passwd(config)
|
||||
}
|
||||
Some(idx) if AUTH_MECHANISMS[idx] == OAUTH2 => {
|
||||
let mut config = OAuth2Config::default();
|
||||
let mut config = OAuth2Config::new()?;
|
||||
|
||||
let method = Select::with_theme(&*THEME)
|
||||
.with_prompt("IMAP OAuth 2.0 mechanism")
|
||||
|
|
|
@ -131,10 +131,10 @@ async fn main() -> Result<()> {
|
|||
return account::handlers::sync(&mut printer, sync_builder, dry_run).await;
|
||||
}
|
||||
Some(account::args::Cmd::Configure(reset)) => {
|
||||
let (_, account_config) = toml_config
|
||||
let (toml_account_config, _) = toml_config
|
||||
.clone()
|
||||
.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;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue