use std::{
collections::HashSet,
fs::{self, read, read_dir, ReadDir},
io,
path::{Path, PathBuf},
};
use crate::{validate::validate_id, Error, Flag, CUR, NEW, SEP, TMP};
#[derive(Debug)]
pub struct MailEntry {
id: String,
flags: HashSet<Flag>,
path: PathBuf,
}
impl MailEntry {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref();
let filename = path
.file_name()
.ok_or(Error::InvalidFilenameError(
path.to_string_lossy().to_string(),
))?
.to_string_lossy()
.to_string();
let (id, flags) = match filename.rsplit_once(SEP) {
Some((id, flags)) => (
id,
flags
.chars()
.map(TryFrom::try_from)
.filter_map(Result::ok)
.collect(),
),
None => (filename.as_ref(), HashSet::new()),
};
Ok(MailEntry {
id: id.to_string(),
flags,
path: path.to_path_buf(),
})
}
pub(crate) fn create<P: AsRef<Path>, S: ToString>(
id: S,
path: P,
data: &[u8],
) -> Result<Self, Error> {
let path = path.as_ref();
fs::write(path, data)?;
Ok(MailEntry {
id: id.to_string(),
flags: HashSet::new(),
path: path.to_path_buf(),
})
}
fn update(&mut self) -> Result<(), Error> {
let mut new_file_name = self.id.clone();
let flags = self.flags_to_string();
if !flags.is_empty() {
new_file_name.push_str(SEP);
new_file_name.push_str(&flags);
}
let prev_path = self.path.clone();
let new_path = self.path.with_file_name(new_file_name);
match fs::rename(prev_path, &new_path) {
Ok(_) => {
self.path = new_path;
Ok(())
}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
Err(Error::AlreadyExistsError(new_path))
}
Err(e) => Err(e.into()),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn set_id<S: ToString>(&mut self, id: S) -> Result<(), Error> {
self.id = validate_id(id.to_string())?;
self.update()
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn to_path_buf(self) -> PathBuf {
self.path
}
fn move_to(&mut self, folder: &str) -> Result<(), Error> {
let parent = self
.path
.parent()
.ok_or_else(|| Error::NoParentError(self.path.clone()))?;
if parent.file_name() == Some(folder.as_ref()) {
return Ok(());
}
let new_path = parent
.parent()
.ok_or_else(|| Error::NoParentError(parent.to_path_buf()))?
.join(folder)
.join(self.path.file_name().unwrap());
fs::rename(&self.path, &new_path)?;
self.path = new_path;
Ok(())
}
pub fn move_to_new(&mut self) -> Result<(), Error> {
self.move_to(NEW)
}
pub fn move_to_cur(&mut self) -> Result<(), Error> {
self.move_to(CUR)
}
pub fn move_to_tmp(&mut self) -> Result<(), Error> {
self.move_to(TMP)
}
pub fn flags(&self) -> impl Iterator<Item = &Flag> {
self.flags.iter()
}
pub fn flags_to_string(&self) -> String {
let mut flags: Vec<&str> = self.flags().map(AsRef::as_ref).collect();
flags.sort();
flags.join("")
}
pub fn set_flag(&mut self, flag: Flag) -> Result<(), Error> {
if self.flags.insert(flag) {
self.update()?;
}
Ok(())
}
pub fn unset_flag(&mut self, flag: Flag) -> Result<(), Error> {
if self.flags.remove(&flag) {
self.update()?;
}
Ok(())
}
pub fn has_flag(&self, flag: Flag) -> bool {
self.flags.contains(&flag)
}
pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
read(&self.path)
}
}
pub struct MailEntries {
readdir: Option<ReadDir>,
move_to_cur: bool,
}
impl MailEntries {
pub(crate) fn new<P: AsRef<Path>>(path: P, move_to_cur: bool) -> MailEntries {
MailEntries {
readdir: read_dir(path).ok(),
move_to_cur,
}
}
}
impl Iterator for MailEntries {
type Item = Result<MailEntry, Error>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ref mut readdir) = self.readdir {
for entry in readdir {
let path = match entry {
Err(e) => return Some(Err(e.into())),
Ok(e) => e.path(),
};
if path.is_dir()
|| path
.file_name()
.map_or(true, |n| n.to_string_lossy().starts_with('.'))
{
continue;
}
let mut entry = MailEntry::from_path(path);
if self.move_to_cur {
if let Ok(ref mut entry) = entry {
if let Err(e) = entry.move_to_cur() {
return Some(Err(e));
}
}
}
return Some(entry);
}
}
None
}
}