use std::fs::{self, File};
use std::io::ErrorKind;
use std::io::prelude::*;
use std::os::unix::prelude::*;
use std::path::{PathBuf, Path};
use std::process;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{DeliveryDurability, Result};
use gethostname::gethostname;
use libc;
pub struct EmailFilenameGenerator {
count: usize,
max_seen_unix_time: u64,
hostname: String,
}
impl EmailFilenameGenerator {
pub fn new() -> Self {
let hostname =
gethostname()
.to_string_lossy()
.into_owned()
.replace("/", r"\057")
.replace(":", r"\072");
EmailFilenameGenerator{
count: 0,
max_seen_unix_time: 0,
hostname: hostname,
}
}
}
impl Iterator for EmailFilenameGenerator {
type Item = String;
fn next(&mut self) -> Option<String> {
let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let pid = process::id();
if self.max_seen_unix_time < unix_time {
self.max_seen_unix_time = unix_time;
self.count = 0;
} else {
self.count += 1;
}
Some(format!("{}.{}_{}.{}", unix_time, pid, self.count, self.hostname))
}
}
pub struct Maildir {
root: PathBuf,
email_filename_gen: Arc<Mutex<EmailFilenameGenerator>>,
}
impl Maildir {
pub fn open_or_create(
mailbox: &Path,
email_filename_gen: Arc<Mutex<EmailFilenameGenerator>>
) -> Result<Self> {
let root = PathBuf::from(mailbox);
for s in &["tmp", "new", "cur"] {
let path = root.join(&s);
fs::create_dir_all(&path)?;
}
Ok(Maildir{root, email_filename_gen})
}
pub fn deliver(
&self,
data: &[u8],
delivery_durability: DeliveryDurability
) -> Result<PathBuf> {
loop {
let tmp_dir = self.root.join("tmp");
let new_dir = self.root.join("new");
let tmp_email = self.write_email_to_dir(data, &tmp_dir)?;
let new_email = new_dir.join(
tmp_email.file_name().ok_or("")?.to_str().ok_or("")?);
let result = fs::hard_link(&tmp_email, &new_email);
fs::remove_file(&tmp_email)?;
match result {
Ok(_) => {
if delivery_durability == DeliveryDurability::FileAndDirSync {
File::open(&new_dir)?.sync_all()?;
File::open(&tmp_dir)?.sync_all()?;
}
return Ok(new_email);
},
Err(ref err) if err.kind() == ErrorKind::AlreadyExists => {},
Err(err) => return Err(err.into()),
}
}
}
pub fn deliver_with_hard_link(
&self,
src: &Path,
delivery_durability: DeliveryDurability
) -> Result<PathBuf> {
loop {
let new_dir = self.root.join("new");
let new_email = new_dir.join(self.next_email_filename_candidate()?);
match fs::hard_link(&src, &new_email) {
Ok(_) => {
if delivery_durability == DeliveryDurability::FileAndDirSync {
File::open(&new_dir)?.sync_all()?;
}
return Ok(new_email);
},
Err(ref err) if err.kind() == ErrorKind::AlreadyExists => {},
Err(err) => return Err(err.into()),
}
}
}
fn write_email_to_dir(&self, data: &[u8], dir: &Path) -> Result<PathBuf> {
loop {
let email = dir.join(self.next_email_filename_candidate()?);
let result = fs::OpenOptions::new()
.create_new(true)
.write(true)
.custom_flags(libc::O_SYNC)
.open(&email);
match result {
Ok(mut f) => {
f.write_all(&data)?;
return Ok(email);
},
Err(ref err) if err.kind() == ErrorKind::AlreadyExists => {},
Err(err) => return Err(err.into()),
}
}
}
fn next_email_filename_candidate(&self) -> Result<String> {
let mut gen = self.email_filename_gen.lock().map_err(|_| "")?;
gen.next().ok_or("".into())
}
}