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