use std::env;
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use super::PersistableTempFile;
pub struct Sponge {
dest: PathBuf,
temp: io::BufWriter<PersistableTempFile>,
}
impl Sponge {
pub fn new_for<P: AsRef<Path>>(path: P) -> Result<Sponge, io::Error> {
let path = path.as_ref();
let path = if path.is_absolute() {
path.to_path_buf()
} else {
let mut absolute = env::current_dir()?;
absolute.push(path);
absolute
};
let parent = path
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "path must have a parent"))?;
fs::create_dir_all(parent)?;
Ok(Sponge {
temp: io::BufWriter::new(PersistableTempFile::new_in(parent)?),
dest: path,
})
}
pub fn commit(self) -> Result<(), io::Error> {
let temp = self.temp.into_inner()?;
copy_metadata(&self.dest, temp.as_ref())?;
temp.persist_by_rename(self.dest)
.map_err(|persist_error| persist_error.error)?;
Ok(())
}
}
impl io::Write for Sponge {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
self.temp.write(buf)
}
fn flush(&mut self) -> Result<(), io::Error> {
self.temp.flush()
}
}
fn copy_metadata(source: &Path, dest: &fs::File) -> Result<(), io::Error> {
let metadata = match source.metadata() {
Ok(metadata) => metadata,
Err(ref e) if io::ErrorKind::NotFound == e.kind() => {
return Ok(());
}
Err(e) => Err(e)?,
};
dest.set_permissions(metadata.permissions())?;
#[cfg(unix)]
unix_chown::chown(metadata, dest)?;
Ok(())
}
#[cfg(unix)]
mod unix_chown {
use std::fs;
use std::io;
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::AsRawFd;
pub fn chown(source: fs::Metadata, dest: &fs::File) -> Result<(), io::Error> {
let fd = dest.as_raw_fd();
zero_success(unsafe { libc::fchown(fd, source.uid(), source.gid()) })?;
Ok(())
}
fn zero_success(err: libc::c_int) -> Result<(), io::Error> {
if 0 == err {
return Ok(());
}
Err(io::Error::last_os_error())
}
}