1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
//! Module dedicated to backend management.
//!
//! The core concept of this module is the [`Backend`] trait, which is
//! an abstraction over emails manipulation.
//!
//! Then you have the [`BackendConfig`] which represents the
//! backend-specific configuration, mostly used by the [account
//! configuration](crate::AccountConfig).
mod config;
#[cfg(feature = "imap-backend")]
pub mod imap;
pub mod maildir;
#[cfg(feature = "notmuch-backend")]
pub mod notmuch;
use async_trait::async_trait;
use log::error;
use std::any::Any;
use thiserror::Error;
use crate::{
account::AccountConfig,
email::{Envelope, Envelopes, Flag, Flags, Messages},
folder::Folders,
Result,
};
#[doc(inline)]
pub use self::config::BackendConfig;
#[cfg(feature = "imap-backend")]
#[doc(inline)]
pub use self::imap::{ImapAuthConfig, ImapBackend, ImapConfig};
#[doc(inline)]
pub use self::maildir::{MaildirBackend, MaildirBackendBuilder, MaildirConfig};
#[cfg(feature = "notmuch-backend")]
#[doc(inline)]
pub use self::notmuch::{NotmuchBackend, NotmuchBackendBuilder, NotmuchConfig};
/// Errors related to backend.
#[derive(Debug, Error)]
pub enum Error {
#[error("cannot build undefined backend")]
BuildUndefinedBackendError,
}
/// The backend abstraction.
///
/// The backend trait abstracts every action needed to manipulate
/// emails.
#[async_trait]
pub trait Backend: Send {
/// Returns the name of the backend.
fn name(&self) -> String;
/// Creates the given folder.
async fn add_folder(&mut self, folder: &str) -> Result<()>;
/// Lists all available folders.
async fn list_folders(&mut self) -> Result<Folders>;
/// Expunges the given folder.
///
/// The concept is similar to the IMAP expunge: it definitely
/// deletes emails that have the Deleted flag.
async fn expunge_folder(&mut self, folder: &str) -> Result<()>;
/// Purges the given folder.
///
/// Manipulate with caution: all emails contained in the given
/// folder are definitely deleted.
async fn purge_folder(&mut self, folder: &str) -> Result<()>;
/// Definitely deletes the given folder.
///
/// Manipulate with caution: all emails contained in the given
/// folder are also definitely deleted.
async fn delete_folder(&mut self, folder: &str) -> Result<()>;
/// Gets the envelope from the given folder matching the given id.
async fn get_envelope(&mut self, folder: &str, id: &str) -> Result<Envelope>;
/// Lists all available envelopes from the given folder matching
/// the given pagination.
async fn list_envelopes(
&mut self,
folder: &str,
page_size: usize,
page: usize,
) -> Result<Envelopes>;
/// Sorts and filters envelopes from the given folder matching the
/// given query, sort and pagination.
// TODO: we should avoid using strings for query and sort, instead
// it would be better to have a shared API.
// See https://todo.sr.ht/~soywod/pimalaya/39.
async fn search_envelopes(
&mut self,
folder: &str,
query: &str,
sort: &str,
page_size: usize,
page: usize,
) -> Result<Envelopes>;
/// Adds the given raw email with the given flags to the given
/// folder.
async fn add_email(&mut self, folder: &str, email: &[u8], flags: &Flags) -> Result<String>;
/// Previews emails from the given folder matching the given ids.
///
/// Same as `get_emails`, except that it just "previews": the Seen
/// flag is not applied to the corresponding envelopes.
async fn preview_emails(&mut self, folder: &str, ids: Vec<&str>) -> Result<Messages>;
/// Gets emails from the given folder matching the given ids.
async fn get_emails(&mut self, folder: &str, ids: Vec<&str>) -> Result<Messages>;
/// Copies emails from the given folder to the given folder
/// matching the given ids.
async fn copy_emails(
&mut self,
from_folder: &str,
to_folder: &str,
ids: Vec<&str>,
) -> Result<()>;
/// Moves emails from the given folder to the given folder
/// matching the given ids.
async fn move_emails(
&mut self,
from_folder: &str,
to_folder: &str,
ids: Vec<&str>,
) -> Result<()>;
/// Deletes emails from the given folder matching the given ids.
///
/// In fact the matching emails are not deleted, they are moved to
/// the trash folder. If the given folder IS the trash folder,
/// then it adds the Deleted flag instead. Matching emails will be
/// definitely deleted after calling `expunge_folder`.
async fn delete_emails(&mut self, folder: &str, ids: Vec<&str>) -> Result<()>;
/// Adds the given flags to envelopes matching the given ids from
/// the given folder.
async fn add_flags(&mut self, folder: &str, ids: Vec<&str>, flags: &Flags) -> Result<()>;
/// Replaces envelopes flags matching the given ids from the given
/// folder.
async fn set_flags(&mut self, folder: &str, ids: Vec<&str>, flags: &Flags) -> Result<()>;
/// Removes the given flags to envelopes matching the given ids
/// from the given folder.
async fn remove_flags(&mut self, folder: &str, ids: Vec<&str>, flags: &Flags) -> Result<()>;
/// Alias for adding the Deleted flag to the matching envelopes.
async fn mark_emails_as_deleted(&mut self, folder: &str, ids: Vec<&str>) -> Result<()> {
self.add_flags(folder, ids, &Flags::from_iter([Flag::Deleted]))
.await
}
/// Cleans up sessions, clients, cache etc.
fn close(&mut self) -> Result<()> {
Ok(())
}
fn as_any(&self) -> &dyn Any;
}
/// The backend builder.
///
/// This builder helps you to build a `Box<dyn Backend>`. The type of
/// backend depends on the given account configuration.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BackendBuilder {
account_config: AccountConfig,
default_credentials: Option<String>,
disable_cache: bool,
}
impl BackendBuilder {
/// Creates a new builder with default value.
pub fn new(account_config: AccountConfig) -> Self {
Self {
account_config,
..Default::default()
}
}
/// Disable cache setter.
pub fn disable_cache(&mut self, disable_cache: bool) {
self.disable_cache = disable_cache;
}
/// Disable cache setter following the builder pattern.
pub fn with_cache_disabled(mut self, disable_cache: bool) -> Self {
self.disable_cache = disable_cache;
self
}
/// Default credentials setter following the builder pattern.
pub async fn with_default_credentials(mut self) -> Result<Self> {
self.default_credentials = match &self.account_config.backend {
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(imap_config) if !self.account_config.sync || self.disable_cache => {
Some(imap_config.build_credentials().await?)
}
_ => None,
};
Ok(self)
}
/// Builds a [Backend] by cloning self options.
pub async fn build(&self) -> Result<Box<dyn Backend>> {
match &self.account_config.backend {
BackendConfig::None => Ok(Err(Error::BuildUndefinedBackendError)?),
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(imap_config) if !self.account_config.sync || self.disable_cache => {
Ok(Box::new(
ImapBackend::new(
self.account_config.clone(),
imap_config.clone(),
self.default_credentials.clone(),
)
.await?,
))
}
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(_) => {
let root_dir = self.account_config.sync_dir()?;
Ok(Box::new(MaildirBackend::new(
self.account_config.clone(),
MaildirConfig { root_dir },
)?))
}
BackendConfig::Maildir(mdir_config) => Ok(Box::new(MaildirBackend::new(
self.account_config.clone(),
mdir_config.clone(),
)?)),
#[cfg(feature = "notmuch-backend")]
BackendConfig::Notmuch(notmuch_config) => Ok(Box::new(NotmuchBackend::new(
self.account_config.clone(),
notmuch_config.clone(),
)?)),
}
}
/// Builds a [Backend] by moving self options.
pub async fn into_build(self) -> Result<Box<dyn Backend>> {
match self.account_config.backend.clone() {
BackendConfig::None => Ok(Err(Error::BuildUndefinedBackendError)?),
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(imap_config) if !self.account_config.sync || self.disable_cache => {
Ok(Box::new(
ImapBackend::new(self.account_config, imap_config, self.default_credentials)
.await?,
))
}
#[cfg(feature = "imap-backend")]
BackendConfig::Imap(_) => {
let root_dir = self.account_config.sync_dir()?;
Ok(Box::new(MaildirBackend::new(
self.account_config,
MaildirConfig { root_dir },
)?))
}
BackendConfig::Maildir(mdir_config) => Ok(Box::new(MaildirBackend::new(
self.account_config,
mdir_config,
)?)),
#[cfg(feature = "notmuch-backend")]
BackendConfig::Notmuch(notmuch_config) => Ok(Box::new(NotmuchBackend::new(
self.account_config,
notmuch_config,
)?)),
}
}
}