1use std::{
8 fs, io,
9 path::{Path, PathBuf},
10};
11
12pub struct FolderIterator<'x> {
14 inbox: Option<MessageIterator>,
15 it_stack: Vec<fs::ReadDir>,
16 name_stack: Vec<String>,
17 prefix: Option<&'x str>,
18}
19
20pub struct MessageIterator {
22 name: Option<String>,
23 cur_it: fs::ReadDir,
24 new_it: fs::ReadDir,
25}
26
27#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
29pub struct Message {
30 internal_date: u64,
31 flags: Vec<Flag>,
32 contents: Vec<u8>,
33 path: PathBuf,
34}
35
36#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
38pub enum Flag {
39 Passed,
40 Replied,
41 Seen,
42 Trashed,
43 Draft,
44 Flagged,
45}
46
47impl FolderIterator<'_> {
48 pub fn new(
52 path: impl Into<PathBuf>,
53 sub_folder_prefix: Option<&str>,
54 ) -> io::Result<FolderIterator<'_>> {
55 let path = path.into();
56
57 Ok(FolderIterator {
58 it_stack: vec![fs::read_dir(&path)?],
59 name_stack: Vec::new(),
60 inbox: match MessageIterator::new_(&path, None) {
61 Ok(inbox) => inbox.into(),
62 Err(err) => {
63 if err.kind() == io::ErrorKind::NotFound {
64 None
65 } else {
66 return Err(err);
67 }
68 }
69 },
70 prefix: sub_folder_prefix,
71 })
72 }
73}
74
75impl MessageIterator {
76 pub fn new(path: impl Into<PathBuf>) -> io::Result<MessageIterator> {
78 MessageIterator::new_(&path.into(), None)
79 }
80
81 fn new_(path: &Path, name: Option<String>) -> io::Result<MessageIterator> {
82 let mut cur_path = path.to_path_buf();
83 cur_path.push("cur");
84 if !cur_path.exists() {
85 return Err(io::Error::new(
86 io::ErrorKind::NotFound,
87 "Invalid Maildir format, 'cur' directory not found.",
88 ));
89 }
90 let mut new_path = path.to_path_buf();
91 new_path.push("new");
92 if !new_path.exists() {
93 return Err(io::Error::new(
94 io::ErrorKind::NotFound,
95 "Invalid Maildir format, 'new' directory not found.",
96 ));
97 }
98
99 Ok(MessageIterator {
100 name,
101 cur_it: fs::read_dir(cur_path)?,
102 new_it: fs::read_dir(new_path)?,
103 })
104 }
105
106 pub fn name(&self) -> Option<&str> {
108 self.name.as_deref()
109 }
110}
111
112impl Iterator for FolderIterator<'_> {
113 type Item = io::Result<MessageIterator>;
114
115 fn next(&mut self) -> Option<Self::Item> {
116 if let Some(inbox) = self.inbox.take() {
117 return Some(Ok(inbox));
118 }
119
120 loop {
121 let entry = match self.it_stack.last_mut().unwrap().next() {
122 Some(Ok(entry)) => entry,
123 Some(Err(err)) => return Some(Err(err)),
124 None => {
125 self.it_stack.pop();
126 self.name_stack.pop();
127
128 if !self.it_stack.is_empty() {
129 continue;
130 } else {
131 return None;
132 }
133 }
134 };
135
136 let path = entry.path();
137 if path.is_dir()
138 && let Some(name) =
139 path.file_name()
140 .and_then(|name| name.to_str())
141 .and_then(|name| {
142 if !["cur", "new", "tmp"].contains(&name) {
143 if let Some(prefix) = self.prefix {
144 name.strip_prefix(prefix)
145 } else {
146 name.into()
147 }
148 } else {
149 None
150 }
151 })
152 {
153 match fs::read_dir(&path) {
154 Ok(next_it) => {
155 self.it_stack.push(next_it);
156 self.name_stack.push(name.to_string());
157 }
158 Err(err) => {
159 return Some(Err(err));
160 }
161 }
162
163 match MessageIterator::new_(
164 &path,
165 self.name_stack.join(self.prefix.unwrap_or("/")).into(),
166 ) {
167 Ok(folder) => return Some(Ok(folder)),
168 Err(err) => {
169 if err.kind() != io::ErrorKind::NotFound {
170 return Some(Err(err));
171 }
172 }
173 }
174 }
175 }
176 }
177}
178
179impl Iterator for MessageIterator {
180 type Item = io::Result<Message>;
181
182 fn next(&mut self) -> Option<Self::Item> {
183 loop {
184 let entry = match self.cur_it.next().or_else(|| self.new_it.next()) {
185 Some(Ok(entry)) => entry,
186 Some(Err(err)) => return Some(Err(err)),
187 None => return None,
188 };
189 let path = entry.path();
190 if path.is_file()
191 && let Some(name) = path.file_name().and_then(|name| name.to_str())
192 && !name.starts_with('.')
193 {
194 let internal_date =
195 match fs::metadata(&path)
196 .and_then(|m| m.modified())
197 .and_then(|d| {
198 d.duration_since(std::time::UNIX_EPOCH)
199 .map(|d| d.as_secs())
200 .map_err(|e| {
201 io::Error::new(io::ErrorKind::InvalidData, e.to_string())
202 })
203 }) {
204 Ok(metadata) => metadata,
205 Err(err) => return Some(Err(err)),
206 };
207 let contents = match fs::read(&path) {
208 Ok(contents) => contents,
209 Err(err) => return Some(Err(err)),
210 };
211 let mut flags = Vec::new();
212 if let Some((_, part)) = name.rsplit_once("2,") {
213 for &ch in part.as_bytes() {
214 match ch {
215 b'P' => flags.push(Flag::Passed),
216 b'R' => flags.push(Flag::Replied),
217 b'S' => flags.push(Flag::Seen),
218 b'T' => flags.push(Flag::Trashed),
219 b'D' => flags.push(Flag::Draft),
220 b'F' => flags.push(Flag::Flagged),
221 _ => {
222 if !ch.is_ascii_alphanumeric() {
223 break;
224 }
225 }
226 }
227 }
228 }
229 return Some(Ok(Message {
230 contents,
231 internal_date,
232 flags,
233 path: path.to_path_buf(),
234 }));
235 }
236 }
237 }
238}
239
240impl Message {
241 pub fn internal_date(&self) -> u64 {
243 self.internal_date
244 }
245
246 pub fn flags(&self) -> &[Flag] {
248 &self.flags
249 }
250
251 pub fn path(&self) -> &Path {
253 &self.path
254 }
255
256 pub fn contents(&self) -> &[u8] {
258 &self.contents
259 }
260
261 pub fn unwrap_contents(self) -> Vec<u8> {
263 self.contents
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use std::path::PathBuf;
270
271 use crate::mailbox::maildir::{Flag, Message};
272
273 use super::FolderIterator;
274
275 #[test]
276 fn parse_maildir() {
277 let mut messages = Vec::new();
278 let expected_messages = vec![
279 (
280 "INBOX".to_string(),
281 Message {
282 internal_date: 0,
283 flags: vec![Flag::Seen],
284 contents: vec![98, 10],
285 path: "unknown".into(),
286 },
287 ),
288 (
289 "INBOX".to_string(),
290 Message {
291 internal_date: 0,
292 flags: vec![Flag::Seen, Flag::Trashed],
293 contents: vec![97, 10],
294 path: "unknown".into(),
295 },
296 ),
297 (
298 "My Folder".to_string(),
299 Message {
300 internal_date: 0,
301 flags: vec![],
302 contents: vec![100, 10],
303 path: "unknown".into(),
304 },
305 ),
306 (
307 "My Folder".to_string(),
308 Message {
309 internal_date: 0,
310 flags: vec![Flag::Trashed, Flag::Draft, Flag::Replied],
311 contents: vec![99, 10],
312 path: "unknown".into(),
313 },
314 ),
315 (
316 "My Folder.Nested Folder".to_string(),
317 Message {
318 internal_date: 0,
319 flags: vec![Flag::Replied, Flag::Draft, Flag::Flagged],
320 contents: vec![102, 10],
321 path: "unknown".into(),
322 },
323 ),
324 (
325 "My Folder.Nested Folder".to_string(),
326 Message {
327 internal_date: 0,
328 flags: vec![Flag::Flagged, Flag::Passed],
329 contents: vec![101, 10],
330 path: "unknown".into(),
331 },
332 ),
333 ];
334
335 for folder in FolderIterator::new(
336 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
337 .join("resources")
338 .join("maildir"),
339 ".".into(),
340 )
341 .unwrap()
342 {
343 let folder = folder.unwrap();
344 let name = folder.name().unwrap_or("INBOX").to_string();
345
346 for message in folder {
347 let mut message = message.unwrap();
348 assert_ne!(message.internal_date(), 0);
349 assert!(message.path.exists());
350 message.internal_date = 0;
351 message.path = PathBuf::from("unknown");
352 messages.push((name.clone(), message));
353 }
354 }
355
356 messages.sort_unstable();
357 assert_eq!(messages, expected_messages);
358 }
359}