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
53pub struct MaildirContext {
58 pub account_config: Arc<AccountConfig>,
60
61 pub maildir_config: Arc<MaildirConfig>,
63
64 pub root: Maildirs,
66}
67
68impl MaildirContext {
69 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 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#[derive(Clone)]
89pub struct MaildirContextSync {
90 pub account_config: Arc<AccountConfig>,
92
93 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
111pub struct MaildirContextBuilder {
112 pub account_config: Arc<AccountConfig>,
114
115 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 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 Ok(())
298 }
299}
300
301pub fn encode_folder(folder: impl AsRef<str>) -> String {
303 urlencoding::encode(folder.as_ref()).to_string()
304}
305
306pub 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}