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, email::{ envelope::list::{imap::ListEnvelopesImap, maildir::ListEnvelopesMaildir}, message::send_raw::{sendmail::SendRawMessageSendmail, smtp::SendRawMessageSmtp}, }, folder::{ add::{imap::AddFolderImap, maildir::AddFolderMaildir}, delete::{imap::DeleteFolderImap, maildir::DeleteFolderMaildir}, expunge::{imap::ExpungeFolderImap, maildir::ExpungeFolderMaildir}, list::{imap::ListFoldersImap, maildir::ListFoldersMaildir}, purge::imap::PurgeFolderImap, }, maildir::{MaildirSessionBuilder, MaildirSessionSync}, sendmail::SendmailContext, }; use serde::{Deserialize, Serialize}; use crate::config::DeserializedConfig; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum BackendKind { Maildir, #[cfg(feature = "imap-backend")] Imap, #[cfg(feature = "notmuch-backend")] Notmuch, #[cfg(feature = "smtp-sender")] Smtp, Sendmail, } #[derive(Clone, Default)] pub struct BackendContextBuilder { #[cfg(feature = "imap-backend")] imap: Option, maildir: Option, #[cfg(feature = "smtp-sender")] smtp: Option, sendmail: Option, } #[async_trait] impl email::backend::BackendContextBuilder for BackendContextBuilder { type Context = BackendContext; async fn build(self) -> Result { 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, pub maildir: Option, #[cfg(feature = "smtp-sender")] pub smtp: Option, pub sendmail: Option, } pub struct BackendBuilder(pub email::backend::BackendBuilder); impl BackendBuilder { pub async fn new(config: DeserializedConfig, account_name: Option<&str>) -> Result { let (account_name, mut 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}")), }?; #[cfg(feature = "imap-backend")] if let Some(imap_config) = deserialized_account_config.imap.as_mut() { imap_config .auth .replace_undefined_keyring_entries(&account_name); } #[cfg(feature = "smtp-sender")] if let Some(smtp_config) = deserialized_account_config.smtp.as_mut() { smtp_config .auth .replace_undefined_keyring_entries(&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 { 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 = "notmuch-backend")] notmuch: deserialized_account_config .notmuch .as_ref() .map(|notmuch_config| { NotmuchSessionBuilder::new(account_config.clone(), notmuch_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()) }), }; let mut backend_builder = email::backend::BackendBuilder::new(account_config.clone(), backend_ctx_builder); let add_folder = deserialized_account_config .folder .as_ref() .and_then(|folder| folder.add.as_ref()) .and_then(|add| add.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match add_folder { Some(BackendKind::Maildir) => { backend_builder = backend_builder .with_add_folder(|ctx| ctx.maildir.as_ref().and_then(AddFolderMaildir::new)); } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_add_folder(|ctx| ctx.imap.as_ref().and_then(AddFolderImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder .with_add_folder(|ctx| ctx.notmuch.as_ref().and_then(AddFolderNotmuch::new)); } _ => (), } let list_folders = deserialized_account_config .folder .as_ref() .and_then(|folder| folder.list.as_ref()) .and_then(|list| list.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match list_folders { Some(BackendKind::Maildir) => { backend_builder = backend_builder.with_list_folders(|ctx| { ctx.maildir.as_ref().and_then(ListFoldersMaildir::new) }); } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_list_folders(|ctx| ctx.imap.as_ref().and_then(ListFoldersImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder.with_list_folders(|ctx| { ctx.notmuch.as_ref().and_then(ListFoldersNotmuch::new) }); } _ => (), } let expunge_folder = deserialized_account_config .folder .as_ref() .and_then(|folder| folder.expunge.as_ref()) .and_then(|expunge| expunge.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match expunge_folder { Some(BackendKind::Maildir) => { backend_builder = backend_builder.with_expunge_folder(|ctx| { ctx.maildir.as_ref().and_then(ExpungeFolderMaildir::new) }); } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_expunge_folder(|ctx| ctx.imap.as_ref().and_then(ExpungeFolderImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder.with_expunge_folder(|ctx| { ctx.notmuch.as_ref().and_then(ExpungeFolderNotmuch::new) }); } _ => (), } let purge_folder = deserialized_account_config .folder .as_ref() .and_then(|folder| folder.purge.as_ref()) .and_then(|purge| purge.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match purge_folder { // TODO // Some(BackendKind::Maildir) => { // backend_builder = backend_builder // .with_purge_folder(|ctx| ctx.maildir.as_ref().and_then(PurgeFolderMaildir::new)); // } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_purge_folder(|ctx| ctx.imap.as_ref().and_then(PurgeFolderImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder.with_purge_folder(|ctx| { ctx.notmuch.as_ref().and_then(PurgeFolderNotmuch::new) }); } _ => (), } let delete_folder = deserialized_account_config .folder .as_ref() .and_then(|folder| folder.delete.as_ref()) .and_then(|delete| delete.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match delete_folder { Some(BackendKind::Maildir) => { backend_builder = backend_builder.with_delete_folder(|ctx| { ctx.maildir.as_ref().and_then(DeleteFolderMaildir::new) }); } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_delete_folder(|ctx| ctx.imap.as_ref().and_then(DeleteFolderImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder.with_delete_folder(|ctx| { ctx.notmuch.as_ref().and_then(DeleteFolderNotmuch::new) }); } _ => (), } let list_envelopes = deserialized_account_config .envelope .as_ref() .and_then(|envelope| envelope.list.as_ref()) .and_then(|send| send.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match list_envelopes { Some(BackendKind::Maildir) => { backend_builder = backend_builder.with_list_envelopes(|ctx| { ctx.maildir.as_ref().and_then(ListEnvelopesMaildir::new) }); } #[cfg(feature = "imap-backend")] Some(BackendKind::Imap) => { backend_builder = backend_builder .with_list_envelopes(|ctx| ctx.imap.as_ref().and_then(ListEnvelopesImap::new)); } #[cfg(feature = "notmuch-backend")] Some(BackendKind::Notmuch) => { backend_builder = backend_builder.with_list_envelopes(|ctx| { ctx.notmuch.as_ref().and_then(ListEnvelopesNotmuch::new) }); } _ => (), } let send_msg = deserialized_account_config .message .as_ref() .and_then(|msg| msg.send.as_ref()) .and_then(|send| send.backend.as_ref()) .or_else(|| deserialized_account_config.backend.as_ref()); match send_msg { #[cfg(feature = "smtp-sender")] Some(BackendKind::Smtp) => { backend_builder = backend_builder.with_send_raw_message(|ctx| { ctx.smtp.as_ref().and_then(SendRawMessageSmtp::new) }); } Some(BackendKind::Sendmail) => { backend_builder = backend_builder.with_send_raw_message(|ctx| { ctx.sendmail.as_ref().and_then(SendRawMessageSendmail::new) }); } _ => (), } Ok(Self(backend_builder)) } } impl Deref for BackendBuilder { type Target = email::backend::BackendBuilder; fn deref(&self) -> &Self::Target { &self.0 } } pub type Backend = email::backend::Backend;