use std::ffi::OsString;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, BufReader, Read, Seek};
use std::path::PathBuf;
use std::sync::Arc;
use educe::Educe;
pub use tempfile;
use tempfile::NamedTempFile;
use super::StorageProvider;
use crate::WrapIoResult;
type TempfileFnType = Arc<dyn Fn() -> io::Result<NamedTempFile> + Send + Sync + 'static>;
#[derive(Clone, Educe)]
#[educe(Debug)]
struct TempfileFn(#[educe(Debug = false)] TempfileFnType);
#[derive(Default, Clone, Debug)]
pub struct TempStorageProvider {
storage_dir: Option<PathBuf>,
prefix: Option<OsString>,
tempfile_fn: Option<TempfileFn>,
}
impl TempStorageProvider {
pub fn new() -> Self {
Self::default()
}
pub fn new_in<T>(path: T) -> Self
where
T: Into<PathBuf>,
{
Self {
storage_dir: Some(path.into()),
tempfile_fn: None,
prefix: None,
}
}
pub fn with_prefix<T>(prefix: T) -> Self
where
T: Into<OsString>,
{
Self {
storage_dir: None,
tempfile_fn: None,
prefix: Some(prefix.into()),
}
}
pub fn with_prefix_in<S, P>(prefix: S, path: P) -> Self
where
S: Into<OsString>,
P: Into<PathBuf>,
{
Self {
storage_dir: Some(path.into()),
tempfile_fn: None,
prefix: Some(prefix.into()),
}
}
pub fn with_tempfile_builder<F: Fn() -> io::Result<NamedTempFile> + Send + Sync + 'static>(
builder: F,
) -> Self {
Self {
storage_dir: None,
prefix: None,
tempfile_fn: Some(TempfileFn(Arc::new(builder))),
}
}
}
impl StorageProvider for TempStorageProvider {
type Reader = TempStorageReader;
type Writer = File;
fn into_reader_writer(
self,
_content_length: Option<u64>,
) -> io::Result<(Self::Reader, Self::Writer)> {
let tempfile = if let Some(tempfile_fn) = self.tempfile_fn {
(tempfile_fn.0)()
} else {
let mut builder = tempfile::Builder::new();
let mut builder_mut = &mut builder;
if let Some(prefix) = &self.prefix {
builder_mut = builder_mut.prefix(prefix);
}
self.storage_dir.map_or_else(
|| builder_mut.tempfile(),
|dir| builder_mut.tempfile_in(dir),
)
}
.wrap_err("error creating temp file")?;
let handle = tempfile.reopen().wrap_err("error reopening temp file")?;
let reader = TempStorageReader {
reader: BufReader::new(tempfile),
};
let writer = handle
.try_clone()
.wrap_err("error cloning temporary file")?;
Ok((reader, writer))
}
}
#[derive(Debug)]
pub struct TempStorageReader {
reader: BufReader<NamedTempFile>,
}
impl Read for TempStorageReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
}
impl Seek for TempStorageReader {
fn seek(&mut self, position: io::SeekFrom) -> io::Result<u64> {
self.reader.seek(position)
}
}