use std::{
fs::{self, File, OpenOptions},
io,
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use crate::DryIceError;
static TEMP_FILE_COUNTER: AtomicU64 = AtomicU64::new(0);
pub struct TempDryIceFile {
path: PathBuf,
cleanup_on_drop: bool,
}
impl TempDryIceFile {
pub fn new() -> Result<Self, DryIceError> {
Self::new_in(std::env::temp_dir())
}
pub fn new_in<P: AsRef<Path>>(directory: P) -> Result<Self, DryIceError> {
let directory = directory.as_ref();
let path = create_unique_temp_file(directory)?;
Ok(Self {
path,
cleanup_on_drop: true,
})
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn open(&self) -> Result<File, DryIceError> {
Ok(OpenOptions::new().read(true).write(true).open(&self.path)?)
}
pub fn cleanup(mut self) -> Result<(), DryIceError> {
if !self.cleanup_on_drop {
return Ok(());
}
remove_temp_file(&self.path)?;
self.cleanup_on_drop = false;
Ok(())
}
pub fn persist<P: AsRef<Path>>(&mut self, path: P) -> Result<PathBuf, DryIceError> {
let destination = path.as_ref().to_path_buf();
if destination.exists() {
return Err(DryIceError::Io(io::Error::new(
io::ErrorKind::AlreadyExists,
"persist destination already exists",
)));
}
fs::rename(&self.path, &destination)?;
self.cleanup_on_drop = false;
self.path.clone_from(&destination);
Ok(destination)
}
}
impl Drop for TempDryIceFile {
fn drop(&mut self) {
if !self.cleanup_on_drop {
return;
}
if let Err(error) = remove_temp_file(&self.path) {
log::warn!(
"failed to clean up temporary dryice file `{}`: {error}",
self.path.display()
);
}
}
}
fn create_unique_temp_file(directory: &Path) -> Result<PathBuf, DryIceError> {
let pid = std::process::id();
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
for _ in 0..100 {
let counter = TEMP_FILE_COUNTER.fetch_add(1, Ordering::Relaxed);
let candidate = directory.join(format!("dryice-{pid}-{nanos}-{counter}.dryice"));
match OpenOptions::new()
.write(true)
.create_new(true)
.open(&candidate)
{
Ok(file) => {
drop(file);
return Ok(candidate);
},
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {},
Err(error) => return Err(DryIceError::Io(error)),
}
}
Err(DryIceError::Io(io::Error::new(
io::ErrorKind::AlreadyExists,
"could not create a unique temporary dryice file",
)))
}
fn remove_temp_file(path: &Path) -> Result<(), DryIceError> {
match fs::remove_file(path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(DryIceError::Io(error)),
}
}