email/maildir/
mod.rs

1pub mod config;
2mod error;
3
4use std::{ops::Deref, path::PathBuf, sync::Arc};
5
6use async_trait::async_trait;
7use maildirs::{Maildir, Maildirs};
8use shellexpand_utils::{shellexpand_path, try_shellexpand_path};
9use tokio::sync::Mutex;
10use tracing::info;
11
12use self::config::MaildirConfig;
13#[doc(inline)]
14pub use self::error::{Error, Result};
15#[cfg(feature = "thread")]
16use crate::envelope::thread::{maildir::ThreadMaildirEnvelopes, ThreadEnvelopes};
17#[cfg(feature = "watch")]
18use crate::envelope::watch::{maildir::WatchMaildirEnvelopes, WatchEnvelopes};
19use crate::{
20    account::config::AccountConfig,
21    backend::{
22        context::{BackendContext, BackendContextBuilder},
23        feature::{BackendFeature, CheckUp},
24    },
25    envelope::{
26        get::{maildir::GetMaildirEnvelope, GetEnvelope},
27        list::{maildir::ListMaildirEnvelopes, ListEnvelopes},
28    },
29    flag::{
30        add::{maildir::AddMaildirFlags, AddFlags},
31        remove::{maildir::RemoveMaildirFlags, RemoveFlags},
32        set::{maildir::SetMaildirFlags, SetFlags},
33    },
34    folder::{
35        add::{maildir::AddMaildirFolder, AddFolder},
36        delete::{maildir::DeleteMaildirFolder, DeleteFolder},
37        expunge::{maildir::ExpungeMaildirFolder, ExpungeFolder},
38        list::{maildir::ListMaildirFolders, ListFolders},
39        FolderKind,
40    },
41    message::{
42        add::{maildir::AddMaildirMessage, AddMessage},
43        copy::{maildir::CopyMaildirMessages, CopyMessages},
44        delete::{maildir::DeleteMaildirMessages, DeleteMessages},
45        get::{maildir::GetMaildirMessages, GetMessages},
46        peek::{maildir::PeekMaildirMessages, PeekMessages},
47        r#move::{maildir::MoveMaildirMessages, MoveMessages},
48        remove::{maildir::RemoveMaildirMessages, RemoveMessages},
49    },
50    AnyResult,
51};
52
53/// The Maildir backend context.
54///
55/// This context is unsync, which means it cannot be shared between
56/// threads. For the sync version, see [`MaildirContextSync`].
57pub struct MaildirContext {
58    /// The account configuration.
59    pub account_config: Arc<AccountConfig>,
60
61    /// The Maildir configuration.
62    pub maildir_config: Arc<MaildirConfig>,
63
64    /// The maildir instance.
65    pub root: Maildirs,
66}
67
68impl MaildirContext {
69    /// Create a maildir instance from a folder name.
70    pub fn get_maildir_from_folder_alias(&self, folder: &str) -> Result<Maildir> {
71        let folder = self.account_config.get_folder_alias(folder);
72
73        // If the folder matches to the inbox folder kind, create a
74        // maildir instance from the root folder.
75        if self.maildir_config.maildirpp && FolderKind::matches_inbox(&folder) {
76            return Ok(Maildir::from(try_shellexpand_path(self.root.path())?));
77        }
78
79        let mdir = self.root.get(folder)?;
80        Ok(mdir)
81    }
82}
83
84/// The sync version of the Maildir backend context.
85///
86/// This is just a Maildir session wrapped into a mutex, so the same
87/// Maildir session can be shared and updated across multiple threads.
88#[derive(Clone)]
89pub struct MaildirContextSync {
90    /// The account configuration.
91    pub account_config: Arc<AccountConfig>,
92
93    /// The Maildir configuration.
94    pub maildir_config: Arc<MaildirConfig>,
95
96    inner: Arc<Mutex<MaildirContext>>,
97}
98
99impl Deref for MaildirContextSync {
100    type Target = Arc<Mutex<MaildirContext>>;
101
102    fn deref(&self) -> &Self::Target {
103        &self.inner
104    }
105}
106
107impl BackendContext for MaildirContextSync {}
108
109/// The Maildir backend context builder.
110#[derive(Clone, Debug, Default, Eq, PartialEq)]
111pub struct MaildirContextBuilder {
112    /// The account configuration.
113    pub account_config: Arc<AccountConfig>,
114
115    /// The Maildir configuration.
116    pub mdir_config: Arc<MaildirConfig>,
117}
118
119impl MaildirContextBuilder {
120    pub fn new(account_config: Arc<AccountConfig>, mdir_config: Arc<MaildirConfig>) -> Self {
121        Self {
122            account_config,
123            mdir_config,
124        }
125    }
126
127    pub fn expanded_root_dir(&self) -> PathBuf {
128        shellexpand_path(&self.mdir_config.root_dir)
129    }
130
131    pub fn maildir(&self) -> Maildirs {
132        Maildirs::new(self.expanded_root_dir()).with_maildirpp(self.mdir_config.maildirpp)
133    }
134}
135
136#[cfg(feature = "sync")]
137impl crate::sync::hash::SyncHash for MaildirContextBuilder {
138    fn sync_hash(&self, state: &mut std::hash::DefaultHasher) {
139        self.mdir_config.sync_hash(state);
140    }
141}
142
143#[async_trait]
144impl BackendContextBuilder for MaildirContextBuilder {
145    type Context = MaildirContextSync;
146
147    async fn configure(&mut self) -> AnyResult<()> {
148        let mdir = self.maildir();
149
150        if self.mdir_config.maildirpp {
151            Maildir::from(mdir.path())
152                .create_all()
153                .map_err(|err| Error::CreateFolderStructureError(err, mdir.path().to_owned()))?;
154        }
155
156        Ok(())
157    }
158
159    fn check_configuration(&self) -> AnyResult<()> {
160        match try_shellexpand_path(&self.mdir_config.root_dir) {
161            Ok(_) => Ok(()),
162            Err(err) => Err(Error::CheckConfigurationInvalidPathError(err).into()),
163        }
164    }
165
166    fn check_up(&self) -> Option<BackendFeature<Self::Context, dyn CheckUp>> {
167        Some(Arc::new(CheckUpMaildir::some_new_boxed))
168    }
169
170    fn add_folder(&self) -> Option<BackendFeature<Self::Context, dyn AddFolder>> {
171        Some(Arc::new(AddMaildirFolder::some_new_boxed))
172    }
173
174    fn list_folders(&self) -> Option<BackendFeature<Self::Context, dyn ListFolders>> {
175        Some(Arc::new(ListMaildirFolders::some_new_boxed))
176    }
177
178    fn expunge_folder(&self) -> Option<BackendFeature<Self::Context, dyn ExpungeFolder>> {
179        Some(Arc::new(ExpungeMaildirFolder::some_new_boxed))
180    }
181
182    // TODO
183    // fn purge_folder(&self) -> Option<BackendFeature<Self::Context, dyn PurgeFolder>> {
184    //     Some(Arc::new(PurgeMaildirFolder::some_new_boxed))
185    // }
186
187    fn delete_folder(&self) -> Option<BackendFeature<Self::Context, dyn DeleteFolder>> {
188        Some(Arc::new(DeleteMaildirFolder::some_new_boxed))
189    }
190
191    fn get_envelope(&self) -> Option<BackendFeature<Self::Context, dyn GetEnvelope>> {
192        Some(Arc::new(GetMaildirEnvelope::some_new_boxed))
193    }
194
195    fn list_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn ListEnvelopes>> {
196        Some(Arc::new(ListMaildirEnvelopes::some_new_boxed))
197    }
198
199    #[cfg(feature = "thread")]
200    fn thread_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn ThreadEnvelopes>> {
201        Some(Arc::new(ThreadMaildirEnvelopes::some_new_boxed))
202    }
203
204    #[cfg(feature = "watch")]
205    fn watch_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn WatchEnvelopes>> {
206        Some(Arc::new(WatchMaildirEnvelopes::some_new_boxed))
207    }
208
209    fn add_flags(&self) -> Option<BackendFeature<Self::Context, dyn AddFlags>> {
210        Some(Arc::new(AddMaildirFlags::some_new_boxed))
211    }
212
213    fn set_flags(&self) -> Option<BackendFeature<Self::Context, dyn SetFlags>> {
214        Some(Arc::new(SetMaildirFlags::some_new_boxed))
215    }
216
217    fn remove_flags(&self) -> Option<BackendFeature<Self::Context, dyn RemoveFlags>> {
218        Some(Arc::new(RemoveMaildirFlags::some_new_boxed))
219    }
220
221    fn add_message(&self) -> Option<BackendFeature<Self::Context, dyn AddMessage>> {
222        Some(Arc::new(AddMaildirMessage::some_new_boxed))
223    }
224
225    fn peek_messages(&self) -> Option<BackendFeature<Self::Context, dyn PeekMessages>> {
226        Some(Arc::new(PeekMaildirMessages::some_new_boxed))
227    }
228
229    fn get_messages(&self) -> Option<BackendFeature<Self::Context, dyn GetMessages>> {
230        Some(Arc::new(GetMaildirMessages::some_new_boxed))
231    }
232
233    fn copy_messages(&self) -> Option<BackendFeature<Self::Context, dyn CopyMessages>> {
234        Some(Arc::new(CopyMaildirMessages::some_new_boxed))
235    }
236
237    fn move_messages(&self) -> Option<BackendFeature<Self::Context, dyn MoveMessages>> {
238        Some(Arc::new(MoveMaildirMessages::some_new_boxed))
239    }
240
241    fn delete_messages(&self) -> Option<BackendFeature<Self::Context, dyn DeleteMessages>> {
242        Some(Arc::new(DeleteMaildirMessages::some_new_boxed))
243    }
244
245    fn remove_messages(&self) -> Option<BackendFeature<Self::Context, dyn RemoveMessages>> {
246        Some(Arc::new(RemoveMaildirMessages::some_new_boxed))
247    }
248
249    async fn build(self) -> AnyResult<Self::Context> {
250        info!("building new maildir context");
251
252        let ctx = MaildirContext {
253            account_config: self.account_config.clone(),
254            maildir_config: self.mdir_config.clone(),
255            root: self.maildir(),
256        };
257
258        Ok(MaildirContextSync {
259            account_config: self.account_config,
260            maildir_config: self.mdir_config,
261            inner: Arc::new(Mutex::new(ctx)),
262        })
263    }
264}
265
266#[derive(Clone)]
267pub struct CheckUpMaildir {
268    pub ctx: MaildirContextSync,
269}
270
271impl CheckUpMaildir {
272    pub fn new(ctx: &MaildirContextSync) -> Self {
273        Self { ctx: ctx.clone() }
274    }
275
276    pub fn new_boxed(ctx: &MaildirContextSync) -> Box<dyn CheckUp> {
277        Box::new(Self::new(ctx))
278    }
279
280    pub fn some_new_boxed(ctx: &MaildirContextSync) -> Option<Box<dyn CheckUp>> {
281        Some(Self::new_boxed(ctx))
282    }
283}
284
285#[async_trait]
286impl CheckUp for CheckUpMaildir {
287    async fn check_up(&self) -> AnyResult<()> {
288        // FIXME
289        //
290        // let ctx = self.ctx.lock().await;
291
292        // ctx.root
293        //     .list_cur()
294        //     .try_for_each(|e| e.map(|_| ()))
295        //     .map_err(Error::CheckUpCurrentDirectoryError)?;
296
297        Ok(())
298    }
299}
300
301/// URL-encode the given folder.
302pub fn encode_folder(folder: impl AsRef<str>) -> String {
303    urlencoding::encode(folder.as_ref()).to_string()
304}
305
306/// URL-decode the given folder.
307pub fn decode_folder(folder: impl AsRef<str> + ToString) -> String {
308    urlencoding::decode(folder.as_ref())
309        .map(|folder| folder.to_string())
310        .unwrap_or_else(|_| folder.to_string())
311}