#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{
fs,
io::Write,
path::{Path, PathBuf},
time::{Duration, Instant}
};
pub trait TmpProc {
type Output;
type Error;
fn update(&mut self, buf: &[u8]);
fn finalize(
&mut self,
src: Option<&Path>
) -> Result<(Self::Output, Option<PathBuf>), Self::Error>;
}
pub struct NullProc<'a>(&'a Path);
impl TmpProc for NullProc<'_> {
type Output = ();
type Error = ();
#[allow(unused_variables)]
fn update(&mut self, buf: &[u8]) {}
#[allow(unused_variables)]
fn finalize(
&mut self,
src: Option<&Path>
) -> Result<(Self::Output, Option<PathBuf>), Self::Error> {
Ok(((), Some(self.0.to_path_buf())))
}
}
#[derive(Debug)]
pub enum Output {
File(PathBuf),
Buf(Vec<u8>)
}
impl Output {
pub fn try_into_fname(self) -> Result<PathBuf, Self> {
match self {
Self::File(fname) => Ok(fname),
r @ Self::Buf(_) => Err(r)
}
}
#[must_use]
pub fn unwrap_fname(self) -> PathBuf {
let Self::File(fname) = self else {
panic!("Not a file name");
};
fname
}
pub fn try_into_buf(self) -> Result<Vec<u8>, Self> {
match self {
Self::Buf(buf) => Ok(buf),
r @ Self::File(_) => Err(r)
}
}
#[must_use]
pub fn unwrap_buf(self) -> Vec<u8> {
let Self::Buf(buf) = self else {
panic!("Not a buffer");
};
buf
}
}
#[non_exhaustive]
pub struct Persisted<T> {
pub output: Output,
pub size: u64,
pub procres: T,
pub duration: Duration
}
struct MemBuf {
buf: Vec<u8>,
idx: usize
}
pub struct TmpFile<T, E>
where
E: From<std::io::Error>
{
tmpfile: PathBuf,
f: Option<Box<dyn Write + Send>>,
tp: Box<dyn TmpProc<Output = T, Error = E> + Send>,
size: u64,
start_time: Instant,
membuf: Option<MemBuf>,
#[cfg(feature = "defer-persist")]
sctx: Option<swctx::SetCtx<Persisted<T>, (), E>>
}
impl<T, E> TmpFile<T, E>
where
E: From<std::io::Error>
{
fn inner_persist(&mut self) -> Result<Persisted<T>, E> {
if let Some(f) = self.f.take() {
drop(f);
}
let (output, t) = if let Some(ref mut membuf) = self.membuf {
let mut buf = std::mem::take(&mut membuf.buf);
buf.truncate(membuf.idx);
let (t, _) = self.tp.finalize(None)?;
(Output::Buf(buf), t)
} else {
let (t, outfile) = self.tp.finalize(Some(&self.tmpfile))?;
let outfile = outfile.expect("An output file was not specified.");
if !outfile.exists() {
fs::hard_link(&self.tmpfile, &outfile)?;
}
(Output::File(outfile), t)
};
Ok(Persisted {
output,
size: self.size,
procres: t,
duration: self.start_time.elapsed()
})
}
}
impl<T, E> TmpFile<T, E>
where
E: From<std::io::Error>
{
pub fn new<P>(
fname: P,
tp: Box<dyn TmpProc<Output = T, Error = E> + Send>
) -> Result<Self, std::io::Error>
where
P: AsRef<Path>
{
let tmpfile = fname.as_ref().to_path_buf();
let f = fs::File::create(&tmpfile)?;
let f = Box::new(f);
Ok(Self {
tmpfile,
f: Some(f),
tp,
size: 0,
start_time: Instant::now(),
membuf: None,
#[cfg(feature = "defer-persist")]
sctx: None
})
}
pub fn with_minsize<P>(
fname: P,
tp: Box<dyn TmpProc<Output = T, Error = E> + Send>,
minsize: usize
) -> Result<Self, std::io::Error>
where
P: AsRef<Path>
{
let tmpfile = fname.as_ref().to_path_buf();
let f = fs::File::create(&tmpfile)?;
let f = Box::new(f);
let membuf = MemBuf {
buf: vec![0u8; minsize],
idx: 0
};
let membuf = Some(membuf);
Ok(Self {
tmpfile,
f: Some(f),
tp,
size: 0,
start_time: Instant::now(),
membuf,
#[cfg(feature = "defer-persist")]
sctx: None
})
}
#[cfg_attr(
feature = "defer-persist",
doc = r"
# Panics
If the `defer-persist` feature is used: If the `TmpFile` has previously
registered to receive the finalization results via a channel using
[`TmpFile::defer_persist()`] this method will cause a panic.
"
)]
pub fn persist(mut self) -> Result<Persisted<T>, E> {
#[cfg(feature = "defer-persist")]
assert!(
self.sctx.is_none(),
"Con not persist TmpFile that has been configured for deferred persist"
);
self.inner_persist()
}
#[cfg(feature = "defer-persist")]
#[cfg_attr(docsrs, doc(cfg(feature = "defer-persist")))]
pub fn defer_persist(&mut self) -> swctx::WaitCtx<Persisted<T>, (), E> {
assert!(
self.sctx.is_none(),
"TmpFile already configured for deferred persist"
);
let (sctx, wctx) = swctx::mkpair();
self.sctx = Some(sctx);
wctx
}
#[cfg(feature = "defer-persist")]
#[cfg_attr(docsrs, doc(cfg(feature = "defer-persist")))]
pub fn cancel(mut self) {
let _ = self.sctx.take();
}
}
impl<T, E> Write for TmpFile<T, E>
where
E: From<std::io::Error>
{
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
if let Some(ref mut membuf) = self.membuf {
if membuf.idx + buf.len() > membuf.buf.len() {
let f = fs::File::create(&self.tmpfile)?;
let mut f = Box::new(f);
if membuf.idx > 0 {
f.write_all(&membuf.buf[..membuf.idx])?;
}
self.f = Some(f);
self.membuf = None;
} else {
membuf.buf[membuf.idx..(membuf.idx + buf.len())].copy_from_slice(buf);
membuf.idx += buf.len();
self.size += buf.len() as u64;
self.tp.update(buf);
return Ok(buf.len());
}
}
let Some(ref mut f) = self.f else {
panic!("No file?");
};
let n = f.write(buf)?;
self.tp.update(&buf[..n]);
self.size += n as u64;
Ok(n)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
if let Some(ref mut f) = self.f {
f.flush()?;
}
Ok(())
}
}
impl<T, E> Drop for TmpFile<T, E>
where
E: From<std::io::Error>
{
fn drop(&mut self) {
if let Some(f) = self.f.take() {
drop(f);
}
#[cfg(feature = "defer-persist")]
if let Some(sctx) = self.sctx.take() {
match self.inner_persist() {
Ok(res) => {
let _ = sctx.set(res);
}
Err(e) => {
let _ = sctx.fail(e);
}
}
}
if let Err(_e) = fs::remove_file(&self.tmpfile) {
}
}
}