use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use crate::Error;
pub struct Destination {
out: PathBuf,
written: PathBuf,
file: File,
atomic: bool,
}
impl Destination {
pub fn open(out: &Path, atomic: bool) -> Result<Self, Error> {
let written = if atomic {
let name = out.file_name().unwrap_or_default();
let mut temp = name.to_os_string();
temp.push(format!(".ferrosys-{}.tmp", std::process::id()));
out.with_file_name(temp)
} else {
out.to_path_buf()
};
let mut options = OpenOptions::new();
options.write(true);
if atomic {
options.create_new(true);
} else {
options.create(true).truncate(true);
}
let file = options.open(&written).map_err(|e| Error::io(&written, e))?;
Ok(Self {
out: out.to_path_buf(),
written,
file,
atomic,
})
}
pub fn file(&mut self) -> &mut File {
&mut self.file
}
pub fn written(&self) -> &Path {
&self.written
}
pub fn commit(self) -> Result<(), Error> {
if !self.atomic {
return Ok(());
}
self.file
.sync_all()
.map_err(|e| Error::io(&self.written, e))?;
std::fs::rename(&self.written, &self.out).map_err(|e| Error::io(&self.out, e))?;
if let Some(parent) = self.out.parent().filter(|p| !p.as_os_str().is_empty())
&& let Ok(dir) = File::open(parent)
{
let _ = dir.sync_all();
}
Ok(())
}
}
impl Drop for Destination {
fn drop(&mut self) {
if self.atomic {
let _ = std::fs::remove_file(&self.written);
}
}
}