pub mod config;
mod error;
use std::{ops::Deref, path::PathBuf, sync::Arc};
use async_trait::async_trait;
use maildirs::{Maildir, Maildirs};
use shellexpand_utils::{shellexpand_path, try_shellexpand_path};
use tokio::sync::Mutex;
use tracing::info;
use self::config::MaildirConfig;
#[doc(inline)]
pub use self::error::{Error, Result};
#[cfg(feature = "thread")]
use crate::envelope::thread::{maildir::ThreadMaildirEnvelopes, ThreadEnvelopes};
#[cfg(feature = "watch")]
use crate::envelope::watch::{maildir::WatchMaildirEnvelopes, WatchEnvelopes};
use crate::{
account::config::AccountConfig,
backend::{
context::{BackendContext, BackendContextBuilder},
feature::{BackendFeature, CheckUp},
},
envelope::{
get::{maildir::GetMaildirEnvelope, GetEnvelope},
list::{maildir::ListMaildirEnvelopes, ListEnvelopes},
},
flag::{
add::{maildir::AddMaildirFlags, AddFlags},
remove::{maildir::RemoveMaildirFlags, RemoveFlags},
set::{maildir::SetMaildirFlags, SetFlags},
},
folder::{
add::{maildir::AddMaildirFolder, AddFolder},
delete::{maildir::DeleteMaildirFolder, DeleteFolder},
expunge::{maildir::ExpungeMaildirFolder, ExpungeFolder},
list::{maildir::ListMaildirFolders, ListFolders},
FolderKind,
},
message::{
add::{maildir::AddMaildirMessage, AddMessage},
copy::{maildir::CopyMaildirMessages, CopyMessages},
delete::{maildir::DeleteMaildirMessages, DeleteMessages},
get::{maildir::GetMaildirMessages, GetMessages},
peek::{maildir::PeekMaildirMessages, PeekMessages},
r#move::{maildir::MoveMaildirMessages, MoveMessages},
remove::{maildir::RemoveMaildirMessages, RemoveMessages},
},
AnyResult,
};
pub struct MaildirContext {
pub account_config: Arc<AccountConfig>,
pub maildir_config: Arc<MaildirConfig>,
pub root: Maildirs,
}
impl MaildirContext {
pub fn get_maildir_from_folder_alias(&self, folder: &str) -> Result<Maildir> {
let folder = self.account_config.get_folder_alias(folder);
if self.maildir_config.maildirpp && FolderKind::matches_inbox(&folder) {
return Ok(Maildir::from(try_shellexpand_path(self.root.path())?));
}
let mdir = self.root.get(folder)?;
Ok(mdir)
}
}
#[derive(Clone)]
pub struct MaildirContextSync {
pub account_config: Arc<AccountConfig>,
pub maildir_config: Arc<MaildirConfig>,
inner: Arc<Mutex<MaildirContext>>,
}
impl Deref for MaildirContextSync {
type Target = Arc<Mutex<MaildirContext>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl BackendContext for MaildirContextSync {}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MaildirContextBuilder {
pub account_config: Arc<AccountConfig>,
pub mdir_config: Arc<MaildirConfig>,
}
impl MaildirContextBuilder {
pub fn new(account_config: Arc<AccountConfig>, mdir_config: Arc<MaildirConfig>) -> Self {
Self {
account_config,
mdir_config,
}
}
pub fn expanded_root_dir(&self) -> PathBuf {
shellexpand_path(&self.mdir_config.root_dir)
}
pub fn maildir(&self) -> Maildirs {
Maildirs::new(self.expanded_root_dir()).with_maildirpp(self.mdir_config.maildirpp)
}
}
#[cfg(feature = "sync")]
impl crate::sync::hash::SyncHash for MaildirContextBuilder {
fn sync_hash(&self, state: &mut std::hash::DefaultHasher) {
self.mdir_config.sync_hash(state);
}
}
#[async_trait]
impl BackendContextBuilder for MaildirContextBuilder {
type Context = MaildirContextSync;
async fn configure(&mut self) -> AnyResult<()> {
let mdir = self.maildir();
if self.mdir_config.maildirpp {
Maildir::from(mdir.path())
.create_all()
.map_err(|err| Error::CreateFolderStructureError(err, mdir.path().to_owned()))?;
}
Ok(())
}
fn check_configuration(&self) -> AnyResult<()> {
match try_shellexpand_path(&self.mdir_config.root_dir) {
Ok(_) => Ok(()),
Err(err) => Err(Error::CheckConfigurationInvalidPathError(err).into()),
}
}
fn check_up(&self) -> Option<BackendFeature<Self::Context, dyn CheckUp>> {
Some(Arc::new(CheckUpMaildir::some_new_boxed))
}
fn add_folder(&self) -> Option<BackendFeature<Self::Context, dyn AddFolder>> {
Some(Arc::new(AddMaildirFolder::some_new_boxed))
}
fn list_folders(&self) -> Option<BackendFeature<Self::Context, dyn ListFolders>> {
Some(Arc::new(ListMaildirFolders::some_new_boxed))
}
fn expunge_folder(&self) -> Option<BackendFeature<Self::Context, dyn ExpungeFolder>> {
Some(Arc::new(ExpungeMaildirFolder::some_new_boxed))
}
fn delete_folder(&self) -> Option<BackendFeature<Self::Context, dyn DeleteFolder>> {
Some(Arc::new(DeleteMaildirFolder::some_new_boxed))
}
fn get_envelope(&self) -> Option<BackendFeature<Self::Context, dyn GetEnvelope>> {
Some(Arc::new(GetMaildirEnvelope::some_new_boxed))
}
fn list_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn ListEnvelopes>> {
Some(Arc::new(ListMaildirEnvelopes::some_new_boxed))
}
#[cfg(feature = "thread")]
fn thread_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn ThreadEnvelopes>> {
Some(Arc::new(ThreadMaildirEnvelopes::some_new_boxed))
}
#[cfg(feature = "watch")]
fn watch_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn WatchEnvelopes>> {
Some(Arc::new(WatchMaildirEnvelopes::some_new_boxed))
}
fn add_flags(&self) -> Option<BackendFeature<Self::Context, dyn AddFlags>> {
Some(Arc::new(AddMaildirFlags::some_new_boxed))
}
fn set_flags(&self) -> Option<BackendFeature<Self::Context, dyn SetFlags>> {
Some(Arc::new(SetMaildirFlags::some_new_boxed))
}
fn remove_flags(&self) -> Option<BackendFeature<Self::Context, dyn RemoveFlags>> {
Some(Arc::new(RemoveMaildirFlags::some_new_boxed))
}
fn add_message(&self) -> Option<BackendFeature<Self::Context, dyn AddMessage>> {
Some(Arc::new(AddMaildirMessage::some_new_boxed))
}
fn peek_messages(&self) -> Option<BackendFeature<Self::Context, dyn PeekMessages>> {
Some(Arc::new(PeekMaildirMessages::some_new_boxed))
}
fn get_messages(&self) -> Option<BackendFeature<Self::Context, dyn GetMessages>> {
Some(Arc::new(GetMaildirMessages::some_new_boxed))
}
fn copy_messages(&self) -> Option<BackendFeature<Self::Context, dyn CopyMessages>> {
Some(Arc::new(CopyMaildirMessages::some_new_boxed))
}
fn move_messages(&self) -> Option<BackendFeature<Self::Context, dyn MoveMessages>> {
Some(Arc::new(MoveMaildirMessages::some_new_boxed))
}
fn delete_messages(&self) -> Option<BackendFeature<Self::Context, dyn DeleteMessages>> {
Some(Arc::new(DeleteMaildirMessages::some_new_boxed))
}
fn remove_messages(&self) -> Option<BackendFeature<Self::Context, dyn RemoveMessages>> {
Some(Arc::new(RemoveMaildirMessages::some_new_boxed))
}
async fn build(self) -> AnyResult<Self::Context> {
info!("building new maildir context");
let ctx = MaildirContext {
account_config: self.account_config.clone(),
maildir_config: self.mdir_config.clone(),
root: self.maildir(),
};
Ok(MaildirContextSync {
account_config: self.account_config,
maildir_config: self.mdir_config,
inner: Arc::new(Mutex::new(ctx)),
})
}
}
#[derive(Clone)]
pub struct CheckUpMaildir {
pub ctx: MaildirContextSync,
}
impl CheckUpMaildir {
pub fn new(ctx: &MaildirContextSync) -> Self {
Self { ctx: ctx.clone() }
}
pub fn new_boxed(ctx: &MaildirContextSync) -> Box<dyn CheckUp> {
Box::new(Self::new(ctx))
}
pub fn some_new_boxed(ctx: &MaildirContextSync) -> Option<Box<dyn CheckUp>> {
Some(Self::new_boxed(ctx))
}
}
#[async_trait]
impl CheckUp for CheckUpMaildir {
async fn check_up(&self) -> AnyResult<()> {
Ok(())
}
}
pub fn encode_folder(folder: impl AsRef<str>) -> String {
urlencoding::encode(folder.as_ref()).to_string()
}
pub fn decode_folder(folder: impl AsRef<str> + ToString) -> String {
urlencoding::decode(folder.as_ref())
.map(|folder| folder.to_string())
.unwrap_or_else(|_| folder.to_string())
}