use std::ffi::{OsStr, OsString};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use tempfile::NamedTempFile;
use zoog::Error;
#[derive(Debug)]
enum FileEnum {
Temp(tempfile::NamedTempFile, PathBuf),
Sink,
}
#[derive(Debug)]
pub struct OutputFile {
file_enum: FileEnum,
}
fn make_sibling_temporary_file(path: &Path, distinguisher: &OsStr) -> Result<NamedTempFile, Error> {
let parent_dir = path.parent().ok_or_else(|| Error::NoParentError(path.to_path_buf()))?;
let file_stem = path.file_stem().ok_or_else(|| Error::NotAFilePath(path.to_path_buf()))?;
let file_ext = path.extension().map(|e| {
let mut ext = OsString::from(".");
ext.push(e);
ext
});
let file_stem = {
let mut stem = file_stem.to_os_string();
stem.push("-");
stem.push(distinguisher);
stem
};
let mut builder = tempfile::Builder::new();
builder.prefix(&file_stem);
if let Some(file_ext) = file_ext.as_ref() {
builder.suffix(file_ext);
}
let temp = builder.tempfile_in(parent_dir).map_err(|e| Error::TempFileOpenError(parent_dir.to_path_buf(), e))?;
Ok(temp)
}
impl OutputFile {
pub fn new_sink() -> OutputFile { OutputFile { file_enum: FileEnum::Sink } }
pub fn new_target(path: &Path) -> Result<OutputFile, Error> {
let temp = make_sibling_temporary_file(path, OsStr::new("new"))?;
Ok(OutputFile { file_enum: FileEnum::Temp(temp, path.to_path_buf()) })
}
pub fn new_target_or_discard(path: &Path, discard: bool) -> Result<OutputFile, Error> {
if discard {
Ok(Self::new_sink())
} else {
Self::new_target(path)
}
}
#[allow(dead_code)]
pub fn abort(self) -> Result<(), Error> {
match self.file_enum {
FileEnum::Sink => {}
FileEnum::Temp(temp, _) => {
let temp_path = temp.path().to_path_buf();
temp.close().map_err(|e| Error::FileDelete(temp_path, e))?;
}
}
Ok(())
}
pub fn commit(self) -> Result<(), Error> {
match self.file_enum {
FileEnum::Sink => {}
FileEnum::Temp(temp, final_path) => {
temp.as_file().sync_all().map_err(Error::WriteError)?;
temp.persist(final_path)
.map_err(Error::PersistError)
.and_then(|f| f.sync_all().map_err(Error::WriteError))?;
}
}
Ok(())
}
}
impl Write for OutputFile {
fn write(&mut self, data: &[u8]) -> Result<usize, io::Error> {
match &mut self.file_enum {
FileEnum::Sink => Ok(data.len()),
FileEnum::Temp(ref mut temp, _) => temp.write(data),
}
}
fn flush(&mut self) -> Result<(), io::Error> {
match &mut self.file_enum {
FileEnum::Sink => Ok(()),
FileEnum::Temp(ref mut temp, _) => temp.flush(),
}
}
}