1use alloc::{string::String, vec::Vec};
14use std::{
15 fs, io, process,
16 time::{SystemTime, UNIX_EPOCH},
17};
18
19use gethostname::gethostname;
20use io_maildir::{client::MaildirClient as InnerMaildirClient, coroutine::*, path::FsPath};
21use log::trace;
22use thiserror::Error;
23
24#[cfg(feature = "search")]
25use crate::{
26 envelope::maildir::search::{MaildirEnvelopeSearch, MaildirEnvelopeSearchError},
27 search::query::SearchEmailsQuery,
28};
29use crate::{
30 envelope::{
31 maildir::list::{MaildirEnvelopeList, MaildirEnvelopeListError},
32 types::Envelope,
33 },
34 flag::{
35 maildir::store::{MaildirFlagStore, MaildirFlagStoreError},
36 types::{Flag, FlagOp},
37 },
38 mailbox::{
39 maildir::{
40 create::{MaildirMailboxCreate, MaildirMailboxCreateError},
41 delete::{MaildirMailboxDelete, MaildirMailboxDeleteError},
42 list::{MaildirMailboxList, MaildirMailboxListError},
43 },
44 types::Mailbox,
45 },
46 message::maildir::{
47 add::{MaildirMessageAdd, MaildirMessageAddError},
48 copy::{MaildirMessageCopy, MaildirMessageCopyError},
49 delete::{MaildirMessageDelete, MaildirMessageDeleteError},
50 get::{MaildirMessageGet, MaildirMessageGetError},
51 r#move::{MaildirMessageMove, MaildirMessageMoveError},
52 },
53};
54
55#[derive(Debug, Error)]
59pub enum MaildirClientError {
60 #[error(transparent)]
61 Io(#[from] io::Error),
62 #[error(transparent)]
63 MailboxList(#[from] MaildirMailboxListError),
64 #[error(transparent)]
65 EnvelopeList(#[from] MaildirEnvelopeListError),
66 #[cfg(feature = "search")]
67 #[error(transparent)]
68 EnvelopeSearch(#[from] MaildirEnvelopeSearchError),
69 #[error(transparent)]
70 FlagStore(#[from] MaildirFlagStoreError),
71 #[error(transparent)]
72 MailboxCreate(#[from] MaildirMailboxCreateError),
73 #[error(transparent)]
74 MailboxDelete(#[from] MaildirMailboxDeleteError),
75 #[error(transparent)]
76 MessageAdd(#[from] MaildirMessageAddError),
77 #[error(transparent)]
78 MessageCopy(#[from] MaildirMessageCopyError),
79 #[error(transparent)]
80 MessageDelete(#[from] MaildirMessageDeleteError),
81 #[error(transparent)]
82 MessageGet(#[from] MaildirMessageGetError),
83 #[error(transparent)]
84 MessageMove(#[from] MaildirMessageMoveError),
85 #[error(transparent)]
86 Inner(#[from] io_maildir::client::MaildirClientError),
87}
88
89pub struct MaildirClient {
95 pub inner: InnerMaildirClient,
96}
97
98impl MaildirClient {
99 pub fn new(root: impl Into<FsPath>) -> Self {
103 Self {
104 inner: InnerMaildirClient::new(root),
105 }
106 }
107
108 pub fn run<C, T, E>(&self, mut coroutine: C) -> Result<T, MaildirClientError>
116 where
117 C: MaildirCoroutine<Yield = MaildirYield, Return = Result<T, E>>,
118 MaildirClientError: From<E>,
119 {
120 let mut arg: Option<MaildirReply> = None;
121
122 loop {
123 match coroutine.resume(arg.take()) {
124 MaildirCoroutineState::Complete(Ok(out)) => return Ok(out),
125 MaildirCoroutineState::Complete(Err(err)) => return Err(err.into()),
126 MaildirCoroutineState::Yielded(MaildirYield::WantsFileExists(paths)) => {
127 let mut out = alloc::collections::BTreeMap::new();
128 for path in paths {
129 let exists = fs::metadata(path.as_str())
130 .map(|m| m.is_file())
131 .unwrap_or(false);
132 trace!("file_exists {path}: {exists}");
133 out.insert(path, exists);
134 }
135 arg = Some(MaildirReply::FileExists(out));
136 }
137 MaildirCoroutineState::Yielded(MaildirYield::WantsDirExists(paths)) => {
138 let mut out = alloc::collections::BTreeMap::new();
139 for path in paths {
140 let exists = fs::metadata(path.as_str())
141 .map(|m| m.is_dir())
142 .unwrap_or(false);
143 trace!("dir_exists {path}: {exists}");
144 out.insert(path, exists);
145 }
146 arg = Some(MaildirReply::DirExists(out));
147 }
148 MaildirCoroutineState::Yielded(MaildirYield::WantsDirRead(paths)) => {
149 let mut entries = alloc::collections::BTreeMap::new();
150 for path in paths {
151 trace!("read_dir {path}");
152 let mut names = alloc::collections::BTreeSet::new();
153 match fs::read_dir(path.as_str()) {
154 Ok(iter) => {
155 for entry in iter {
156 let entry = entry?;
157 names.insert(FsPath::from(entry.path()));
158 }
159 }
160 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
161 Err(err) => return Err(err.into()),
162 }
163 entries.insert(path, names);
164 }
165 arg = Some(MaildirReply::DirRead(entries));
166 }
167 MaildirCoroutineState::Yielded(MaildirYield::WantsFileRead(paths)) => {
168 let mut contents = alloc::collections::BTreeMap::new();
169 for path in paths {
170 trace!("read_file {path}");
171 let bytes = fs::read(path.as_str())?;
172 contents.insert(path, bytes);
173 }
174 arg = Some(MaildirReply::FileRead(contents));
175 }
176 MaildirCoroutineState::Yielded(MaildirYield::WantsFileCreate(files)) => {
177 for (path, bytes) in files {
178 trace!("write {path} ({} bytes)", bytes.len());
179 if let Some(parent) = std::path::Path::new(path.as_str()).parent() {
180 fs::create_dir_all(parent)?;
181 }
182 fs::write(path.as_str(), &bytes)?;
183 }
184 arg = Some(MaildirReply::FileCreate);
185 }
186 MaildirCoroutineState::Yielded(MaildirYield::WantsDirCreate(paths)) => {
187 for path in paths {
188 trace!("create_dir_all {path}");
189 fs::create_dir_all(path.as_str())?;
190 }
191 arg = Some(MaildirReply::DirCreate);
192 }
193 MaildirCoroutineState::Yielded(MaildirYield::WantsDirRemove(paths)) => {
194 for path in paths {
195 trace!("remove_dir_all {path}");
196 fs::remove_dir_all(path.as_str())?;
197 }
198 arg = Some(MaildirReply::DirRemove);
199 }
200 MaildirCoroutineState::Yielded(MaildirYield::WantsRename(pairs)) => {
201 for (from, to) in pairs {
202 trace!("rename {from} -> {to}");
203 fs::rename(from.as_str(), to.as_str())?;
204 }
205 arg = Some(MaildirReply::Rename);
206 }
207 MaildirCoroutineState::Yielded(MaildirYield::WantsCopy(pairs)) => {
208 for (from, to) in pairs {
209 trace!("copy {from} -> {to}");
210 fs::copy(from.as_str(), to.as_str())?;
211 }
212 arg = Some(MaildirReply::Copy);
213 }
214 MaildirCoroutineState::Yielded(MaildirYield::WantsTime) => {
215 let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
216 arg = Some(MaildirReply::Time {
217 secs: ts.as_secs(),
218 nanos: ts.subsec_nanos(),
219 });
220 }
221 MaildirCoroutineState::Yielded(MaildirYield::WantsPid) => {
222 arg = Some(MaildirReply::Pid(process::id()));
223 }
224 MaildirCoroutineState::Yielded(MaildirYield::WantsHostname) => {
225 let hostname = gethostname().into_string().unwrap_or_default();
226 arg = Some(MaildirReply::Hostname(hostname));
227 }
228 }
229 }
230 }
231
232 pub fn list_mailboxes(&self, with_counts: bool) -> Result<Vec<Mailbox>, MaildirClientError> {
236 self.run(MaildirMailboxList::new(&self.inner.store, with_counts))
237 }
238
239 pub fn list_envelopes(
243 &self,
244 mailbox: &str,
245 page: Option<u32>,
246 page_size: Option<u32>,
247 _with_attachment: bool,
248 ) -> Result<Vec<Envelope>, MaildirClientError> {
249 self.run(MaildirEnvelopeList::new(
250 &self.inner.store,
251 mailbox,
252 page,
253 page_size,
254 )?)
255 }
256
257 #[cfg(feature = "search")]
260 pub fn search_envelopes(
261 &self,
262 mailbox: &str,
263 query: Option<&SearchEmailsQuery>,
264 page: Option<u32>,
265 page_size: Option<u32>,
266 _with_attachment: bool,
267 ) -> Result<Vec<Envelope>, MaildirClientError> {
268 self.run(MaildirEnvelopeSearch::new(
269 &self.inner.store,
270 mailbox,
271 query,
272 page,
273 page_size,
274 )?)
275 }
276
277 pub fn store_flags(
279 &self,
280 mailbox: &str,
281 ids: &[&str],
282 flags: &[Flag],
283 op: FlagOp,
284 ) -> Result<(), MaildirClientError> {
285 self.run(MaildirFlagStore::new(
286 &self.inner.store,
287 mailbox,
288 ids,
289 flags,
290 op,
291 )?)
292 }
293
294 pub fn get_message(&self, mailbox: &str, id: &str) -> Result<Vec<u8>, MaildirClientError> {
296 self.run(MaildirMessageGet::new(&self.inner.store, mailbox, id)?)
297 }
298
299 pub fn add_message(
302 &self,
303 mailbox: &str,
304 flags: &[Flag],
305 raw: Vec<u8>,
306 ) -> Result<String, MaildirClientError> {
307 self.run(MaildirMessageAdd::new(
308 &self.inner.store,
309 mailbox,
310 flags,
311 raw,
312 )?)
313 }
314
315 pub fn create_mailbox(&self, name: &str) -> Result<(), MaildirClientError> {
317 self.run(MaildirMailboxCreate::new(&self.inner.store, name)?)
318 }
319
320 pub fn delete_mailbox(&self, name: &str) -> Result<(), MaildirClientError> {
322 self.run(MaildirMailboxDelete::new(&self.inner.store, name)?)
323 }
324
325 pub fn delete_message(&self, mailbox: &str, id: &str) -> Result<(), MaildirClientError> {
329 self.run(MaildirMessageDelete::new(&self.inner.store, mailbox, id)?)
330 }
331
332 pub fn copy_messages(
334 &self,
335 from: &str,
336 to: &str,
337 ids: &[&str],
338 ) -> Result<(), MaildirClientError> {
339 self.run(MaildirMessageCopy::new(&self.inner.store, from, to, ids)?)
340 }
341
342 pub fn move_messages(
344 &self,
345 from: &str,
346 to: &str,
347 ids: &[&str],
348 ) -> Result<(), MaildirClientError> {
349 self.run(MaildirMessageMove::new(&self.inner.store, from, to, ids)?)
350 }
351}