bump lib with backend features

This commit is contained in:
Clément DOUIN 2023-11-25 12:37:00 +01:00
parent 56fc31b367
commit cec658aff4
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
26 changed files with 2622 additions and 1805 deletions

2730
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,9 @@ version = "3.3"
[dependencies.anyhow] [dependencies.anyhow]
version = "1.0" version = "1.0"
[dependencies.async-trait]
version = "0.1"
[dependencies.atty] [dependencies.atty]
version = "0.2" version = "0.2"
@ -88,21 +91,23 @@ version = "0.7.0"
version = "1.16.0" version = "1.16.0"
[dependencies.email-lib] [dependencies.email-lib]
version = "=0.15.3" # version = "=0.15.3"
default-features = false default-features = false
path = "/home/soywod/sourcehut/pimalaya/email"
[dependencies.keyring-lib] [dependencies.keyring-lib]
version = "=0.1.0" version = "=0.1.0"
[dependencies.oauth-lib] [dependencies.oauth-lib]
version = "=0.1.0" # version = "=0.1.0"
path = "/home/soywod/sourcehut/pimalaya/oauth"
[dependencies.process-lib] [dependencies.process-lib]
version = "=0.1.0" version = "=0.1.0"
[dependencies.mml-lib] [dependencies.mml-lib]
version = "=0.5.0" # version = "=1.0.1"
default-features = false path = "/home/soywod/sourcehut/pimalaya/mml"
[dependencies.secret-lib] [dependencies.secret-lib]
version = "=0.1.0" version = "=0.1.0"
@ -115,7 +120,8 @@ features = ["derive"]
version = "1.0" version = "1.0"
[dependencies.shellexpand-utils] [dependencies.shellexpand-utils]
version = "=0.1.0" # version = "=0.1.0"
path = "/home/soywod/sourcehut/pimalaya/shellexpand-utils"
[dependencies.termcolor] [dependencies.termcolor]
version = "1.1" version = "1.1"

View file

@ -1,54 +1,18 @@
display-name = "Display NAME"
signature-delim = "~~"
signature = "~/.signature"
downloads-dir = "~/downloads"
folder-listing-page-size = 12
email-listing-page-size = 12
email-reading-headers = ["From", "To"]
email-reading-verify-cmd = "gpg --verify -q"
email-reading-decrypt-cmd = "gpg -dq"
email-writing-sign-cmd = "gpg -o - -saq"
email-writing-encrypt-cmd = "gpg -o - -eqar <recipient>"
[example] [example]
default = true default = true
display-name = "Display NAME (gmail)"
email = "display.name@gmail.local"
# The display-name and email are used to build the full email address:
# "My example account" <example@localhost>
display-name = "My example account"
email = "example@localhost"
# The default backend used for all the features like adding folders,
# listing envelopes or copying messages.
backend = "imap" backend = "imap"
imap-host = "imap.gmail.com"
imap-login = "display.name@gmail.local"
imap-auth = "passwd"
imap-passwd.cmd = "pass show gmail"
imap-port = 993
imap-ssl = true
imap-starttls = false
imap-notify-cmd = """📫 "<sender>" "<subject>""""
imap-notify-query = "NOT SEEN"
imap-watch-cmds = ["echo \"received server changes!\""]
sender = "smtp" imap.host = "imap.gmail.com"
smtp-host = "smtp.gmail.com" imap.port = 993
smtp-login = "display.name@gmail.local" imap.login = "example@localhost"
smtp-auth = "passwd" imap.auth = "passwd"
smtp-passwd.cmd = "pass show piana/gmail" # imap.Some.passwd.cmd = "pass show gmail"
smtp-port = 465
smtp-ssl = true
smtp-starttls = false
sync = true
sync-dir = "/tmp/sync/gmail"
sync-folders-strategy.include = ["INBOX"]
[example.folder-aliases]
inbox = "INBOX"
drafts = "[Gmail]/Drafts"
sent = "[Gmail]/Sent Mail"
trash = "[Gmail]/Trash"
[example.email-hooks]
pre-send = "echo $1"
[example.email-reading-format]
type = "fixed"
width = 64

237
src/backend.rs Normal file
View file

@ -0,0 +1,237 @@
use std::{collections::HashMap, ops::Deref};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
#[cfg(feature = "imap-backend")]
use email::imap::{ImapSessionBuilder, ImapSessionSync};
#[cfg(feature = "smtp-sender")]
use email::smtp::{SmtpClientBuilder, SmtpClientSync};
use email::{
account::AccountConfig,
config::Config,
folder::list::{imap::ListFoldersImap, maildir::ListFoldersMaildir},
maildir::{MaildirSessionBuilder, MaildirSessionSync},
sendmail::SendmailContext,
};
use serde::{Deserialize, Serialize};
use crate::config::DeserializedConfig;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BackendKind {
#[default]
None,
Maildir,
#[cfg(feature = "imap-backend")]
Imap,
#[cfg(feature = "notmuch-backend")]
Notmuch,
#[cfg(feature = "smtp-sender")]
Smtp,
Sendmail,
}
#[derive(Clone, Default)]
pub struct BackendContextBuilder {
account_config: AccountConfig,
#[cfg(feature = "imap-backend")]
imap: Option<ImapSessionBuilder>,
maildir: Option<MaildirSessionBuilder>,
#[cfg(feature = "smtp-sender")]
smtp: Option<SmtpClientBuilder>,
sendmail: Option<SendmailContext>,
}
#[async_trait]
impl email::backend::BackendContextBuilder for BackendContextBuilder {
type Context = BackendContext;
async fn build(self) -> Result<Self::Context> {
let mut ctx = BackendContext::default();
if let Some(maildir) = self.maildir {
ctx.maildir = Some(maildir.build().await?);
}
#[cfg(feature = "imap-backend")]
if let Some(imap) = self.imap {
ctx.imap = Some(imap.build().await?);
}
#[cfg(feature = "notmuch-backend")]
if let Some(notmuch) = self.notmuch {
ctx.notmuch = Some(notmuch.build().await?);
}
#[cfg(feature = "smtp-sender")]
if let Some(smtp) = self.smtp {
ctx.smtp = Some(smtp.build().await?);
}
if let Some(sendmail) = self.sendmail {
ctx.sendmail = Some(sendmail.build().await?);
}
Ok(ctx)
}
}
#[derive(Default)]
pub struct BackendContext {
#[cfg(feature = "imap-backend")]
pub imap: Option<ImapSessionSync>,
pub maildir: Option<MaildirSessionSync>,
#[cfg(feature = "smtp-sender")]
pub smtp: Option<SmtpClientSync>,
pub sendmail: Option<SendmailContext>,
}
pub struct BackendBuilder(pub email::backend::BackendBuilder<BackendContextBuilder>);
impl BackendBuilder {
pub async fn new(config: DeserializedConfig, account_name: Option<&str>) -> Result<Self> {
let (account_name, deserialized_account_config) = match account_name {
Some("default") | Some("") | None => config
.accounts
.iter()
.find_map(|(name, account)| {
account
.default
.filter(|default| *default == true)
.map(|_| (name.to_owned(), account.clone()))
})
.ok_or_else(|| anyhow!("cannot find default account")),
Some(name) => config
.accounts
.get(name)
.map(|account| (name.to_owned(), account.clone()))
.ok_or_else(|| anyhow!("cannot find account {name}")),
}?;
let config = Config {
display_name: config.display_name,
signature_delim: config.signature_delim,
signature: config.signature,
downloads_dir: config.downloads_dir,
folder_listing_page_size: config.folder_listing_page_size,
folder_aliases: config.folder_aliases,
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,
email_writing_headers: config.email_writing_headers,
email_sending_save_copy: config.email_sending_save_copy,
email_hooks: config.email_hooks,
accounts: HashMap::from_iter(config.accounts.clone().into_iter().map(
|(name, config)| {
(
name.clone(),
AccountConfig {
name,
email: config.email,
display_name: config.display_name,
signature_delim: config.signature_delim,
signature: config.signature,
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,
sync_dir: config.sync_dir,
sync_folders_strategy: config.sync_folders_strategy.unwrap_or_default(),
#[cfg(feature = "pgp")]
pgp: config.pgp,
},
)
},
)),
};
let account_config = config.account(account_name)?;
let backend_ctx_builder = BackendContextBuilder {
account_config: account_config.clone(),
maildir: deserialized_account_config
.maildir
.as_ref()
.map(|mdir_config| {
MaildirSessionBuilder::new(account_config.clone(), mdir_config.clone())
}),
#[cfg(feature = "imap-backend")]
imap: deserialized_account_config
.imap
.as_ref()
.map(|imap_config| {
ImapSessionBuilder::new(account_config.clone(), imap_config.clone())
}),
#[cfg(feature = "smtp-sender")]
smtp: deserialized_account_config
.smtp
.as_ref()
.map(|smtp_config| {
SmtpClientBuilder::new(account_config.clone(), smtp_config.clone())
}),
sendmail: deserialized_account_config
.sendmail
.as_ref()
.map(|sendmail_config| {
SendmailContext::new(account_config.clone(), sendmail_config.clone())
}),
..Default::default()
};
let backend_builder =
email::backend::BackendBuilder::new(account_config.clone(), backend_ctx_builder)
.with_list_folders(move |ctx| {
println!(
"deserialized_account_config: {:#?}",
deserialized_account_config
);
match deserialized_account_config.backend {
BackendKind::Maildir if ctx.maildir.is_some() => {
ListFoldersMaildir::new(ctx.maildir.as_ref().unwrap())
}
#[cfg(feature = "imap-backend")]
BackendKind::Imap if ctx.imap.is_some() => {
ListFoldersImap::new(ctx.imap.as_ref().unwrap())
}
#[cfg(feature = "notmuch-backend")]
BackendKind::Notmuch if ctx.notmuch.is_some() => {
ListFoldersNotmuch::new(ctx.notmuch.as_ref().unwrap())
}
_ => None,
}
});
Ok(Self(backend_builder))
}
}
impl Deref for BackendBuilder {
type Target = email::backend::BackendBuilder<BackendContextBuilder>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type Backend = email::backend::Backend<BackendContext>;

View file

@ -1,15 +1,12 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
#[cfg(feature = "imap-backend")] use email::account::AccountConfig;
use email::backend::ImapBackend;
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
use email::backend::NotmuchBackend; use email::backend::NotmuchBackend;
use email::{
account::AccountConfig,
backend::{Backend, MaildirBackend},
};
use log::{debug, trace}; use log::{debug, trace};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::backend::Backend;
const ID_MAPPER_DB_FILE_NAME: &str = ".id-mapper.sqlite"; const ID_MAPPER_DB_FILE_NAME: &str = ".id-mapper.sqlite";
#[derive(Debug)] #[derive(Debug)]
@ -39,47 +36,45 @@ impl IdMapper {
db_path db_path
} }
pub fn new( pub fn new(backend: &Backend, account_config: &AccountConfig, folder: &str) -> Result<Self> {
backend: &dyn Backend, Ok(IdMapper::Dummy)
account_config: &AccountConfig,
folder: &str,
) -> Result<Self> {
#[cfg(feature = "imap-backend")]
if backend.as_any().is::<ImapBackend>() {
return Ok(IdMapper::Dummy);
}
let mut db_path = PathBuf::new(); // #[cfg(feature = "imap-backend")]
// if backend.as_any().is::<ImapBackend>() {
// return Ok(IdMapper::Dummy);
// }
if let Some(backend) = backend.as_any().downcast_ref::<MaildirBackend>() { // let mut db_path = PathBuf::new();
db_path = Self::find_closest_db_path(backend.path())
}
#[cfg(feature = "notmuch-backend")] // if let Some(backend) = backend.as_any().downcast_ref::<MaildirBackend>() {
if let Some(backend) = backend.as_any().downcast_ref::<NotmuchBackend>() { // db_path = Self::find_closest_db_path(backend.path())
db_path = Self::find_closest_db_path(backend.path()) // }
}
let folder = account_config.get_folder_alias(folder)?; // #[cfg(feature = "notmuch-backend")]
let digest = md5::compute(account_config.name.clone() + &folder); // if let Some(backend) = backend.as_any().downcast_ref::<NotmuchBackend>() {
let table = format!("id_mapper_{digest:x}"); // db_path = Self::find_closest_db_path(backend.path())
debug!("creating id mapper table {table} at {db_path:?}…"); // }
let conn = rusqlite::Connection::open(&db_path) // let folder = account_config.get_folder_alias(folder)?;
.with_context(|| format!("cannot open id mapper database at {db_path:?}"))?; // let digest = md5::compute(account_config.name.clone() + &folder);
// let table = format!("id_mapper_{digest:x}");
// debug!("creating id mapper table {table} at {db_path:?}…");
let query = format!( // let conn = rusqlite::Connection::open(&db_path)
"CREATE TABLE IF NOT EXISTS {table} ( // .with_context(|| format!("cannot open id mapper database at {db_path:?}"))?;
id INTEGER PRIMARY KEY AUTOINCREMENT,
internal_id TEXT UNIQUE
)",
);
trace!("create table query: {query:#?}");
conn.execute(&query, []) // let query = format!(
.context("cannot create id mapper table")?; // "CREATE TABLE IF NOT EXISTS {table} (
// id INTEGER PRIMARY KEY AUTOINCREMENT,
// internal_id TEXT UNIQUE
// )",
// );
// trace!("create table query: {query:#?}");
Ok(Self::Mapper(table, conn)) // conn.execute(&query, [])
// .context("cannot create id mapper table")?;
// Ok(Self::Mapper(table, conn))
} }
pub fn create_alias<I>(&self, id: I) -> Result<String> pub fn create_alias<I>(&self, id: I) -> Result<String>

