use std::borrow::{Borrow, BorrowMut};
use std::fmt::{Debug, Formatter};
use std::io::{Error, IoSlice, SeekFrom};
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
use uuid::Uuid;
pub struct TempFile {
file: ManuallyDrop<File>,
core: ManuallyDrop<Arc<Box<TempFileCore>>>,
}
#[derive(Debug, Eq, PartialEq)]
pub enum Ownership {
Owned,
Borrowed
}
struct TempFileCore {
path: PathBuf,
file: Option<File>,
}
impl TempFile {
pub async fn new() -> Result<Self, Error> {
Self::new_in(std::env::temp_dir()).await
}
pub async fn new_in<P: Borrow<PathBuf>>(dir: P) -> Result<Self, Error> {
let dir = dir.borrow();
assert!(dir.is_dir()); let file_name = format!("img_pp_{}", Uuid::new_v4());
let mut path = dir.clone();
path.push(file_name);
Self::new_internal(path, Ownership::Owned).await
}
pub async fn from_existing(path: PathBuf) -> Result<Self, Error> {
debug_assert!(path.is_file()); Self::new_internal(path, Ownership::Borrowed).await
}
pub fn file_path(&self) -> &PathBuf {
&self.core.path
}
pub async fn open_rw(&self) -> Result<TempFile, Error> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&self.core.path)
.await?;
Ok(TempFile {
core: self.core.clone(),
file: ManuallyDrop::new(file),
})
}
#[allow(dead_code)]
pub async fn try_clone(&self) -> Result<TempFile, Error> {
Ok(TempFile {
core: self.core.clone(),
file: ManuallyDrop::new(self.file.try_clone().await?),
})
}
pub fn ownership(&self) -> Ownership {
match self.core.file {
Some(_) => Ownership::Owned,
_ => Ownership::Borrowed
}
}
async fn new_internal(path: PathBuf, ownership: Ownership) -> Result<Self, Error> {
let core = TempFileCore {
file: if ownership == Ownership::Owned {
Some(
OpenOptions::new()
.create_new(true)
.read(false)
.write(true)
.open(path.clone())
.await?,
)
} else {
None
},
path: path.clone(),
};
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path.clone())
.await?;
Ok(Self {
file: ManuallyDrop::new(file),
core: ManuallyDrop::new(Arc::new(Box::new(core))),
})
}
}
impl Drop for TempFile {
fn drop(&mut self) {
drop(unsafe { ManuallyDrop::take(&mut self.file) });
drop(unsafe { ManuallyDrop::take(&mut self.core) });
}
}
impl Drop for TempFileCore {
fn drop(&mut self) {
if let Some(file) = self.file.take() {
drop(file);
let _ = std::fs::remove_file(&self.path);
}
}
}
impl Debug for TempFileCore {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.path)
}
}
impl Debug for TempFile {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.core)
}
}
impl Deref for TempFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl DerefMut for TempFile {
fn deref_mut(&mut self) -> &mut File {
&mut self.file
}
}
impl Borrow<File> for TempFile {
fn borrow(&self) -> &File {
&self.file
}
}
impl BorrowMut<File> for TempFile {
fn borrow_mut(&mut self) -> &mut File {
&mut self.file
}
}
impl AsRef<File> for TempFile {
fn as_ref(&self) -> &File {
&self.file
}
}
impl AsyncWrite for TempFile {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, Error>> {
Pin::new(self.file.deref_mut()).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
Pin::new(self.file.deref_mut()).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
Pin::new(self.file.deref_mut()).poll_shutdown(cx)
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<Result<usize, Error>> {
Pin::new(self.file.deref_mut()).poll_write_vectored(cx, bufs)
}
}
impl AsyncRead for TempFile {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(self.file.deref_mut()).poll_read(cx, buf)
}
}
impl AsyncSeek for TempFile {
fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
Pin::new(self.file.deref_mut()).start_seek(position)
}
fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<u64>> {
Pin::new(self.file.deref_mut()).poll_complete(cx)
}
}