pub mod config; #[cfg(feature = "wizard")] pub(crate) mod wizard; use async_trait::async_trait; use color_eyre::Result; use std::{fmt::Display, ops::Deref, sync::Arc}; use tracing::instrument; #[cfg(feature = "imap")] use email::imap::{ImapContextBuilder, ImapContextSync}; #[cfg(any(feature = "account-sync", feature = "maildir"))] use email::maildir::{MaildirContextBuilder, MaildirContextSync}; #[cfg(feature = "notmuch")] use email::notmuch::{NotmuchContextBuilder, NotmuchContextSync}; #[cfg(feature = "sendmail")] use email::sendmail::{SendmailContextBuilder, SendmailContextSync}; #[cfg(feature = "smtp")] use email::smtp::{SmtpContextBuilder, SmtpContextSync}; use email::{ account::config::AccountConfig, backend::{ feature::BackendFeature, macros::BackendContext, mapper::SomeBackendContextBuilderMapper, }, envelope::{ get::GetEnvelope, list::{ListEnvelopes, ListEnvelopesOptions}, thread::ThreadEnvelopes, Id, SingleId, }, flag::{add::AddFlags, remove::RemoveFlags, set::SetFlags, Flag, Flags}, folder::{ add::AddFolder, delete::DeleteFolder, expunge::ExpungeFolder, list::ListFolders, purge::PurgeFolder, }, message::{ add::AddMessage, copy::CopyMessages, delete::DeleteMessages, get::GetMessages, peek::PeekMessages, r#move::MoveMessages, send::{SendMessage, SendMessageThenSaveCopy}, Messages, }, AnyResult, }; use serde::{Deserialize, Serialize}; use crate::{ account::config::TomlAccountConfig, cache::IdMapper, envelope::{Envelopes, ThreadedEnvelopes}, }; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum BackendKind { None, #[cfg(feature = "imap")] Imap, #[cfg(feature = "maildir")] Maildir, #[cfg(feature = "notmuch")] Notmuch, #[cfg(feature = "smtp")] Smtp, #[cfg(feature = "sendmail")] Sendmail, } impl Display for BackendKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Self::None => "None", #[cfg(feature = "imap")] Self::Imap => "IMAP", #[cfg(feature = "maildir")] Self::Maildir => "Maildir", #[cfg(feature = "notmuch")] Self::Notmuch => "Notmuch", #[cfg(feature = "smtp")] Self::Smtp => "SMTP", #[cfg(feature = "sendmail")] Self::Sendmail => "Sendmail", } ) } } #[derive(Clone, Default)] pub struct BackendContextBuilder { pub toml_account_config: Arc, pub account_config: Arc, #[cfg(feature = "imap")] pub imap: Option, #[cfg(feature = "maildir")] pub maildir: Option, #[cfg(feature = "notmuch")] pub notmuch: Option, #[cfg(feature = "smtp")] pub smtp: Option, #[cfg(feature = "sendmail")] pub sendmail: Option, } impl BackendContextBuilder { pub async fn new( toml_account_config: Arc, account_config: Arc, kinds: Vec<&BackendKind>, ) -> Result { Ok(Self { toml_account_config: toml_account_config.clone(), account_config: account_config.clone(), #[cfg(feature = "imap")] imap: { let builder = toml_account_config .imap .as_ref() .filter(|_| kinds.contains(&&BackendKind::Imap)) .map(Clone::clone) .map(Arc::new) .map(|imap_config| { ImapContextBuilder::new(account_config.clone(), imap_config) .with_prebuilt_credentials() }); match builder { Some(builder) => Some(builder.await?), None => None, } }, #[cfg(feature = "maildir")] maildir: toml_account_config .maildir .as_ref() .filter(|_| kinds.contains(&&BackendKind::Maildir)) .map(Clone::clone) .map(Arc::new) .map(|mdir_config| MaildirContextBuilder::new(account_config.clone(), mdir_config)), #[cfg(feature = "notmuch")] notmuch: toml_account_config .notmuch .as_ref() .filter(|_| kinds.contains(&&BackendKind::Notmuch)) .map(Clone::clone) .map(Arc::new) .map(|notmuch_config| { NotmuchContextBuilder::new(account_config.clone(), notmuch_config) }), #[cfg(feature = "smtp")] smtp: toml_account_config .smtp .as_ref() .filter(|_| kinds.contains(&&BackendKind::Smtp)) .map(Clone::clone) .map(Arc::new) .map(|smtp_config| SmtpContextBuilder::new(account_config.clone(), smtp_config)), #[cfg(feature = "sendmail")] sendmail: toml_account_config .sendmail .as_ref() .filter(|_| kinds.contains(&&BackendKind::Sendmail)) .map(Clone::clone) .map(Arc::new) .map(|sendmail_config| { SendmailContextBuilder::new(account_config.clone(), sendmail_config) }), }) } } #[async_trait] impl email::backend::context::BackendContextBuilder for BackendContextBuilder { type Context = BackendContext; fn add_folder(&self) -> Option> { match self.toml_account_config.add_folder_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.add_folder_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.add_folder_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.add_folder_with_some(&self.notmuch), _ => None, } } fn list_folders(&self) -> Option> { match self.toml_account_config.list_folders_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.list_folders_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.list_folders_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.list_folders_with_some(&self.notmuch), _ => None, } } fn expunge_folder(&self) -> Option> { match self.toml_account_config.expunge_folder_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.expunge_folder_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.expunge_folder_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.expunge_folder_with_some(&self.notmuch), _ => None, } } fn purge_folder(&self) -> Option> { match self.toml_account_config.purge_folder_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.purge_folder_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.purge_folder_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.purge_folder_with_some(&self.notmuch), _ => None, } } fn delete_folder(&self) -> Option> { match self.toml_account_config.delete_folder_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.delete_folder_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.delete_folder_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.delete_folder_with_some(&self.notmuch), _ => None, } } fn get_envelope(&self) -> Option> { match self.toml_account_config.get_envelope_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.get_envelope_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.get_envelope_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.get_envelope_with_some(&self.notmuch), _ => None, } } fn list_envelopes(&self) -> Option> { match self.toml_account_config.list_envelopes_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.list_envelopes_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.list_envelopes_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.list_envelopes_with_some(&self.notmuch), _ => None, } } fn thread_envelopes(&self) -> Option> { match self.toml_account_config.thread_envelopes_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.thread_envelopes_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.thread_envelopes_with_some(&self.notmuch), _ => None, } } fn add_flags(&self) -> Option> { match self.toml_account_config.add_flags_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.add_flags_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.add_flags_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.add_flags_with_some(&self.notmuch), _ => None, } } fn set_flags(&self) -> Option> { match self.toml_account_config.set_flags_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.set_flags_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.set_flags_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.set_flags_with_some(&self.notmuch), _ => None, } } fn remove_flags(&self) -> Option> { match self.toml_account_config.remove_flags_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.remove_flags_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.remove_flags_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.remove_flags_with_some(&self.notmuch), _ => None, } } fn add_message(&self) -> Option> { match self.toml_account_config.add_message_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.add_message_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.add_message_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.add_message_with_some(&self.notmuch), _ => None, } } fn send_message(&self) -> Option> { match self.toml_account_config.send_message_kind() { #[cfg(feature = "smtp")] Some(BackendKind::Smtp) => self.send_message_with_some(&self.smtp), #[cfg(feature = "sendmail")] Some(BackendKind::Sendmail) => self.send_message_with_some(&self.sendmail), _ => None, } } fn peek_messages(&self) -> Option> { match self.toml_account_config.peek_messages_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.peek_messages_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.peek_messages_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.peek_messages_with_some(&self.notmuch), _ => None, } } fn get_messages(&self) -> Option> { match self.toml_account_config.get_messages_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.get_messages_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.get_messages_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.get_messages_with_some(&self.notmuch), _ => None, } } fn copy_messages(&self) -> Option> { match self.toml_account_config.copy_messages_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.copy_messages_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.copy_messages_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.copy_messages_with_some(&self.notmuch), _ => None, } } fn move_messages(&self) -> Option> { match self.toml_account_config.move_messages_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.move_messages_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.move_messages_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.move_messages_with_some(&self.notmuch), _ => None, } } fn delete_messages(&self) -> Option> { match self.toml_account_config.delete_messages_kind() { #[cfg(feature = "imap")] Some(BackendKind::Imap) => self.delete_messages_with_some(&self.imap), #[cfg(feature = "maildir")] Some(BackendKind::Maildir) => self.delete_messages_with_some(&self.maildir), #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => self.delete_messages_with_some(&self.notmuch), _ => None, } } async fn build(self) -> AnyResult { let mut ctx = BackendContext::default(); #[cfg(feature = "imap")] if let Some(imap) = self.imap { ctx.imap = Some(imap.build().await?); } #[cfg(feature = "maildir")] if let Some(maildir) = self.maildir { ctx.maildir = Some(maildir.build().await?); } #[cfg(feature = "notmuch")] if let Some(notmuch) = self.notmuch { ctx.notmuch = Some(notmuch.build().await?); } #[cfg(feature = "smtp")] if let Some(smtp) = self.smtp { ctx.smtp = Some(smtp.build().await?); } #[cfg(feature = "sendmail")] if let Some(sendmail) = self.sendmail { ctx.sendmail = Some(sendmail.build().await?); } Ok(ctx) } } #[derive(BackendContext, Default)] pub struct BackendContext { #[cfg(feature = "imap")] pub imap: Option, #[cfg(feature = "maildir")] pub maildir: Option, #[cfg(feature = "notmuch")] pub notmuch: Option, #[cfg(feature = "smtp")] pub smtp: Option, #[cfg(feature = "sendmail")] pub sendmail: Option, } #[cfg(feature = "imap")] impl AsRef> for BackendContext { fn as_ref(&self) -> &Option { &self.imap } } #[cfg(feature = "maildir")] impl AsRef> for BackendContext { fn as_ref(&self) -> &Option { &self.maildir } } #[cfg(feature = "notmuch")] impl AsRef> for BackendContext { fn as_ref(&self) -> &Option { &self.notmuch } } #[cfg(feature = "smtp")] impl AsRef> for BackendContext { fn as_ref(&self) -> &Option { &self.smtp } } #[cfg(feature = "sendmail")] impl AsRef> for BackendContext { fn as_ref(&self) -> &Option { &self.sendmail } } pub struct Backend { pub toml_account_config: Arc, pub backend: email::backend::Backend, } impl Backend { pub async fn new( toml_account_config: Arc, account_config: Arc, backend_kinds: impl IntoIterator, with_features: impl Fn(&mut email::backend::BackendBuilder), ) -> Result { let backend_kinds = backend_kinds.into_iter().collect(); let backend_ctx_builder = BackendContextBuilder::new( toml_account_config.clone(), account_config.clone(), backend_kinds, ) .await?; let mut backend_builder = email::backend::BackendBuilder::new(account_config.clone(), backend_ctx_builder) .without_features(); with_features(&mut backend_builder); Ok(Self { toml_account_config: toml_account_config.clone(), backend: backend_builder.build().await?, }) } #[instrument(skip(self))] fn build_id_mapper( &self, folder: &str, backend_kind: Option<&BackendKind>, ) -> Result { #[allow(unused_mut)] #[cfg(feature = "maildir")] if let Some(BackendKind::Maildir) = backend_kind { if let Some(_) = &self.toml_account_config.maildir { return Ok(IdMapper::new(&self.backend.account_config, folder)?); } } #[cfg(feature = "notmuch")] if let Some(BackendKind::Notmuch) = backend_kind { if let Some(_) = &self.toml_account_config.notmuch { return Ok(IdMapper::new(&self.backend.account_config, folder)?); } } Ok(IdMapper::Dummy) } pub async fn list_envelopes( &self, folder: &str, opts: ListEnvelopesOptions, ) -> Result { let backend_kind = self.toml_account_config.list_envelopes_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let envelopes = self.backend.list_envelopes(folder, opts).await?; let envelopes = Envelopes::try_from_backend(&self.backend.account_config, &id_mapper, envelopes)?; Ok(envelopes) } pub async fn thread_envelopes( &self, folder: &str, opts: ListEnvelopesOptions, ) -> Result { let backend_kind = self.toml_account_config.thread_envelopes_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let envelopes = self.backend.thread_envelopes(folder, opts).await?; let envelopes = ThreadedEnvelopes::try_from_backend(&id_mapper, envelopes)?; Ok(envelopes) } pub async fn thread_envelope( &self, folder: &str, id: usize, opts: ListEnvelopesOptions, ) -> Result { let backend_kind = self.toml_account_config.thread_envelopes_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let id = id_mapper.get_id(id)?; let envelopes = self .backend .thread_envelope(folder, SingleId::from(id), opts) .await?; let envelopes = ThreadedEnvelopes::try_from_backend(&id_mapper, envelopes)?; Ok(envelopes) } pub async fn add_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { let backend_kind = self.toml_account_config.add_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.add_flags(folder, &ids, flags).await?; Ok(()) } pub async fn add_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { let backend_kind = self.toml_account_config.add_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.add_flag(folder, &ids, flag).await?; Ok(()) } pub async fn set_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { let backend_kind = self.toml_account_config.set_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.set_flags(folder, &ids, flags).await?; Ok(()) } pub async fn set_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { let backend_kind = self.toml_account_config.set_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.set_flag(folder, &ids, flag).await?; Ok(()) } pub async fn remove_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> { let backend_kind = self.toml_account_config.remove_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.remove_flags(folder, &ids, flags).await?; Ok(()) } pub async fn remove_flag(&self, folder: &str, ids: &[usize], flag: Flag) -> Result<()> { let backend_kind = self.toml_account_config.remove_flags_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.remove_flag(folder, &ids, flag).await?; Ok(()) } pub async fn add_message(&self, folder: &str, email: &[u8]) -> Result { let backend_kind = self.toml_account_config.add_message_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let id = self.backend.add_message(folder, email).await?; id_mapper.create_alias(&*id)?; Ok(id) } pub async fn add_message_with_flags( &self, folder: &str, email: &[u8], flags: &Flags, ) -> Result { let backend_kind = self.toml_account_config.add_message_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let id = self .backend .add_message_with_flags(folder, email, flags) .await?; id_mapper.create_alias(&*id)?; Ok(id) } pub async fn peek_messages(&self, folder: &str, ids: &[usize]) -> Result { let backend_kind = self.toml_account_config.get_messages_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); let msgs = self.backend.peek_messages(folder, &ids).await?; Ok(msgs) } pub async fn get_messages(&self, folder: &str, ids: &[usize]) -> Result { let backend_kind = self.toml_account_config.get_messages_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); let msgs = self.backend.get_messages(folder, &ids).await?; Ok(msgs) } pub async fn copy_messages( &self, from_folder: &str, to_folder: &str, ids: &[usize], ) -> Result<()> { let backend_kind = self.toml_account_config.move_messages_kind(); let id_mapper = self.build_id_mapper(from_folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend .copy_messages(from_folder, to_folder, &ids) .await?; Ok(()) } pub async fn move_messages( &self, from_folder: &str, to_folder: &str, ids: &[usize], ) -> Result<()> { let backend_kind = self.toml_account_config.move_messages_kind(); let id_mapper = self.build_id_mapper(from_folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend .move_messages(from_folder, to_folder, &ids) .await?; Ok(()) } pub async fn delete_messages(&self, folder: &str, ids: &[usize]) -> Result<()> { let backend_kind = self.toml_account_config.delete_messages_kind(); let id_mapper = self.build_id_mapper(folder, backend_kind)?; let ids = Id::multiple(id_mapper.get_ids(ids)?); self.backend.delete_messages(folder, &ids).await?; Ok(()) } pub async fn send_message_then_save_copy(&self, msg: &[u8]) -> Result<()> { self.backend.send_message_then_save_copy(msg).await?; Ok(()) } } impl Deref for Backend { type Target = email::backend::Backend; fn deref(&self) -> &Self::Target { &self.backend } }