1use base64::{engine::general_purpose, Engine};
2use chrono::{DateTime, Local, Utc};
3use mail_parser::MessageParser;
4use std::{
5 ffi::OsStr,
6 fs,
7 io::Write,
8 path::{Path, PathBuf},
9};
10
11use crate::flags::{self, Flags};
12use crate::{util, Error, Message, Messages};
13
14pub struct M2dir {
19 pub(crate) path: PathBuf,
20}
21
22impl TryFrom<&Path> for M2dir {
23 type Error = Error;
24
25 fn try_from(path: &Path) -> Result<Self, Error> {
26 if !path.is_dir() {
27 return Err(Error::FolderNotFound);
28 }
29 let marker = PathBuf::from_iter([path.as_os_str(), OsStr::new(".m2dir")]);
30 if !marker.is_file() {
31 return Err(Error::FolderNotFound);
33 }
34 Ok(M2dir {
35 path: PathBuf::from(path),
36 })
37 }
38}
39
40impl M2dir {
41 pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
46 M2dir::try_from(path.as_ref())
47 }
48
49 pub fn create(path: impl AsRef<Path>) -> Result<Self, Error> {
54 fs::create_dir_all(&path)?;
55 let marker = PathBuf::from_iter([path.as_ref().as_os_str(), OsStr::new(".m2dir")]);
56 let _ = fs::File::create(marker)?;
57 Ok(M2dir {
58 path: PathBuf::from(path.as_ref()),
59 })
60 }
61
62 pub fn path(&self) -> &Path {
64 &self.path
65 }
66
67 pub fn count(&self) -> usize {
69 self.list().count()
70 }
71
72 pub fn list(&self) -> Messages {
77 Messages::new(self.path.clone())
78 }
79
80 pub fn deliver(&self, data: &[u8]) -> Result<Message, Error> {
81 self.store(data, &Flags::new())
82 }
83
84 pub fn find(&self, id: &str) -> Result<Option<Message>, Error> {
85 for msg in self.list() {
86 let msg = msg?;
87 if msg.id() == id {
88 return Ok(Some(msg));
89 }
90 }
91 Ok(None)
92 }
93
94 pub fn store(&self, data: &[u8], flags: &Flags) -> Result<Message, Error> {
97 let message = MessageParser::default()
98 .parse_headers(data)
99 .ok_or(Error::Parse())?;
100
101 let date = match message.date() {
104 None => Local::now(),
105 Some(dt) => DateTime::from_timestamp(dt.to_timestamp(), 0)
106 .unwrap_or_else(Utc::now)
107 .with_timezone(&Local),
108 };
109 let from = message
110 .from()
111 .and_then(|f| f.first())
112 .and_then(|a| a.address())
113 .unwrap_or("<parse_error>");
114
115 let mut id = [0u8; 12];
116 id[0..4].copy_from_slice(&(data.len() as u32).to_le_bytes());
117 let digest = &util::fnv64(&id[0..4], data).to_le_bytes();
118 id[4..].copy_from_slice(digest); let mut id_b64 = general_purpose::URL_SAFE.encode(id);
120
121 let final_name = format!("{}_{},{}", date.format("%Y-%m-%d_%H:%M"), from, id_b64);
122 let mut final_path = self.path.clone();
123 final_path.push(&final_name);
124
125 if final_path.exists() {
126 let mut i = 0u16;
128 while final_path.exists() {
129 i = i.wrapping_add(1);
130 if i == 0 {
131 return Err(Error::TooManyDuplicates);
132 }
133 final_path.set_file_name(format!(
134 "{}_{},{}.{}",
135 date.format("%Y-%m-%d_%H:%M"),
136 from,
137 id_b64,
138 i
139 ));
140 }
141 id_b64 = format!("{}.{}", id_b64, i);
142 }
143
144 if flags.len() > 0 {
145 let flags_path = flags::flags_path_for(&self.path, &id_b64);
146
147 util::write_atomic(&flags_path, |f| {
148 for flag in flags.iter() {
149 writeln!(f, "{}", flag)?;
150 }
151 Ok(())
152 })
153 .map_err(|e| Error::WriteFlags(flags_path, e))?;
154 }
155
156 util::write_atomic(&final_path, |f| f.write_all(data)).map_err(|e| {
157 if flags.len() > 0 {
158 let flags_path = flags::flags_path_for(&self.path, &id_b64);
159 _ = fs::remove_file(flags_path);
160 }
161 e
162 })?;
163
164 Ok(Message {
165 id: id_b64,
166 path: final_path,
167 })
168 }
169}