use super::*; use std::{ fs::{self, File}, io::{self, Write}, os::unix::fs::DirBuilderExt, path::PathBuf, process::id, }; pub trait MultiDomain: MailStore { type Error; fn domains(&self) -> Result, ::Error>; fn add_domain(&mut self, domain: &str) -> Result<(), ::Error>; fn remove_domain( &mut self, domain: &str, ) -> Result<(), ::Error>; } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct Filesystem { pub path: PathBuf, } impl MailStore for Filesystem { type Error = io::Error; fn users(&self) -> Vec { todo!() } fn serves_domain(&self, domain: &str) -> bool { let mut path = self.path.clone(); path.push(domain); path.exists() } fn has_mailuser(&self, mailuser: &str) -> bool { if let Some((user, host)) = mailuser.rsplit_once('@') { let mut path = self.path.clone(); if host.contains('.') && !host.contains(|c: char| c.is_whitespace()) { path.push(host); path.push(user); return path.exists(); } } false } fn get_folder(&self, user: &str, folder: &str) -> Option { let Some((user, host)) = user.rsplit_once('@') else { return None; }; let mut path = self.path.clone(); path.push(host); path.push(user); path.push(folder); let Ok(dir) = fs::read_dir(path) else { return None; }; let mut folder = Folder { name: folder.to_string(), messages: HashMap::new(), }; dir.filter(|x| x.is_ok()).map(|x| x.unwrap()).for_each(|e| { if let Ok(contents) = fs::read_to_string(e.path()) { if let Ok(message) = contents.parse::() { folder.messages.insert(message.id.clone(), message); } } }); Some(folder) } fn get_message(&self, user: &str, folder: &str, title: &str) -> Option { self.get_folder(user, folder).and_then(|f| { f.messages .values() .find(|m| m.title.as_ref().map(|x| x.as_str()) == Some(title)) .cloned() }) } fn add_message( &mut self, user: &str, folder: &str, message: Message, ) -> Result<(), Self::Error> { let Some((user, host)) = user.rsplit_once('@') else { return Err(io::Error::new(io::ErrorKind::Other, "Invalid username")); }; let mut path = self.path.clone(); path.push(host); path.push(user); path.push(folder); path.push(&message.id); let mut fd = File::create(path)?; write!(fd, "{message}") } fn delete_message(&mut self, user: &str, folder: &str, id: &str) -> Result<(), Self::Error> { let Some((user, host)) = user.rsplit_once('@') else { return Err(io::Error::new(io::ErrorKind::Other, "Invalid username")); }; let mut path = self.path.clone(); path.push(host); path.push(user); path.push(folder); path.push(id); if path.exists() { fs::remove_file(&path)?; } Ok(()) } fn move_message( &mut self, user: &str, id: &str, folder1: &str, folder2: &str, ) -> Result<(), io::Error> { let Some((user, host)) = user.rsplit_once('@') else { return Err(io::Error::new(io::ErrorKind::Other, "Invalid username")); }; let mut infile = self.path.clone(); infile.push(host); infile.push(user); let mut outfile = infile.clone(); infile.push(folder1); infile.push(id); outfile.push(folder2); outfile.push(id); fs::copy(&infile, &outfile)?; fs::remove_file(infile)?; Ok(()) } fn add_user(&mut self, user: &str) -> Result<(), Self::Error> { todo!() } fn remove_user(&mut self, user: &str) -> bool { todo!() } } impl MultiDomain for Filesystem { type Error = io::Error; fn domains(&self) -> Result, io::Error> { Ok(self .path .read_dir()? .filter(|x| { if let Ok(x) = x { if let Ok(t) = x.file_type() { return t.is_dir(); } } false }) .map(|x| x.unwrap().file_name().to_string_lossy().to_string()) .collect::>()) } fn add_domain(&mut self, domain: &str) -> Result<(), io::Error> { let mut path = self.path.clone(); path.push(domain); if !path.exists() { fs::DirBuilder::new() .recursive(true) .mode(700) .create(path)?; } Ok(()) } fn remove_domain(&mut self, domain: &str) -> Result<(), io::Error> { let mut path = self.path.clone(); path.push(domain); if path.exists() { fs::remove_dir_all(path)?; } Ok(()) } }