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]
version = "1.0"
[dependencies.async-trait]
version = "0.1"
[dependencies.atty]
version = "0.2"
@ -88,21 +91,23 @@ version = "0.7.0"
version = "1.16.0"
[dependencies.email-lib]
version = "=0.15.3"
# version = "=0.15.3"
default-features = false
path = "/home/soywod/sourcehut/pimalaya/email"
[dependencies.keyring-lib]
version = "=0.1.0"
[dependencies.oauth-lib]
version = "=0.1.0"
# version = "=0.1.0"
path = "/home/soywod/sourcehut/pimalaya/oauth"
[dependencies.process-lib]
version = "=0.1.0"
[dependencies.mml-lib]
version = "=0.5.0"
default-features = false
# version = "=1.0.1"
path = "/home/soywod/sourcehut/pimalaya/mml"
[dependencies.secret-lib]
version = "=0.1.0"
@ -115,7 +120,8 @@ features = ["derive"]
version = "1.0"
[dependencies.shellexpand-utils]
version = "=0.1.0"
# version = "=0.1.0"
path = "/home/soywod/sourcehut/pimalaya/shellexpand-utils"
[dependencies.termcolor]
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]
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"
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"
smtp-host = "smtp.gmail.com"
smtp-login = "display.name@gmail.local"
smtp-auth = "passwd"
smtp-passwd.cmd = "pass show piana/gmail"
smtp-port = 465
smtp-ssl = true
smtp-starttls = false
imap.host = "imap.gmail.com"
imap.port = 993
imap.login = "example@localhost"
imap.auth = "passwd"
# imap.Some.passwd.cmd = "pass show gmail"
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};
#[cfg(feature = "imap-backend")]
use email::backend::ImapBackend;
use email::account::AccountConfig;
#[cfg(feature = "notmuch-backend")]
use email::backend::NotmuchBackend;
use email::{
account::AccountConfig,
backend::{Backend, MaildirBackend},
};
use log::{debug, trace};
use std::path::{Path, PathBuf};
use crate::backend::Backend;
const ID_MAPPER_DB_FILE_NAME: &str = ".id-mapper.sqlite";
#[derive(Debug)]
@ -39,47 +36,45 @@ impl IdMapper {
db_path
}
pub fn new(
backend: &dyn Backend,
account_config: &AccountConfig,
folder: &str,
) -> Result<Self> {
#[cfg(feature = "imap-backend")]
if backend.as_any().is::<ImapBackend>() {
return Ok(IdMapper::Dummy);
}
pub fn new(backend: &Backend, account_config: &AccountConfig, folder: &str) -> Result<Self> {
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>() {
db_path = Self::find_closest_db_path(backend.path())
}
// let mut db_path = PathBuf::new();
#[cfg(feature = "notmuch-backend")]
if let Some(backend) = backend.as_any().downcast_ref::<NotmuchBackend>() {
db_path = Self::find_closest_db_path(backend.path())
}
// if let Some(backend) = backend.as_any().downcast_ref::<MaildirBackend>() {
// db_path = Self::find_closest_db_path(backend.path())
// }
let folder = account_config.get_folder_alias(folder)?;
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:?}…");
// #[cfg(feature = "notmuch-backend")]
// if let Some(backend) = backend.as_any().downcast_ref::<NotmuchBackend>() {
// db_path = Self::find_closest_db_path(backend.path())
// }
let conn = rusqlite::Connection::open(&db_path)
.with_context(|| format!("cannot open id mapper database at {db_path:?}"))?;
// let folder = account_config.get_folder_alias(folder)?;
// 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!(
"CREATE TABLE IF NOT EXISTS {table} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
internal_id TEXT UNIQUE
)",
);
trace!("create table query: {query:#?}");
// let conn = rusqlite::Connection::open(&db_path)
// .with_context(|| format!("cannot open id mapper database at {db_path:?}"))?;
conn.execute(&query, [])
.context("cannot create id mapper table")?;
// let query = format!(
// "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>

View file

@ -6,12 +6,8 @@
use anyhow::{anyhow, Context, Result};
use dialoguer::Confirm;
use dirs::{config_dir, home_dir};
use email::{
account::AccountConfig,
email::{EmailHooks, EmailTextPlainFormat},
};
use email::email::{EmailHooks, EmailTextPlainFormat};
use log::{debug, trace};
use process::Cmd;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs, path::PathBuf, process::exit};
use toml;
@ -39,44 +35,12 @@ pub struct DeserializedConfig {
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 = "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>,
#[serde(default, with = "OptionEmailTextPlainFormatDef")]
pub email_reading_format: Option<EmailTextPlainFormat>,
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>,
#[serde(
default,
with = "EmailHooksDef",
skip_serializing_if = "EmailHooks::is_empty"
)]
pub email_hooks: EmailHooks,
#[serde(default, with = "OptionEmailHooksDef")]
pub email_hooks: Option<EmailHooks>,
#[serde(flatten)]
pub accounts: HashMap<String, DeserializedAccountConfig>,
@ -134,28 +98,6 @@ impl DeserializedConfig {
.or_else(|| home_dir().map(|p| p.join(".himalayarc")))
.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)]