View file

@ -6,12 +6,8 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use dialoguer::Confirm; use dialoguer::Confirm;
use dirs::{config_dir, home_dir}; use dirs::{config_dir, home_dir};
use email::{ use email::email::{EmailHooks, EmailTextPlainFormat};
account::AccountConfig,
email::{EmailHooks, EmailTextPlainFormat},
};
use log::{debug, trace}; use log::{debug, trace};
use process::Cmd;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf, process::exit}; use std::{collections::HashMap, fs, path::PathBuf, process::exit};
use toml; use toml;
@ -39,44 +35,12 @@ pub struct DeserializedConfig {
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( #[serde(default, with = "OptionEmailTextPlainFormatDef")]
default, pub email_reading_format: Option<EmailTextPlainFormat>,
with = "EmailTextPlainFormatDef",
skip_serializing_if = "EmailTextPlainFormat::is_default"
)]
pub email_reading_format: EmailTextPlainFormat,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_verify_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_decrypt_cmd: Option<Cmd>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_sign_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_encrypt_cmd: Option<Cmd>,
pub email_sending_save_copy: Option<bool>, pub email_sending_save_copy: Option<bool>,
#[serde( #[serde(default, with = "OptionEmailHooksDef")]
default, pub email_hooks: Option<EmailHooks>,
with = "EmailHooksDef",
skip_serializing_if = "EmailHooks::is_empty"
)]
pub email_hooks: EmailHooks,
#[serde(flatten)] #[serde(flatten)]
pub accounts: HashMap<String, DeserializedAccountConfig>, pub accounts: HashMap<String, DeserializedAccountConfig>,
@ -134,28 +98,6 @@ impl DeserializedConfig {
.or_else(|| home_dir().map(|p| p.join(".himalayarc"))) .or_else(|| home_dir().map(|p| p.join(".himalayarc")))
.filter(|p| p.exists()) .filter(|p| p.exists())
} }
pub fn to_account_config(&self, account_name: Option<&str>) -> Result<AccountConfig> {
let (account_name, deserialized_account_config) = match account_name {
Some("default") | Some("") | None => self
.accounts
.iter()
.find_map(|(name, account)| {
account
.default
.filter(|default| *default == true)
.map(|_| (name.clone(), account))
})
.ok_or_else(|| anyhow!("cannot find default account")),
Some(name) => self
.accounts
.get(name)
.map(|account| (name.to_string(), account))
.ok_or_else(|| anyhow!(format!("cannot find account {}", name))),
}?;
Ok(deserialized_account_config.to_account_config(account_name, self))
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -9,15 +9,15 @@ use email::account::{NativePgpConfig, NativePgpSecretKey, SignedSecretKey};
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
use email::backend::NotmuchConfig; use email::backend::NotmuchConfig;
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
use email::backend::{ImapAuthConfig, ImapConfig}; use email::imap::{ImapAuthConfig, ImapConfig};
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
use email::sender::{SmtpAuthConfig, SmtpConfig}; use email::smtp::{SmtpAuthConfig, SmtpConfig};
use email::{ use email::{
account::{OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig}, account::{OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig},
backend::{BackendConfig, MaildirConfig},
email::{EmailHooks, EmailTextPlainFormat}, email::{EmailHooks, EmailTextPlainFormat},
folder::sync::FolderSyncStrategy, folder::sync::FolderSyncStrategy,
sender::{SenderConfig, SendmailConfig}, maildir::MaildirConfig,
sendmail::SendmailConfig,
}; };
use keyring::Entry; use keyring::Entry;
use process::{Cmd, Pipeline, SingleCmd}; use process::{Cmd, Pipeline, SingleCmd};
@ -108,49 +108,47 @@ pub enum OAuth2MethodDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "BackendConfig", tag = "backend", rename_all = "kebab-case")] #[serde(remote = "Option<ImapConfig>", from = "OptionImapConfig")]
pub enum BackendConfigDef { pub struct OptionImapConfigDef;
#[default]
None, #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[cfg(feature = "imap-backend")] pub struct OptionImapConfig {
#[serde(with = "ImapConfigDef")] #[serde(default, skip)]
Imap(ImapConfig), is_none: bool,
#[serde(with = "MaildirConfigDef")] #[serde(flatten, with = "ImapConfigDef")]
Maildir(MaildirConfig), inner: ImapConfig,
#[cfg(feature = "notmuch-backend")] }
#[serde(with = "NotmuchConfigDef")]
Notmuch(NotmuchConfig), impl From<OptionImapConfig> for Option<ImapConfig> {
fn from(config: OptionImapConfig) -> Option<ImapConfig> {
if config.is_none {
None
} else {
Some(config.inner)
}
}
} }
#[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")] #[serde(remote = "ImapConfig", rename_all = "kebab-case")]
pub struct ImapConfigDef { pub struct ImapConfigDef {
#[serde(rename = "imap-host")]
pub host: String, pub host: String,
#[serde(rename = "imap-port")]
pub port: u16, pub port: u16,
#[serde(rename = "imap-ssl")]
pub ssl: Option<bool>, pub ssl: Option<bool>,
#[serde(rename = "imap-starttls")]
pub starttls: Option<bool>, pub starttls: Option<bool>,
#[serde(rename = "imap-insecure")]
pub insecure: Option<bool>, pub insecure: Option<bool>,
#[serde(rename = "imap-login")]
pub login: String, pub login: String,
#[serde(flatten, with = "ImapAuthConfigDef")] #[serde(flatten, with = "ImapAuthConfigDef")]
pub auth: ImapAuthConfig, pub auth: ImapAuthConfig,
#[serde(rename = "imap-notify-cmd")]
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
#[serde(rename = "imap-notify-query")]
pub notify_query: Option<String>, pub notify_query: Option<String>,
#[serde(rename = "imap-watch-cmds")]
pub watch_cmds: Option<Vec<String>>, pub watch_cmds: Option<Vec<String>>,
} }
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "ImapAuthConfig", tag = "imap-auth")] #[serde(remote = "ImapAuthConfig", tag = "auth")]
pub enum ImapAuthConfigDef { pub enum ImapAuthConfigDef {
#[serde(rename = "passwd", alias = "password", with = "ImapPasswdConfigDef")] #[serde(rename = "passwd", alias = "password", with = "ImapPasswdConfigDef")]
Passwd(#[serde(default)] PasswdConfig), Passwd(#[serde(default)] PasswdConfig),
@ -227,6 +225,28 @@ pub enum ImapOAuth2ScopesDef {
Scopes(Vec<String>), Scopes(Vec<String>),
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<MaildirConfig>", from = "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),
}
impl From<OptionMaildirConfig> for Option<MaildirConfig> {
fn from(config: OptionMaildirConfig) -> Option<MaildirConfig> {
match config {
OptionMaildirConfig::None => None,
OptionMaildirConfig::Some(config) => Some(config),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "MaildirConfig", rename_all = "kebab-case")] #[serde(remote = "MaildirConfig", rename_all = "kebab-case")]
pub struct MaildirConfigDef { pub struct MaildirConfigDef {
@ -234,6 +254,31 @@ pub struct MaildirConfigDef {
pub root_dir: PathBuf, pub root_dir: PathBuf,
} }
#[cfg(feature = "notmuch-backend")]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<NotmuchConfig>", from = "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),
}
#[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),
}
}
}
#[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 = "NotmuchConfig", rename_all = "kebab-case")] #[serde(remote = "NotmuchConfig", rename_all = "kebab-case")]
@ -242,6 +287,35 @@ pub struct NotmuchConfigDef {
pub db_path: PathBuf, pub db_path: PathBuf,
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(
remote = "Option<EmailTextPlainFormat>",
from = "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),
}
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)),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde( #[serde(
remote = "EmailTextPlainFormat", remote = "EmailTextPlainFormat",
@ -257,15 +331,25 @@ pub enum EmailTextPlainFormatDef {
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "SenderConfig", tag = "sender", rename_all = "kebab-case")] #[serde(remote = "Option<SmtpConfig>", from = "OptionSmtpConfig")]
pub enum SenderConfigDef { pub struct OptionSmtpConfigDef;
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OptionSmtpConfig {
#[default] #[default]
#[serde(skip_serializing)]
None, None,
#[cfg(feature = "smtp-sender")] Some(#[serde(with = "SmtpConfigDef")] SmtpConfig),
#[serde(with = "SmtpConfigDef")] }
Smtp(SmtpConfig),
#[serde(with = "SendmailConfigDef")] impl From<OptionSmtpConfig> for Option<SmtpConfig> {
Sendmail(SendmailConfig), fn from(config: OptionSmtpConfig) -> Option<SmtpConfig> {
match config {
OptionSmtpConfig::None => None,
OptionSmtpConfig::Some(config) => Some(config),
}
}
} }
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
@ -367,6 +451,28 @@ pub enum SmtpOAuth2ScopesDef {
Scopes(Vec<String>), Scopes(Vec<String>),
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<SendmailConfig>", from = "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),
}
impl From<OptionSendmailConfig> for Option<SendmailConfig> {
fn from(config: OptionSendmailConfig) -> Option<SendmailConfig> {
match config {
OptionSendmailConfig::None => None,
OptionSendmailConfig::Some(config) => Some(config),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "SendmailConfig", rename_all = "kebab-case")] #[serde(remote = "SendmailConfig", rename_all = "kebab-case")]
pub struct SendmailConfigDef { pub struct SendmailConfigDef {
@ -382,6 +488,28 @@ fn sendmail_default_cmd() -> Cmd {
Cmd::from("/usr/sbin/sendmail") Cmd::from("/usr/sbin/sendmail")
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "Option<EmailHooks>", from = "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),
}
impl From<OptionEmailHooks> for Option<EmailHooks> {
fn from(fmt: OptionEmailHooks) -> Option<EmailHooks> {
match fmt {
OptionEmailHooks::None => None,
OptionEmailHooks::Some(hooks) => Some(hooks),
}
}
}
/// Represents the email hooks. Useful for doing extra email /// Represents the email hooks. Useful for doing extra email
/// processing before or after sending it. /// processing before or after sending it.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
@ -392,6 +520,31 @@ pub struct EmailHooksDef {
pub pre_send: Option<Cmd>, pub pre_send: Option<Cmd>,
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(
remote = "Option<FolderSyncStrategy>",
from = "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),
}
impl From<OptionFolderSyncStrategy> for Option<FolderSyncStrategy> {
fn from(config: OptionFolderSyncStrategy) -> Option<FolderSyncStrategy> {
match config {
OptionFolderSyncStrategy::None => None,
OptionFolderSyncStrategy::Some(config) => Some(config),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "FolderSyncStrategy", rename_all = "kebab-case")] #[serde(remote = "FolderSyncStrategy", rename_all = "kebab-case")]
pub enum FolderSyncStrategyDef { pub enum FolderSyncStrategyDef {

View file

@ -5,7 +5,6 @@
//! accounts from the config file. //! accounts from the config file.
use anyhow::Result; use anyhow::Result;
use email::backend::BackendConfig;
use serde::Serialize; use serde::Serialize;
use std::{collections::hash_map::Iter, ops::Deref}; use std::{collections::hash_map::Iter, ops::Deref};
@ -40,19 +39,45 @@ impl PrintTable for Accounts {
impl From<Iter<'_, String, DeserializedAccountConfig>> for Accounts { impl From<Iter<'_, String, DeserializedAccountConfig>> for Accounts {
fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self { fn from(map: Iter<'_, String, DeserializedAccountConfig>) -> Self {
let mut accounts: Vec<_> = map let mut accounts: Vec<_> = map
.map(|(name, account)| match &account.backend { .map(|(name, account)| {
BackendConfig::None => Account::new(name, "none", false), let mut backends = String::new();
BackendConfig::Maildir(_) => {
Account::new(name, "maildir", account.default.unwrap_or_default())
}
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
BackendConfig::Imap(_) => { if account.imap.is_some() {
Account::new(name, "imap", account.default.unwrap_or_default()) backends.push_str("imap");
} }
if account.maildir.is_some() {
if !backends.is_empty() {
backends.push_str(", ")
}
backends.push_str("maildir");
}
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
BackendConfig::Notmuch(_) => { if account.imap.is_some() {
Account::new(name, "notmuch", account.default.unwrap_or_default()) if !backends.is_empty() {
backends.push_str(", ")
}
backends.push_str("notmuch");
} }
#[cfg(feature = "smtp-sender")]
if account.smtp.is_some() {
if !backends.is_empty() {
backends.push_str(", ")
}
backends.push_str("smtp");
}
if account.sendmail.is_some() {
if !backends.is_empty() {
backends.push_str(", ")
}
backends.push_str("sendmail");
}
Account::new(name, &backends, account.default.unwrap_or_default())
}) })
.collect(); .collect();
accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap()); accounts.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap());

View file

@ -6,29 +6,27 @@
#[cfg(feature = "pgp")] #[cfg(feature = "pgp")]
use email::account::PgpConfig; use email::account::PgpConfig;
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
use email::backend::ImapAuthConfig; use email::imap::ImapConfig;
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
use email::sender::SmtpAuthConfig; use email::smtp::SmtpConfig;
use email::{ use email::{
account::AccountConfig,
backend::BackendConfig,
email::{EmailHooks, EmailTextPlainFormat}, email::{EmailHooks, EmailTextPlainFormat},
folder::sync::FolderSyncStrategy, folder::sync::FolderSyncStrategy,
sender::SenderConfig, maildir::MaildirConfig,
sendmail::SendmailConfig,
}; };
use process::Cmd;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use crate::config::{prelude::*, DeserializedConfig}; use crate::{backend::BackendKind, config::prelude::*};
/// 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(tag = "backend", rename_all = "kebab-case")]
pub struct DeserializedAccountConfig { pub struct DeserializedAccountConfig {
pub email: String,
pub default: Option<bool>, pub default: Option<bool>,
pub email: String,
pub display_name: Option<String>, pub display_name: Option<String>,
pub signature_delim: Option<String>, pub signature_delim: Option<String>,
pub signature: Option<String>, pub signature: Option<String>,
@ -41,192 +39,39 @@ pub struct DeserializedAccountConfig {
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( #[serde(default, with = "OptionEmailTextPlainFormatDef")]
default, pub email_reading_format: Option<EmailTextPlainFormat>,
with = "EmailTextPlainFormatDef",
skip_serializing_if = "EmailTextPlainFormat::is_default"
)]
pub email_reading_format: EmailTextPlainFormat,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_verify_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_reading_decrypt_cmd: Option<Cmd>,
pub email_writing_headers: Option<Vec<String>>, pub email_writing_headers: Option<Vec<String>>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_sign_cmd: Option<Cmd>,
#[serde(
default,
with = "OptionCmdDef",
skip_serializing_if = "Option::is_none"
)]
pub email_writing_encrypt_cmd: Option<Cmd>,
pub email_sending_save_copy: Option<bool>, pub email_sending_save_copy: Option<bool>,
#[serde( #[serde(default, with = "OptionEmailHooksDef")]
default, pub email_hooks: Option<EmailHooks>,
with = "EmailHooksDef",
skip_serializing_if = "EmailHooks::is_empty"
)]
pub email_hooks: EmailHooks,
pub sync: Option<bool>, pub sync: Option<bool>,
pub sync_dir: Option<PathBuf>, pub sync_dir: Option<PathBuf>,
#[serde( #[serde(default, with = "OptionFolderSyncStrategyDef")]
default, pub sync_folders_strategy: Option<FolderSyncStrategy>,
with = "FolderSyncStrategyDef",
skip_serializing_if = "FolderSyncStrategy::is_default"
)]
pub sync_folders_strategy: FolderSyncStrategy,
#[serde(flatten, with = "BackendConfigDef")] pub backend: BackendKind,
pub backend: BackendConfig,
#[serde(flatten, with = "SenderConfigDef")] #[cfg(feature = "imap-backend")]
pub sender: SenderConfig, #[serde(default, with = "OptionImapConfigDef")]
pub imap: Option<ImapConfig>,
#[serde(default, with = "OptionMaildirConfigDef")]
pub maildir: Option<MaildirConfig>,
#[cfg(feature = "notmuch-backend")]
#[serde(default, with = "OptionNotmuchConfigDef")]
pub notmuch: Option<NotmuchConfig>,
#[cfg(feature = "smtp-sender")]
#[serde(default, with = "OptionSmtpConfigDef")]
pub smtp: Option<SmtpConfig>,
#[serde(default, with = "OptionSendmailConfigDef")]
pub sendmail: Option<SendmailConfig>,
#[cfg(feature = "pgp")] #[cfg(feature = "pgp")]
#[serde(default, with = "PgpConfigDef")] #[serde(default, with = "OptionPgpConfigDef")]
pub pgp: PgpConfig, pub pgp: Option<PgpConfig>,
}
impl DeserializedAccountConfig {
pub fn to_account_config(&self, name: String, config: &DeserializedConfig) -> AccountConfig {
let mut folder_aliases = config
.folder_aliases
.as_ref()
.map(ToOwned::to_owned)
.unwrap_or_default();
folder_aliases.extend(
self.folder_aliases
.as_ref()
.map(ToOwned::to_owned)
.unwrap_or_default(),
);
AccountConfig {
name: name.clone(),
email: self.email.to_owned(),
display_name: self
.display_name
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.display_name.as_ref().map(ToOwned::to_owned)),
signature_delim: self
.signature_delim
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.signature_delim.as_ref().map(ToOwned::to_owned)),
signature: self
.signature
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.signature.as_ref().map(ToOwned::to_owned)),
downloads_dir: self
.downloads_dir
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.downloads_dir.as_ref().map(ToOwned::to_owned)),
folder_listing_page_size: self
.folder_listing_page_size
.or_else(|| config.folder_listing_page_size),
folder_aliases,
email_listing_page_size: self
.email_listing_page_size
.or_else(|| config.email_listing_page_size),
email_listing_datetime_fmt: self
.email_listing_datetime_fmt
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| {
config
.email_listing_datetime_fmt
.as_ref()
.map(ToOwned::to_owned)
}),
email_listing_datetime_local_tz: self
.email_listing_datetime_local_tz
.or_else(|| config.email_listing_datetime_local_tz),
email_reading_headers: self
.email_reading_headers
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)),
email_reading_format: self.email_reading_format.clone(),
email_writing_headers: self
.email_writing_headers
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| config.email_writing_headers.as_ref().map(ToOwned::to_owned)),
email_sending_save_copy: self.email_sending_save_copy.unwrap_or(true),
email_hooks: EmailHooks {
pre_send: self.email_hooks.pre_send.clone(),
},
sync: self.sync.unwrap_or_default(),
sync_dir: self.sync_dir.clone(),
sync_folders_strategy: self.sync_folders_strategy.clone(),
backend: {
let mut backend = self.backend.clone();
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(config) = &mut backend {
match &mut config.auth {
ImapAuthConfig::Passwd(secret) => {
secret.set_keyring_entry_if_undefined(format!("{name}-imap-passwd"));
}
ImapAuthConfig::OAuth2(config) => {
config.client_secret.set_keyring_entry_if_undefined(format!(
"{name}-imap-oauth2-client-secret"
));
config.access_token.set_keyring_entry_if_undefined(format!(
"{name}-imap-oauth2-access-token"
));
config.refresh_token.set_keyring_entry_if_undefined(format!(
"{name}-imap-oauth2-refresh-token"
));
}
};
}
backend
},
sender: {
let mut sender = self.sender.clone();
#[cfg(feature = "smtp-sender")]
if let SenderConfig::Smtp(config) = &mut sender {
match &mut config.auth {
SmtpAuthConfig::Passwd(secret) => {
secret.set_keyring_entry_if_undefined(format!("{name}-smtp-passwd"));
}
SmtpAuthConfig::OAuth2(config) => {
config.client_secret.set_keyring_entry_if_undefined(format!(
"{name}-smtp-oauth2-client-secret"
));
config.access_token.set_keyring_entry_if_undefined(format!(
"{name}-smtp-oauth2-access-token"
));
config.refresh_token.set_keyring_entry_if_undefined(format!(
"{name}-smtp-oauth2-refresh-token"
));
}
};
}
sender
},
#[cfg(feature = "pgp")]
pgp: self.pgp.clone(),
}
}
} }

