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 if 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}
179
180impl Iterator for MessageIterator {
181 type Item = io::Result<Message>;
182
183 fn next(&mut self) -> Option<Self::Item> {
184 loop {
185 let entry = match self.cur_it.next().or_else(|| self.new_it.next()) {
186 Some(Ok(entry)) => entry,
187 Some(Err(err)) => return Some(Err(err)),
188 None => return None,
189 };
190 let path = entry.path();
191 if path.is_file() {
192 if let Some(name) = path.file_name().and_then(|name| name.to_str()) {
193 if !name.starts_with('.') {
194 let internal_date = match fs::metadata(&path)
195 .and_then(|m| m.modified())
196 .and_then(|d| {
197 d.duration_since(std::time::UNIX_EPOCH)
198 .map(|d| d.as_secs())
199 .map_err(|e| {
200 io::Error::new(io::ErrorKind::InvalidData, e.to_string())
201 })
202 }) {
203 Ok(metadata) => metadata,
204 Err(err) => return Some(Err(err)),
205 };
206 let contents = match fs::read(&path) {
207 Ok(contents) => contents,
208 Err(err) => return Some(Err(err)),
209 };
210 let mut flags = Vec::new();
211 if let Some((_, part)) = name.rsplit_once("2,") {
212 for &ch in part.as_bytes() {
213 match ch {
214 b'P' => flags.push(Flag::Passed),
215 b'R' => flags.push(Flag::Replied),
216 b'S' => flags.push(Flag::Seen),
217 b'T' => flags.push(Flag::Trashed),
218 b'D' => flags.push(Flag::Draft),
219 b'F' => flags.push(Flag::Flagged),
220 _ => {
221 if !ch.is_ascii_alphanumeric() {
222 break;
223 }
224 }
225 }
226 }
227 }
228 return Some(Ok(Message {
229 contents,
230 internal_date,
231 flags,
232 path: path.to_path_buf(),
233 }));
234 }
235 }
236 }
237 }
238 }
239}
240
241impl Message {
242 pub fn internal_date(&self) -> u64 {
244 self.internal_date
245 }
246
247 pub fn flags(&self) -> &[Flag] {
249 &self.flags
250 }
251
252 pub fn path(&self) -> &Path {
254 &self.path
255 }
256
257 pub fn contents(&self) -> &[u8] {
259 &self.contents
260 }
261
262 pub fn unwrap_contents(self) -> Vec<u8> {
264 self.contents
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use std::path::PathBuf;
271
272 use crate::mailbox::maildir::{Flag, Message};
273
274 use super::FolderIterator;
275
276 #[test]
277 fn parse_maildir() {
278 let mut messages = Vec::new();
279 let expected_messages = vec![
280 (
281 "INBOX".to_string(),
282 Message {
283 internal_date: 0,
284 flags: vec![Flag::Seen],
285 contents: vec![98, 10],
286 path: "unknown".into(),
287 },
288 ),
289 (
290 "INBOX".to_string(),
291 Message {
292 internal_date: 0,
293 flags: vec![Flag::Seen, Flag::Trashed],
294 contents: vec![97, 10],
295 path: "unknown".into(),
296 },
297 ),
298 (
299 "My Folder".to_string(),
300 Message {
301 internal_date: 0,
302 flags: vec![],
303 contents: vec![100, 10],
304 path: "unknown".into(),
305 },
306 ),
307 (
308 "My Folder".to_string(),
309 Message {
310 internal_date: 0,
311 flags: vec![Flag::Trashed, Flag::Draft, Flag::Replied],
312 contents: vec![99, 10],
313 path: "unknown".into(),
314 },
315 ),
316 (
317 "My Folder.Nested Folder".to_string(),
318 Message {
319 internal_date: 0,
320 flags: vec![Flag::Replied, Flag::Draft, Flag::Flagged],
321 contents: vec![102, 10],
322 path: "unknown".into(),
323 },
324 ),
325 (
326 "My Folder.Nested Folder".to_string(),
327 Message {
328 internal_date: 0,
329 flags: vec![Flag::Flagged, Flag::Passed],
330 contents: vec![101, 10],
331 path: "unknown".into(),
332 },
333 ),
334 ];
335
336 for folder in FolderIterator::new(
337 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
338 .join("resources")
339 .join("maildir"),
340 ".".into(),
341 )
342 .unwrap()
343 {
344 let folder = folder.unwrap();
345 let name = folder.name().unwrap_or("INBOX").to_string();
346
347 for message in folder {
348 let mut message = message.unwrap();
349 assert_ne!(message.internal_date(), 0);
350 assert!(message.path.exists());
351 message.internal_date = 0;
352 message.path = PathBuf::from("unknown");
353 messages.push((name.clone(), message));
354 }
355 }
356
357 messages.sort_unstable();
358 assert_eq!(messages, expected_messages);
359 }
360}