View file

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

View file

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

View file

@ -6,29 +6,27 @@
#[cfg(feature = "pgp")]
use email::account::PgpConfig;
#[cfg(feature = "imap-backend")]
use email::backend::ImapAuthConfig;
use email::imap::ImapConfig;
#[cfg(feature = "smtp-sender")]
use email::sender::SmtpAuthConfig;
use email::smtp::SmtpConfig;
use email::{
account::AccountConfig,
backend::BackendConfig,
email::{EmailHooks, EmailTextPlainFormat},
folder::sync::FolderSyncStrategy,
sender::SenderConfig,
maildir::MaildirConfig,
sendmail::SendmailConfig,
};
use process::Cmd;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
use crate::config::{prelude::*, DeserializedConfig};
use crate::{backend::BackendKind, config::prelude::*};
/// Represents all existing kind of account config.
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(tag = "backend", rename_all = "kebab-case")]
pub struct DeserializedAccountConfig {
pub email: String,
pub default: Option<bool>,
pub email: String,
pub display_name: Option<String>,
pub signature_delim: Option<String>,
pub signature: Option<String>,
@ -41,192 +39,39 @@ pub struct DeserializedAccountConfig {
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 = "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>,
#[serde(default, with = "OptionEmailTextPlainFormatDef")]
pub email_reading_format: Option<EmailTextPlainFormat>,
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>,
#[serde(
default,
with = "EmailHooksDef",
skip_serializing_if = "EmailHooks::is_empty"
)]
pub email_hooks: EmailHooks,
#[serde(default, with = "OptionEmailHooksDef")]
pub email_hooks: Option<EmailHooks>,
pub sync: Option<bool>,
pub sync_dir: Option<PathBuf>,
#[serde(
default,
with = "FolderSyncStrategyDef",
skip_serializing_if = "FolderSyncStrategy::is_default"
)]
pub sync_folders_strategy: FolderSyncStrategy,
#[serde(default, with = "OptionFolderSyncStrategyDef")]
pub sync_folders_strategy: Option<FolderSyncStrategy>,
#[serde(flatten, with = "BackendConfigDef")]
pub backend: BackendConfig,
#[serde(flatten, with = "SenderConfigDef")]
pub sender: SenderConfig,
pub backend: BackendKind,
#[cfg(feature = "imap-backend")]
#[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")]
#[serde(default, with = "PgpConfigDef")]
pub pgp: 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(),
}
}
#[serde(default, with = "OptionPgpConfigDef")]
pub pgp: Option<PgpConfig>,
}

View file

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

View file

@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
use dialoguer::Input;
use email_address::EmailAddress;
use crate::{backend, config::wizard::THEME, sender};
use crate::config::wizard::THEME;
use super::DeserializedAccountConfig;
@ -31,9 +31,9 @@ pub(crate) async fn configure() -> Result<Option<(String, DeserializedAccountCon
.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)))
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
pub mod sendmail;
#[cfg(feature = "smtp-sender")]
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 dialoguer::Select;
use email::sender::SenderConfig;
use crate::config::wizard::THEME;

View file

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

View file

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

View file

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

View file

@ -1,9 +1,7 @@
use anyhow::{Context, Result};
use email::{
account::AccountConfig,
backend::Backend,
email::{local_draft_path, remove_local_draft, Flag, Flags},
sender::Sender,
};
use log::debug;
use mml::MmlCompilerBuilder;
@ -11,6 +9,7 @@ use process::Cmd;
use std::{env, fs};
use crate::{
backend::Backend,
printer::Printer,
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>(
config: &AccountConfig,
printer: &mut P,
backend: &mut dyn Backend,
sender: &mut dyn Sender,
backend: &Backend,
mut tpl: String,
) -> Result<()> {
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()?;
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()?;
printer.print_log(format!("Adding email to the {} folder…", sent_folder))?;
backend
.add_email(&sent_folder, &email, &Flags::from_iter([Flag::Seen]))
.add_raw_message_with_flag(&sent_folder, &email, Flag::Seen)
.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()?;
backend
.add_email(
.add_raw_message_with_flags(
"drafts",
&email,
&Flags::from_iter([Flag::Seen, Flag::Draft]),