View file

@ -2,25 +2,22 @@
//! //!
//! This module gathers all account actions triggered by the CLI. //! This module gathers all account actions triggered by the CLI.
use anyhow::Result; use anyhow::{Context, Result};
#[cfg(feature = "imap-backend")] use email::account::{
use email::backend::ImapAuthConfig; sync::{AccountSyncBuilder, AccountSyncProgressEvent},
#[cfg(feature = "smtp-sender")] AccountConfig,
use email::sender::SmtpAuthConfig;
use email::{
account::{
sync::{AccountSyncBuilder, AccountSyncProgressEvent},
AccountConfig,
},
backend::BackendConfig,
sender::SenderConfig,
}; };
#[cfg(feature = "imap-backend")]
use email::imap::ImapAuthConfig;
#[cfg(feature = "smtp-sender")]
use email::smtp::SmtpAuthConfig;
use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressFinish, ProgressStyle};
use log::{info, trace, warn}; use log::{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};
use crate::{ use crate::{
backend::BackendContextBuilder,
config::{ config::{
wizard::{prompt_passwd, prompt_secret}, wizard::{prompt_passwd, prompt_secret},
DeserializedConfig, DeserializedConfig,
@ -48,68 +45,68 @@ const SUB_PROGRESS_DONE_STYLE: Lazy<ProgressStyle> = Lazy::new(|| {
pub async fn configure(config: &AccountConfig, reset: bool) -> Result<()> { pub async fn configure(config: &AccountConfig, 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 BackendConfig::Imap(imap_config) = &config.backend {
let reset = match &imap_config.auth { // let reset = match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => passwd.reset(), // ImapAuthConfig::Passwd(passwd) => passwd.reset(),
ImapAuthConfig::OAuth2(oauth2) => oauth2.reset(), // ImapAuthConfig::OAuth2(oauth2) => oauth2.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, skipping it");
warn!("{err}"); // warn!("{err}");
} // }
} // }
#[cfg(feature = "smtp-sender")] // #[cfg(feature = "smtp-sender")]
if let SenderConfig::Smtp(smtp_config) = &config.sender { // if let SenderConfig::Smtp(smtp_config) = &config.sender {
let reset = match &smtp_config.auth { // let reset = match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => passwd.reset(), // SmtpAuthConfig::Passwd(passwd) => passwd.reset(),
SmtpAuthConfig::OAuth2(oauth2) => oauth2.reset(), // SmtpAuthConfig::OAuth2(oauth2) => oauth2.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, skipping it");
warn!("{err}"); // warn!("{err}");
} // }
} // }
#[cfg(feature = "pgp")] // #[cfg(feature = "pgp")]
config.pgp.reset().await?; // config.pgp.reset().await?;
} // }
#[cfg(feature = "imap-backend")] // #[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = &config.backend { // if let BackendConfig::Imap(imap_config) = &config.backend {
match &imap_config.auth { // match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => { // ImapAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("IMAP password")).await // passwd.configure(|| prompt_passwd("IMAP password")).await
} // }
ImapAuthConfig::OAuth2(oauth2) => { // ImapAuthConfig::OAuth2(oauth2) => {
oauth2 // oauth2
.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 SenderConfig::Smtp(smtp_config) = &config.sender {
match &smtp_config.auth { // match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => { // SmtpAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("SMTP password")).await // passwd.configure(|| prompt_passwd("SMTP password")).await
} // }
SmtpAuthConfig::OAuth2(oauth2) => { // SmtpAuthConfig::OAuth2(oauth2) => {
oauth2 // oauth2
.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 // config
.pgp // .pgp
.configure(&config.email, || prompt_passwd("PGP secret key password")) // .configure(&config.email, || prompt_passwd("PGP secret key password"))
.await?; // .await?;
println!( println!(
"Account successfully {}configured!", "Account successfully {}configured!",
@ -147,7 +144,7 @@ pub fn list<'a, P: Printer>(
/// no account given, synchronizes the default one. /// no account given, synchronizes the default one.
pub async fn sync<P: Printer>( pub async fn sync<P: Printer>(
printer: &mut P, printer: &mut P,
sync_builder: AccountSyncBuilder, sync_builder: AccountSyncBuilder<BackendContextBuilder>,
dry_run: bool, dry_run: bool,
) -> Result<()> { ) -> Result<()> {
info!("entering the sync accounts handler"); info!("entering the sync accounts handler");

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
use dialoguer::Input; use dialoguer::Input;
use email_address::EmailAddress; use email_address::EmailAddress;
use crate::{backend, config::wizard::THEME, sender}; use crate::config::wizard::THEME;
use super::DeserializedAccountConfig; use super::DeserializedAccountConfig;
@ -31,9 +31,9 @@ pub(crate) async fn configure() -> Result<Option<(String, DeserializedAccountCon
.interact()?, .interact()?,
); );
config.backend = backend::wizard::configure(&account_name, &config.email).await?; // config.backend = backend::wizard::configure(&account_name, &config.email).await?;
config.sender = sender::wizard::configure(&account_name, &config.email).await?; // config.sender = sender::wizard::configure(&account_name, &config.email).await?;
Ok(Some((account_name, config))) Ok(Some((account_name, config)))
} }

View file

@ -3,7 +3,6 @@
//! This module gathers all IMAP handlers triggered by the CLI. //! This module gathers all IMAP handlers triggered by the CLI.
use anyhow::Result; use anyhow::Result;
use email::backend::ImapBackend;
pub async fn notify(imap: &mut ImapBackend, folder: &str, keepalive: u64) -> Result<()> { pub async fn notify(imap: &mut ImapBackend, folder: &str, keepalive: u64) -> Result<()> {
imap.notify(keepalive, folder).await?; imap.notify(keepalive, folder).await?;

View file

@ -1,3 +1,3 @@
pub mod args; pub mod args;
pub mod handlers; // pub mod handlers;
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -1 +1 @@
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -3,4 +3,4 @@ pub mod imap;
pub mod maildir; pub mod maildir;
#[cfg(feature = "notmuch-backend")] #[cfg(feature = "notmuch-backend")]
pub mod notmuch; pub mod notmuch;
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -2,9 +2,7 @@ use anyhow::{anyhow, Context, Result};
use atty::Stream; use atty::Stream;
use email::{ use email::{
account::AccountConfig, account::AccountConfig,
backend::Backend, email::{envelope::Id, template::FilterParts, Flag, Message, MessageBuilder},
email::{template::FilterParts, Flag, Flags, Message, MessageBuilder},
sender::Sender,
}; };
use log::{debug, trace}; use log::{debug, trace};
use std::{ use std::{
@ -15,6 +13,7 @@ use url::Url;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
backend::Backend,
printer::{PrintTableOpts, Printer}, printer::{PrintTableOpts, Printer},
ui::editor, ui::editor,
Envelopes, IdMapper, Envelopes, IdMapper,
@ -24,20 +23,20 @@ pub async fn attachments<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); let emails = backend.get_messages(&folder, &ids).await?;
let emails = backend.get_emails(&folder, ids.clone()).await?;
let mut index = 0; let mut index = 0;
let mut emails_count = 0; let mut emails_count = 0;
let mut attachments_count = 0; let mut attachments_count = 0;
let mut ids = ids.iter();
for email in emails.to_vec() { for email in emails.to_vec() {
let id = ids.get(index).unwrap(); let id = ids.next().unwrap();
let attachments = email.attachments()?; let attachments = email.attachments()?;
index = index + 1; index = index + 1;
@ -79,27 +78,27 @@ pub async fn attachments<P: Printer>(
pub async fn copy<P: Printer>( pub async fn copy<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
from_folder: &str, from_folder: &str,
to_folder: &str, to_folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend
backend.copy_emails(&from_folder, &to_folder, ids).await?; .copy_messages(&from_folder, &to_folder, &ids)
.await?;
printer.print("Email(s) successfully copied!") printer.print("Email(s) successfully copied!")
} }
pub async fn delete<P: Printer>( pub async fn delete<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend.delete_messages(&folder, &ids).await?;
backend.delete_emails(&folder, ids).await?;
printer.print("Email(s) successfully deleted!") printer.print("Email(s) successfully deleted!")
} }
@ -107,18 +106,15 @@ pub async fn forward<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
folder: &str, folder: &str,
id: &str, id: &str,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
body: Option<&str>, body: Option<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids([id])?; let id = Id::single(id_mapper.get_id(id)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend let tpl = backend
.get_emails(&folder, ids) .get_messages(&folder, &id)
.await? .await?
.first() .first()
.ok_or_else(|| anyhow!("cannot find email {}", id))? .ok_or_else(|| anyhow!("cannot find email {}", id))?
@ -128,7 +124,7 @@ pub async fn forward<P: Printer>(
.build() .build()
.await?; .await?;
trace!("initial template: {tpl}"); trace!("initial template: {tpl}");
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl).await?; editor::edit_tpl_with_editor(config, printer, backend, tpl).await?;
Ok(()) Ok(())
} }
@ -136,7 +132,7 @@ pub async fn list<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
max_width: Option<usize>, max_width: Option<usize>,
page_size: Option<usize>, page_size: Option<usize>,
@ -166,8 +162,7 @@ pub async fn list<P: Printer>(
/// [mailto]: https://en.wikipedia.org/wiki/Mailto /// [mailto]: https://en.wikipedia.org/wiki/Mailto
pub async fn mailto<P: Printer>( pub async fn mailto<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
printer: &mut P, printer: &mut P,
url: &Url, url: &Url,
) -> Result<()> { ) -> Result<()> {
@ -190,20 +185,21 @@ pub async fn mailto<P: Printer>(
.from_msg_builder(builder) .from_msg_builder(builder)
.await?; .await?;
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl).await editor::edit_tpl_with_editor(config, printer, backend, tpl).await
} }
pub async fn move_<P: Printer>( pub async fn move_<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
from_folder: &str, from_folder: &str,
to_folder: &str, to_folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend
backend.move_emails(&from_folder, &to_folder, ids).await?; .move_messages(&from_folder, &to_folder, &ids)
.await?;
printer.print("Email(s) successfully moved!") printer.print("Email(s) successfully moved!")
} }
@ -211,16 +207,15 @@ pub async fn read<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
text_mime: &str, text_mime: &str,
raw: bool, raw: bool,
headers: Vec<&str>, headers: Vec<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); let emails = backend.get_messages(&folder, &ids).await?;
let emails = backend.get_emails(&folder, ids).await?;
let mut glue = ""; let mut glue = "";
let mut bodies = String::default(); let mut bodies = String::default();
@ -255,19 +250,16 @@ pub async fn reply<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
folder: &str, folder: &str,
id: &str, id: &str,
all: bool, all: bool,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
body: Option<&str>, body: Option<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids([id])?; let id = Id::single(id_mapper.get_id(id)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl = backend let tpl = backend
.get_emails(&folder, ids) .get_messages(folder, &id)
.await? .await?
.first() .first()
.ok_or_else(|| anyhow!("cannot find email {}", id))? .ok_or_else(|| anyhow!("cannot find email {}", id))?
@ -278,17 +270,15 @@ pub async fn reply<P: Printer>(
.build() .build()
.await?; .await?;
trace!("initial template: {tpl}"); trace!("initial template: {tpl}");
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl).await?; editor::edit_tpl_with_editor(config, printer, backend, tpl).await?;
backend backend.add_flag(&folder, &id, Flag::Answered).await?;
.add_flags(&folder, vec![id], &Flags::from_iter([Flag::Answered]))
.await?;
Ok(()) Ok(())
} }
pub async fn save<P: Printer>( pub async fn save<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
raw_email: String, raw_email: String,
) -> Result<()> { ) -> Result<()> {
@ -306,73 +296,74 @@ pub async fn save<P: Printer>(
}; };
let id = backend let id = backend
.add_email(&folder, raw_email.as_bytes(), &Flags::default()) .add_raw_message(&folder, raw_email.as_bytes())
.await?; .await?;
id_mapper.create_alias(id)?; id_mapper.create_alias(&*id)?;
Ok(()) Ok(())
} }
pub async fn search<P: Printer>( pub async fn search<P: Printer>(
config: &AccountConfig, _config: &AccountConfig,
printer: &mut P, _printer: &mut P,
id_mapper: &IdMapper, _id_mapper: &IdMapper,
backend: &mut dyn Backend, _backend: &Backend,
folder: &str, _folder: &str,
query: String, _query: String,
max_width: Option<usize>, _max_width: Option<usize>,
page_size: Option<usize>, _page_size: Option<usize>,
page: usize, _page: usize,
) -> Result<()> { ) -> Result<()> {
let page_size = page_size.unwrap_or(config.email_listing_page_size()); todo!()
let envelopes = Envelopes::from_backend( // let page_size = page_size.unwrap_or(config.email_listing_page_size());
config, // let envelopes = Envelopes::from_backend(
id_mapper, // config,
backend // id_mapper,
.search_envelopes(&folder, &query, "", page_size, page) // backend
.await?, // .search_envelopes(&folder, &query, "", page_size, page)
)?; // .await?,
let opts = PrintTableOpts { // )?;
format: &config.email_reading_format, // let opts = PrintTableOpts {
max_width, // format: &config.email_reading_format,
}; // max_width,
// };
printer.print_table(Box::new(envelopes), opts) // printer.print_table(Box::new(envelopes), opts)
} }
pub async fn sort<P: Printer>( pub async fn sort<P: Printer>(
config: &AccountConfig, _config: &AccountConfig,
printer: &mut P, _printer: &mut P,
id_mapper: &IdMapper, _id_mapper: &IdMapper,
backend: &mut dyn Backend, _backend: &Backend,
folder: &str, _folder: &str,
sort: String, _sort: String,
query: String, _query: String,
max_width: Option<usize>, _max_width: Option<usize>,
page_size: Option<usize>, _page_size: Option<usize>,
page: usize, _page: usize,
) -> Result<()> { ) -> Result<()> {
let page_size = page_size.unwrap_or(config.email_listing_page_size()); todo!()
let envelopes = Envelopes::from_backend( // let page_size = page_size.unwrap_or(config.email_listing_page_size());
config, // let envelopes = Envelopes::from_backend(
id_mapper, // config,
backend // id_mapper,
.search_envelopes(&folder, &query, &sort, page_size, page) // backend
.await?, // .search_envelopes(&folder, &query, &sort, page_size, page)
)?; // .await?,
let opts = PrintTableOpts { // )?;
format: &config.email_reading_format, // let opts = PrintTableOpts {
max_width, // format: &config.email_reading_format,
}; // max_width,
// };
printer.print_table(Box::new(envelopes), opts) // printer.print_table(Box::new(envelopes), opts)
} }
pub async fn send<P: Printer>( pub async fn send<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
raw_email: String, raw_email: String,
) -> Result<()> { ) -> Result<()> {
let folder = config.sent_folder_alias()?; let folder = config.sent_folder_alias()?;
@ -389,14 +380,10 @@ pub async fn send<P: Printer>(
.join("\r\n") .join("\r\n")
}; };
trace!("raw email: {:?}", raw_email); trace!("raw email: {:?}", raw_email);
sender.send(raw_email.as_bytes()).await?; backend.send_raw_message(raw_email.as_bytes()).await?;
if config.email_sending_save_copy { if config.email_sending_save_copy.unwrap_or_default() {
backend backend
.add_email( .add_raw_message_with_flag(&folder, raw_email.as_bytes(), Flag::Seen)
&folder,
raw_email.as_bytes(),
&Flags::from_iter([Flag::Seen]),
)
.await?; .await?;
} }
Ok(()) Ok(())
@ -405,8 +392,7 @@ pub async fn send<P: Printer>(
pub async fn write<P: Printer>( pub async fn write<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
body: Option<&str>, body: Option<&str>,
) -> Result<()> { ) -> Result<()> {
@ -416,6 +402,6 @@ pub async fn write<P: Printer>(
.build() .build()
.await?; .await?;
trace!("initial template: {tpl}"); trace!("initial template: {tpl}");
editor::edit_tpl_with_editor(config, printer, backend, sender, tpl).await?; editor::edit_tpl_with_editor(config, printer, backend, tpl).await?;
Ok(()) Ok(())
} }

View file

@ -1,46 +1,43 @@
use anyhow::Result; use anyhow::Result;
use email::{backend::Backend, email::Flags}; use email::email::{envelope::Id, Flags};
use crate::{printer::Printer, IdMapper}; use crate::{backend::Backend, printer::Printer, IdMapper};
pub async fn add<P: Printer>( pub async fn add<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
flags: &Flags, flags: &Flags,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend.add_flags(folder, &ids, flags).await?;
backend.add_flags(folder, ids, flags).await?;
printer.print("Flag(s) successfully added!") printer.print("Flag(s) successfully added!")
} }
pub async fn set<P: Printer>( pub async fn set<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
flags: &Flags, flags: &Flags,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend.set_flags(folder, &ids, flags).await?;
backend.set_flags(folder, ids, flags).await?;
printer.print("Flag(s) successfully set!") printer.print("Flag(s) successfully set!")
} }
pub async fn remove<P: Printer>( pub async fn remove<P: Printer>(
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
ids: Vec<&str>, ids: Vec<&str>,
flags: &Flags, flags: &Flags,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids(ids)?; let ids = Id::multiple(id_mapper.get_ids(ids)?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>(); backend.remove_flags(folder, &ids, flags).await?;
backend.remove_flags(folder, ids, flags).await?;
printer.print("Flag(s) successfully removed!") printer.print("Flag(s) successfully removed!")
} }

View file

@ -4,19 +4,16 @@
use anyhow::Result; use anyhow::Result;
use dialoguer::Confirm; use dialoguer::Confirm;
use email::{account::AccountConfig, backend::Backend}; use email::account::AccountConfig;
use std::process; use std::process;
use crate::{ use crate::{
backend::Backend,
printer::{PrintTableOpts, Printer}, printer::{PrintTableOpts, Printer},
Folders, Folders,
}; };
pub async fn expunge<P: Printer>( pub async fn expunge<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
printer: &mut P,
backend: &mut dyn Backend,
folder: &str,
) -> Result<()> {
backend.expunge_folder(folder).await?; backend.expunge_folder(folder).await?;
printer.print(format!("Folder {folder} successfully expunged!")) printer.print(format!("Folder {folder} successfully expunged!"))
} }
@ -24,7 +21,7 @@ pub async fn expunge<P: Printer>(
pub async fn list<P: Printer>( pub async fn list<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &Backend,
max_width: Option<usize>, max_width: Option<usize>,
) -> Result<()> { ) -> Result<()> {
let folders: Folders = backend.list_folders().await?.into(); let folders: Folders = backend.list_folders().await?.into();
@ -38,20 +35,12 @@ pub async fn list<P: Printer>(
) )
} }
pub async fn create<P: Printer>( pub async fn create<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
printer: &mut P,
backend: &mut dyn Backend,
folder: &str,
) -> Result<()> {
backend.add_folder(folder).await?; backend.add_folder(folder).await?;
printer.print("Folder successfully created!") printer.print("Folder successfully created!")
} }
pub async fn delete<P: Printer>( pub async fn delete<P: Printer>(printer: &mut P, backend: &Backend, folder: &str) -> Result<()> {
printer: &mut P,
backend: &mut dyn Backend,
folder: &str,
) -> Result<()> {
if let Some(false) | None = Confirm::new() if let Some(false) | None = Confirm::new()
.with_prompt(format!("Confirm deletion of folder {folder}?")) .with_prompt(format!("Confirm deletion of folder {folder}?"))
.default(false) .default(false)

View file

@ -1,4 +1,4 @@
pub mod sendmail; pub mod sendmail;
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
pub mod smtp; pub mod smtp;
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -1 +1 @@
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -1 +1 @@
pub(crate) mod wizard; // pub(crate) mod wizard;

View file

@ -1,6 +1,5 @@
use anyhow::Result; use anyhow::Result;
use dialoguer::Select; use dialoguer::Select;
use email::sender::SenderConfig;
use crate::config::wizard::THEME; use crate::config::wizard::THEME;

View file

@ -2,30 +2,27 @@ use anyhow::{anyhow, Result};
use atty::Stream; use atty::Stream;
use email::{ use email::{
account::AccountConfig, account::AccountConfig,
backend::Backend, email::{envelope::Id, Flag, Message},
email::{Flag, Flags, Message},
sender::Sender,
}; };
use mml::MmlCompilerBuilder; use mml::MmlCompilerBuilder;
use std::io::{stdin, BufRead}; use std::io::{stdin, BufRead};
use crate::{printer::Printer, IdMapper}; use crate::{backend::Backend, printer::Printer, IdMapper};
pub async fn forward<P: Printer>( pub async fn forward<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
id: &str, id: &str,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
body: Option<&str>, body: Option<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids([id])?; let ids = Id::multiple(id_mapper.get_ids([id])?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl: String = backend let tpl: String = backend
.get_emails(folder, ids) .get_messages(folder, &ids)
.await? .await?
.first() .first()
.ok_or_else(|| anyhow!("cannot find email {}", id))? .ok_or_else(|| anyhow!("cannot find email {}", id))?
@ -43,18 +40,17 @@ pub async fn reply<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
id: &str, id: &str,
all: bool, all: bool,
headers: Option<Vec<(&str, &str)>>, headers: Option<Vec<(&str, &str)>>,
body: Option<&str>, body: Option<&str>,
) -> Result<()> { ) -> Result<()> {
let ids = id_mapper.get_ids([id])?; let ids = Id::multiple(id_mapper.get_ids([id])?);
let ids = ids.iter().map(String::as_str).collect::<Vec<_>>();
let tpl: String = backend let tpl: String = backend
.get_emails(folder, ids) .get_messages(folder, &ids)
.await? .await?
.first() .first()
.ok_or_else(|| anyhow!("cannot find email {}", id))? .ok_or_else(|| anyhow!("cannot find email {}", id))?
@ -73,7 +69,7 @@ pub async fn save<P: Printer>(
#[allow(unused_variables)] config: &AccountConfig, #[allow(unused_variables)] config: &AccountConfig,
printer: &mut P, printer: &mut P,
id_mapper: &IdMapper, id_mapper: &IdMapper,
backend: &mut dyn Backend, backend: &Backend,
folder: &str, folder: &str,
tpl: String, tpl: String,
) -> Result<()> { ) -> Result<()> {
@ -95,8 +91,8 @@ pub async fn save<P: Printer>(
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
let id = backend.add_email(folder, &email, &Flags::default()).await?; let id = backend.add_raw_message(folder, &email).await?;
id_mapper.create_alias(id)?; id_mapper.create_alias(&*id)?;
printer.print("Template successfully saved!") printer.print("Template successfully saved!")
} }
@ -104,8 +100,7 @@ pub async fn save<P: Printer>(
pub async fn send<P: Printer>( pub async fn send<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
tpl: String, tpl: String,
) -> Result<()> { ) -> Result<()> {
let folder = config.sent_folder_alias()?; let folder = config.sent_folder_alias()?;
@ -128,11 +123,11 @@ pub async fn send<P: Printer>(
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
sender.send(&email).await?; backend.send_raw_message(&email).await?;
if config.email_sending_save_copy { if config.email_sending_save_copy.unwrap_or_default() {
backend backend
.add_email(&folder, &email, &Flags::from_iter([Flag::Seen])) .add_raw_message_with_flag(&folder, &email, Flag::Seen)
.await?; .await?;
} }

View file

@ -1,3 +1,4 @@
pub mod backend;
pub mod cache; pub mod cache;
pub mod compl; pub mod compl;
pub mod config; pub mod config;

View file

@ -1,10 +1,4 @@
#[cfg(feature = "imap-backend")] use ::email::account::{sync::AccountSyncBuilder, DEFAULT_INBOX_FOLDER};
use ::email::backend::ImapBackend;
use ::email::{
account::{sync::AccountSyncBuilder, DEFAULT_INBOX_FOLDER},
backend::{BackendBuilder, BackendConfig},
sender::SenderBuilder,
};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use clap::Command; use clap::Command;
use log::{debug, warn}; use log::{debug, warn};
@ -14,7 +8,9 @@ use url::Url;
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
use himalaya::imap; use himalaya::imap;
use himalaya::{ use himalaya::{
account, cache, compl, account,
backend::BackendBuilder,
cache, compl,
config::{self, DeserializedConfig}, config::{self, DeserializedConfig},
email, flag, folder, man, output, email, flag, folder, man, output,
printer::StdoutPrinter, printer::StdoutPrinter,
@ -60,21 +56,19 @@ async fn main() -> Result<()> {
// checks mailto command before app initialization // checks mailto command before app initialization
let raw_args: Vec<String> = env::args().collect(); let raw_args: Vec<String> = env::args().collect();
if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") { if raw_args.len() > 1 && raw_args[1].starts_with("mailto:") {
let url = Url::parse(&raw_args[1])?; // let url = Url::parse(&raw_args[1])?;
let config = DeserializedConfig::from_opt_path(None).await?; // let config = DeserializedConfig::from_opt_path(None).await?;
let account_config = config.to_account_config(None)?; // let account_config = config.to_account_config(None)?;
let mut backend = BackendBuilder::new(account_config.clone()).build().await?; // let backend = BackendBuilder::new(account_config.clone()).build().await?;
let mut sender = SenderBuilder::new(account_config.clone()).build().await?; // let mut printer = StdoutPrinter::default();
let mut printer = StdoutPrinter::default();
email::handlers::mailto( // email::handlers::mailto(
&account_config, // &account_config,
backend.as_mut(), // &backend,
sender.as_mut(), // &mut printer,
&mut printer, // &url,
&url, // )
) // .await?;
.await?;
return Ok(()); return Ok(());
} }
@ -100,37 +94,33 @@ async fn main() -> Result<()> {
} }
let config = DeserializedConfig::from_opt_path(config::args::parse_arg(&m)).await?; let config = DeserializedConfig::from_opt_path(config::args::parse_arg(&m)).await?;
let account_config = config.to_account_config(account::args::parse_arg(&m))?; let maybe_account_name = account::args::parse_arg(&m);
let folder = folder::args::parse_source_arg(&m); let folder = folder::args::parse_source_arg(&m);
let disable_cache = cache::args::parse_disable_cache_flag(&m); let disable_cache = cache::args::parse_disable_cache_flag(&m);
// FIXME: find why account config cannot be borrowed let backend_builder = BackendBuilder::new(config.clone(), maybe_account_name).await?;
// let backend_builder = let account_config = &backend_builder.account_config;
// BackendBuilder::new(Cow::Borrowed(&account_config)).with_cache_disabled(disable_cache);
let backend_builder =
BackendBuilder::new(account_config.clone()).with_cache_disabled(disable_cache);
let sender_builder = SenderBuilder::new(account_config.clone());
let mut printer = StdoutPrinter::try_from(&m)?; let mut printer = StdoutPrinter::try_from(&m)?;
#[cfg(feature = "imap-backend")] // #[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = &account_config.backend { // if let BackendConfig::Imap(imap_config) = &account_config.backend {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); // let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
match imap::args::matches(&m)? { // match imap::args::matches(&m)? {
Some(imap::args::Cmd::Notify(keepalive)) => { // Some(imap::args::Cmd::Notify(keepalive)) => {
let mut backend = // let backend =
ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?; // ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
imap::handlers::notify(&mut backend, &folder, keepalive).await?; // imap::handlers::notify(&mut backend, &folder, keepalive).await?;
return Ok(()); // return Ok(());
} // }
Some(imap::args::Cmd::Watch(keepalive)) => { // Some(imap::args::Cmd::Watch(keepalive)) => {
let mut backend = // let backend =
ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?; // ImapBackend::new(account_config.clone(), imap_config.clone(), None).await?;
imap::handlers::watch(&mut backend, &folder, keepalive).await?; // imap::handlers::watch(&mut backend, &folder, keepalive).await?;
return Ok(()); // return Ok(());
} // }
_ => (), // _ => (),
} // }
} // }
match account::args::matches(&m)? { match account::args::matches(&m)? {
Some(account::args::Cmd::List(max_width)) => { Some(account::args::Cmd::List(max_width)) => {
@ -138,7 +128,7 @@ async fn main() -> Result<()> {
return Ok(()); return Ok(());
} }
Some(account::args::Cmd::Sync(strategy, dry_run)) => { Some(account::args::Cmd::Sync(strategy, dry_run)) => {
let sync_builder = AccountSyncBuilder::new(account_config, backend_builder) let sync_builder = AccountSyncBuilder::new(backend_builder.0)
.await? .await?
.with_some_folders_strategy(strategy) .with_some_folders_strategy(strategy)
.with_dry_run(dry_run); .with_dry_run(dry_run);
@ -158,26 +148,25 @@ async fn main() -> Result<()> {
let folder = folder let folder = folder
.ok_or_else(|| anyhow!("the folder argument is missing")) .ok_or_else(|| anyhow!("the folder argument is missing"))
.context("cannot create folder")?; .context("cannot create folder")?;
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
folder::handlers::create(&mut printer, backend.as_mut(), &folder).await?; folder::handlers::create(&mut printer, &backend, &folder).await?;
return Ok(()); return Ok(());
} }
Some(folder::args::Cmd::List(max_width)) => { Some(folder::args::Cmd::List(max_width)) => {
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
folder::handlers::list(&account_config, &mut printer, backend.as_mut(), max_width) folder::handlers::list(&account_config, &mut printer, &backend, max_width).await?;
.await?;
return Ok(()); return Ok(());
} }
Some(folder::args::Cmd::Expunge) => { Some(folder::args::Cmd::Expunge) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
folder::handlers::expunge(&mut printer, backend.as_mut(), &folder).await?; folder::handlers::expunge(&mut printer, &backend, &folder).await?;
return Ok(()); return Ok(());
} }
Some(folder::args::Cmd::Delete) => { Some(folder::args::Cmd::Delete) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
folder::handlers::delete(&mut printer, backend.as_mut(), &folder).await?; folder::handlers::delete(&mut printer, &backend, &folder).await?;
return Ok(()); return Ok(());
} }
_ => (), _ => (),
@ -187,13 +176,13 @@ async fn main() -> Result<()> {
match email::args::matches(&m)? { match email::args::matches(&m)? {
Some(email::args::Cmd::Attachments(ids)) => { Some(email::args::Cmd::Attachments(ids)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::attachments( email::handlers::attachments(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
ids, ids,
) )
@ -202,43 +191,33 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::Copy(ids, to_folder)) => { Some(email::args::Cmd::Copy(ids, to_folder)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::copy( email::handlers::copy(&mut printer, &id_mapper, &backend, &folder, to_folder, ids)
&mut printer, .await?;
&id_mapper,
backend.as_mut(),
&folder,
to_folder,
ids,
)
.await?;
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Delete(ids)) => { Some(email::args::Cmd::Delete(ids)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::delete(&mut printer, &id_mapper, backend.as_mut(), &folder, ids) email::handlers::delete(&mut printer, &id_mapper, &backend, &folder, ids).await?;
.await?;
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Forward(id, headers, body)) => { Some(email::args::Cmd::Forward(id, headers, body)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let mut sender = sender_builder.build().await?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?;
email::handlers::forward( email::handlers::forward(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
sender.as_mut(),
&folder, &folder,
id, id,
headers, headers,
@ -250,14 +229,14 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::List(max_width, page_size, page)) => { Some(email::args::Cmd::List(max_width, page_size, page)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::list( email::handlers::list(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
max_width, max_width,
page_size, page_size,
@ -269,31 +248,24 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::Move(ids, to_folder)) => { Some(email::args::Cmd::Move(ids, to_folder)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::move_( email::handlers::move_(&mut printer, &id_mapper, &backend, &folder, to_folder, ids)
&mut printer, .await?;
&id_mapper,
backend.as_mut(),
&folder,
to_folder,
ids,
)
.await?;
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Read(ids, text_mime, raw, headers)) => { Some(email::args::Cmd::Read(ids, text_mime, raw, headers)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::read( email::handlers::read(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
ids, ids,
text_mime, text_mime,
@ -306,16 +278,14 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::Reply(id, all, headers, body)) => { Some(email::args::Cmd::Reply(id, all, headers, body)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let mut sender = sender_builder.build().await?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?;
email::handlers::reply( email::handlers::reply(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
sender.as_mut(),
&folder, &folder,
id, id,
all, all,
@ -328,30 +298,23 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::Save(raw_email)) => { Some(email::args::Cmd::Save(raw_email)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::save( email::handlers::save(&mut printer, &id_mapper, &backend, &folder, raw_email).await?;
&mut printer,
&id_mapper,
backend.as_mut(),
&folder,
raw_email,
)
.await?;
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Search(query, max_width, page_size, page)) => { Some(email::args::Cmd::Search(query, max_width, page_size, page)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::search( email::handlers::search(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
query, query,
max_width, max_width,
@ -364,14 +327,14 @@ async fn main() -> Result<()> {
} }
Some(email::args::Cmd::Sort(criteria, query, max_width, page_size, page)) => { Some(email::args::Cmd::Sort(criteria, query, max_width, page_size, page)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
email::handlers::sort( email::handlers::sort(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
criteria, criteria,
query, query,
@ -384,68 +347,39 @@ async fn main() -> Result<()> {
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Send(raw_email)) => { Some(email::args::Cmd::Send(raw_email)) => {
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
let mut sender = sender_builder.build().await?; email::handlers::send(&account_config, &mut printer, &backend, raw_email).await?;
email::handlers::send(
&account_config,
&mut printer,
backend.as_mut(),
sender.as_mut(),
raw_email,
)
.await?;
return Ok(()); return Ok(());
} }
Some(email::args::Cmd::Flag(m)) => match m { Some(email::args::Cmd::Flag(m)) => match m {
Some(flag::args::Cmd::Set(ids, ref flags)) => { Some(flag::args::Cmd::Set(ids, ref flags)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
flag::handlers::set( flag::handlers::set(&mut printer, &id_mapper, &backend, &folder, ids, flags)
&mut printer, .await?;
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
)
.await?;
return Ok(()); return Ok(());
} }
Some(flag::args::Cmd::Add(ids, ref flags)) => { Some(flag::args::Cmd::Add(ids, ref flags)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
flag::handlers::add( flag::handlers::add(&mut printer, &id_mapper, &backend, &folder, ids, flags)
&mut printer, .await?;
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
)
.await?;
return Ok(()); return Ok(());
} }
Some(flag::args::Cmd::Remove(ids, ref flags)) => { Some(flag::args::Cmd::Remove(ids, ref flags)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
flag::handlers::remove( flag::handlers::remove(&mut printer, &id_mapper, &backend, &folder, ids, flags)
&mut printer, .await?;
&id_mapper,
backend.as_mut(),
&folder,
ids,
flags,
)
.await?;
return Ok(()); return Ok(());
} }
@ -454,14 +388,14 @@ async fn main() -> Result<()> {
Some(email::args::Cmd::Tpl(m)) => match m { Some(email::args::Cmd::Tpl(m)) => match m {
Some(tpl::args::Cmd::Forward(id, headers, body)) => { Some(tpl::args::Cmd::Forward(id, headers, body)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
tpl::handlers::forward( tpl::handlers::forward(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
id, id,
headers, headers,
@ -477,14 +411,14 @@ async fn main() -> Result<()> {
} }
Some(tpl::args::Cmd::Reply(id, all, headers, body)) => { Some(tpl::args::Cmd::Reply(id, all, headers, body)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
tpl::handlers::reply( tpl::handlers::reply(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
id, id,
all, all,
@ -497,14 +431,14 @@ async fn main() -> Result<()> {
} }
Some(tpl::args::Cmd::Save(tpl)) => { Some(tpl::args::Cmd::Save(tpl)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER); let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let id_mapper = IdMapper::new(backend.as_ref(), &account_config, &folder)?; let id_mapper = IdMapper::new(&backend, &account_config, &folder)?;
tpl::handlers::save( tpl::handlers::save(
&account_config, &account_config,
&mut printer, &mut printer,
&id_mapper, &id_mapper,
backend.as_mut(), &backend,
&folder, &folder,
tpl, tpl,
) )
@ -513,33 +447,16 @@ async fn main() -> Result<()> {
return Ok(()); return Ok(());
} }
Some(tpl::args::Cmd::Send(tpl)) => { Some(tpl::args::Cmd::Send(tpl)) => {
let mut backend = backend_builder.clone().into_build().await?; let backend = backend_builder.clone().build().await?;
let mut sender = sender_builder.build().await?; tpl::handlers::send(&account_config, &mut printer, &backend, tpl).await?;
tpl::handlers::send(
&account_config,
&mut printer,
backend.as_mut(),
sender.as_mut(),
tpl,
)
.await?;
return Ok(()); return Ok(());
} }
_ => (), _ => (),
}, },
Some(email::args::Cmd::Write(headers, body)) => { Some(email::args::Cmd::Write(headers, body)) => {
let mut backend = backend_builder.build().await?; let backend = backend_builder.clone().build().await?;
let mut sender = sender_builder.build().await?; email::handlers::write(&account_config, &mut printer, &backend, headers, body).await?;
email::handlers::write(
&account_config,
&mut printer,
backend.as_mut(),
sender.as_mut(),
headers,
body,
)
.await?;
return Ok(()); return Ok(());
} }

View file

@ -1,9 +1,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use email::{ use email::{
account::AccountConfig, account::AccountConfig,
backend::Backend,
email::{local_draft_path, remove_local_draft, Flag, Flags}, email::{local_draft_path, remove_local_draft, Flag, Flags},
sender::Sender,
}; };
use log::debug; use log::debug;
use mml::MmlCompilerBuilder; use mml::MmlCompilerBuilder;
@ -11,6 +9,7 @@ use process::Cmd;
use std::{env, fs}; use std::{env, fs};
use crate::{ use crate::{
backend::Backend,
printer::Printer, printer::Printer,
ui::choice::{self, PostEditChoice, PreEditChoice}, ui::choice::{self, PostEditChoice, PreEditChoice},
}; };
@ -45,8 +44,7 @@ pub async fn open_with_local_draft() -> Result<String> {
pub async fn edit_tpl_with_editor<P: Printer>( pub async fn edit_tpl_with_editor<P: Printer>(
config: &AccountConfig, config: &AccountConfig,
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &Backend,
sender: &mut dyn Sender,
mut tpl: String, mut tpl: String,
) -> Result<()> { ) -> Result<()> {
let draft = local_draft_path(); let draft = local_draft_path();
@ -86,13 +84,13 @@ pub async fn edit_tpl_with_editor<P: Printer>(
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
sender.send(&email).await?; backend.send_raw_message(&email).await?;
if config.email_sending_save_copy { if config.email_sending_save_copy.unwrap_or_default() {
let sent_folder = config.sent_folder_alias()?; let sent_folder = config.sent_folder_alias()?;
printer.print_log(format!("Adding email to the {} folder…", sent_folder))?; printer.print_log(format!("Adding email to the {} folder…", sent_folder))?;
backend backend
.add_email(&sent_folder, &email, &Flags::from_iter([Flag::Seen])) .add_raw_message_with_flag(&sent_folder, &email, Flag::Seen)
.await?; .await?;
} }
@ -117,7 +115,7 @@ pub async fn edit_tpl_with_editor<P: Printer>(
let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?; let email = compiler.build(tpl.as_str())?.compile().await?.into_vec()?;
backend backend
.add_email( .add_raw_message_with_flags(
"drafts", "drafts",
&email, &email,
&Flags::from_iter([Flag::Seen, Flag::Draft]), &Flags::from_iter([Flag::Seen, Flag::Draft]),