io_email/envelope/m2dir/
list.rs1use alloc::{collections::BTreeSet, vec::Vec};
16use core::mem;
17use std::path::PathBuf;
18
19use io_m2dir::{
20 coroutine::*,
21 entry::{
22 list::{
23 M2dirEntryList as InnerList, M2dirEntryListError as InnerErr,
24 M2dirEntryListOptions as InnerOpts,
25 },
26 types::M2dirEntry,
27 },
28 flag::types::M2dirFlags,
29 m2dir::types::M2dir,
30 path::M2dirPath,
31};
32use log::trace;
33use mail_parser::MessageParser;
34use thiserror::Error;
35
36use crate::{
37 envelope::types::Envelope,
38 m2dir::convert::{InvalidMailboxName, envelope_from, paginate, resolve_mailbox},
39};
40
41#[derive(Debug, Error)]
43pub enum M2dirEnvelopeListError {
44 #[error(transparent)]
45 List(#[from] InnerErr),
46 #[error(transparent)]
47 InvalidMailbox(#[from] InvalidMailboxName),
48 #[error("coroutine was resumed with an M2dirArg variant it did not request")]
49 UnexpectedArg,
50 #[error("coroutine was resumed after completion")]
51 ResumedAfterDone,
52 #[error("failed to parse m2dir message at {0:?}")]
53 Parse(M2dirPath),
54}
55
56pub struct M2dirEnvelopeList {
59 state: State,
60 m2dir: M2dir,
61 page: Option<u32>,
62 page_size: Option<u32>,
63 with_attachment: bool,
64}
65
66impl M2dirEnvelopeList {
67 pub fn new(
68 root: impl Into<PathBuf>,
69 mailbox: &str,
70 page: Option<u32>,
71 page_size: Option<u32>,
72 with_attachment: bool,
73 ) -> Result<Self, M2dirEnvelopeListError> {
74 trace!("prepare m2dir envelope listing");
75 let m2dir = resolve_mailbox(root, mailbox)?;
76 let inner = InnerList::new(m2dir.clone(), InnerOpts::default());
77 Ok(Self {
78 state: State::Listing(inner),
79 m2dir,
80 page,
81 page_size,
82 with_attachment,
83 })
84 }
85}
86
87enum State {
88 Listing(InnerList),
89 Reading(Vec<M2dirEntry>),
90 Done,
91}
92
93fn parse_meta_flags(bytes: &[u8]) -> M2dirFlags {
95 let Ok(text) = core::str::from_utf8(bytes) else {
96 return M2dirFlags::default();
97 };
98 text.lines()
99 .map(str::trim)
100 .filter(|line| !line.is_empty())
101 .collect()
102}
103
104impl M2dirCoroutine for M2dirEnvelopeList {
105 type Yield = M2dirYield;
106 type Return = Result<Vec<Envelope>, M2dirEnvelopeListError>;
107
108 fn resume(&mut self, arg: Option<M2dirArg>) -> M2dirCoroutineState<Self::Yield, Self::Return> {
109 match mem::replace(&mut self.state, State::Done) {
110 State::Listing(mut inner) => match inner.resume(arg) {
111 M2dirCoroutineState::Yielded(y) => {
112 self.state = State::Listing(inner);
113 M2dirCoroutineState::Yielded(y)
114 }
115 M2dirCoroutineState::Complete(Ok(entries)) => {
116 if entries.is_empty() {
117 return M2dirCoroutineState::Complete(Ok(Vec::new()));
118 }
119 let mut paths: BTreeSet<M2dirPath> = BTreeSet::new();
120 for entry in &entries {
121 paths.insert(entry.path().clone());
122 paths.insert(self.m2dir.flags_path(entry.id()));
123 }
124 self.state = State::Reading(entries);
125 M2dirCoroutineState::Yielded(M2dirYield::WantsFileRead(paths))
126 }
127 M2dirCoroutineState::Complete(Err(err)) => {
128 M2dirCoroutineState::Complete(Err(err.into()))
129 }
130 },
131 State::Reading(entries) => {
132 let Some(M2dirArg::FileRead(mut contents)) = arg else {
133 self.state = State::Reading(entries);
134 return M2dirCoroutineState::Complete(Err(
135 M2dirEnvelopeListError::UnexpectedArg,
136 ));
137 };
138 let parser = MessageParser::default();
139 let mut envelopes: Vec<Envelope> = Vec::with_capacity(entries.len());
140 for entry in entries {
141 let Some(body) = contents.remove(entry.path()) else {
142 continue;
143 };
144 let flags_bytes = contents
145 .remove(&self.m2dir.flags_path(entry.id()))
146 .unwrap_or_default();
147 let flags = parse_meta_flags(&flags_bytes);
148 let parsed = if self.with_attachment {
149 parser.parse(&body)
150 } else {
151 parser.parse_headers(&body)
152 };
153 let Some(parsed) = parsed else {
154 return M2dirCoroutineState::Complete(Err(M2dirEnvelopeListError::Parse(
155 entry.path().clone(),
156 )));
157 };
158 let mut envelope = envelope_from(&entry, &flags, &parsed);
159 if self.with_attachment {
160 envelope.has_attachment = Some(parsed.attachment_count() > 0);
161 }
162 envelopes.push(envelope);
163 }
164 envelopes.sort_by(|a, b| b.date.cmp(&a.date));
165 M2dirCoroutineState::Complete(Ok(paginate(envelopes, self.page, self.page_size)))
166 }
167 State::Done => {
168 M2dirCoroutineState::Complete(Err(M2dirEnvelopeListError::ResumedAfterDone))
169 }
170 }
171 }
172}