use std::fs::{self, read_dir, File, ReadDir};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use crate::flags::{self, Flags};
use crate::{util, Error, M2dir, ID};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Message {
pub(crate) id: ID,
pub(crate) path: PathBuf,
}
impl TryFrom<&Path> for Message {
type Error = Error;
fn try_from(path: &Path) -> Result<Self, Error> {
if !path.is_file() {
return Err(Error::MessageNotFound);
}
let fname = path.file_name().unwrap().to_string_lossy();
let (_, id) = fname
.rsplit_once(',')
.ok_or(Error::InvalidFileName(path.to_path_buf()))?;
Ok(Message {
id: ID::try_from(id.to_string())?,
path: PathBuf::from(path),
})
}
}
impl Message {
pub fn path(&self) -> &Path {
&self.path
}
pub fn id(&self) -> &ID {
&self.id
}
pub fn flags_path(&self) -> PathBuf {
flags::flags_path_for(self.path.parent().unwrap(), self.id.as_ref())
}
pub fn flags(&self) -> Result<Flags, Error> {
let flags_path = self.flags_path();
if !flags_path.exists() {
return Ok(Flags::default());
}
let file = File::open(&flags_path)?;
Flags::parse_file(file).map_err(|e| Error::Flags(flags_path, e))
}
pub fn set_flags(&self, flags: &Flags) -> Result<(), Error> {
let flags_path = self.flags_path();
fs::create_dir_all(flags_path.parent().unwrap())
.map_err(|e| Error::WriteFlags(flags_path.clone(), e))?;
let mut file =
File::create(&flags_path).map_err(|e| Error::WriteFlags(flags_path.clone(), e))?;
flags
.write_file(&mut file)
.map_err(|e| Error::WriteFlags(flags_path, e))?;
Ok(())
}
pub fn copy_to(&self, target: &M2dir) -> Result<Message, Error> {
let src_flags = self.flags_path();
let dst_flags = flags::flags_path_for(&target.path, self.id.as_ref());
let dst = PathBuf::from_iter([target.path.as_os_str(), self.path.file_name().unwrap()]);
fs::create_dir_all(dst_flags.parent().unwrap())?;
if src_flags.exists() {
util::copy_atomic(src_flags, &dst_flags)?;
}
util::copy_atomic(&self.path, &dst).inspect_err(|_| {
_ = fs::remove_file(&dst_flags);
})?;
Ok(Message {
id: self.id.clone(),
path: dst,
})
}
pub fn move_to(&mut self, target: &M2dir) -> Result<(), Error> {
let src_flags = self.flags_path();
let dst_flags = flags::flags_path_for(&target.path, self.id.as_ref());
let dst = PathBuf::from_iter([target.path.as_os_str(), self.path.file_name().unwrap()]);
fs::create_dir_all(dst_flags.parent().unwrap())?;
if src_flags.exists() {
fs::rename(&src_flags, &dst_flags)?;
}
fs::rename(&self.path, &dst).inspect_err(|_| {
_ = fs::rename(&dst_flags, &src_flags);
})?;
self.path = dst;
Ok(())
}
pub fn delete(self) -> Result<(), Error> {
let flags = self.flags_path();
fs::remove_file(self.path())?;
if flags.exists() {
fs::remove_file(self.flags_path())?;
}
Ok(())
}
}
pub struct Messages {
path: PathBuf,
readdir: Option<ReadDir>,
}
impl Messages {
pub(crate) fn new(path: PathBuf) -> Messages {
Messages {
path,
readdir: None,
}
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl Iterator for Messages {
type Item = Result<Message, Error>;
fn next(&mut self) -> Option<Result<Message, Error>> {
if self.readdir.is_none() {
self.readdir = match read_dir(&self.path) {
Err(_) => return None,
Ok(v) => Some(v),
};
}
loop {
let dir_entry = self.readdir.iter_mut().next().unwrap().next();
let result = dir_entry.map(|e| {
let entry = e?;
let ftype = entry.file_type()?;
if ftype.is_dir() {
return Ok(None);
}
let filename = String::from(entry.file_name().to_string_lossy().deref());
if filename.starts_with('.') {
return Ok(None);
}
Ok(Some(Message::try_from(entry.path().as_path())?))
});
return match result {
None => None,
Some(Err(e)) => Some(Err(e)),
Some(Ok(None)) => continue,
Some(Ok(Some(v))) => Some(Ok(v)),
};
}
}
}