#![cfg_attr(docsrs, feature(doc_cfg))]
use std::borrow::{Borrow, BorrowMut};
use std::fmt::{Debug, Formatter};
use std::io::{IoSlice, SeekFrom};
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::path::{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};
#[cfg(not(feature = "uuid"))]
use crate::random_name::RandomName;
use crate::Error;
use crate::Ownership;
#[cfg(feature = "uuid")]
use uuid::Uuid;
const FILE_PREFIX: &str = "atmp_";
pub struct TempFile {
file: ManuallyDrop<File>,
core: ManuallyDrop<Arc<TempFileCore>>,
}
struct TempFileCore {
path: PathBuf,
file: ManuallyDrop<File>,
ownership: Ownership,
}
impl TempFile {
pub async fn new() -> Result<Self, Error> {
Self::new_in(Self::default_dir()).await
}
pub async fn new_with_name<N: AsRef<str>>(name: N) -> Result<Self, Error> {
Self::new_with_name_in(name, Self::default_dir()).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
#[cfg(feature = "uuid")]
pub async fn new_with_uuid(uuid: Uuid) -> Result<Self, Error> {
Self::new_with_uuid_in(uuid, Self::default_dir()).await
}
pub async fn new_in<P: Borrow<Path>>(dir: P) -> Result<Self, Error> {
#[cfg(feature = "uuid")]
{
let id = Uuid::new_v4();
Self::new_with_uuid_in(id, dir).await
}
#[cfg(not(feature = "uuid"))]
{
let name = RandomName::new(FILE_PREFIX);
Self::new_with_name_in(name, dir).await
}
}
pub async fn new_with_name_in<N: AsRef<str>, P: Borrow<Path>>(
name: N,
dir: P,
) -> Result<Self, Error> {
let dir = dir.borrow();
if !dir.is_dir() {
return Err(Error::InvalidDirectory);
}
let file_name = name.as_ref();
let mut path = PathBuf::from(dir);
path.push(file_name);
Self::new_internal(path, Ownership::Owned).await
}
#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))]
#[cfg(feature = "uuid")]
pub async fn new_with_uuid_in<P: Borrow<Path>>(uuid: Uuid, dir: P) -> Result<Self, Error> {
let file_name = format!("{}{}", FILE_PREFIX, uuid);
Self::new_with_name_in(file_name, dir).await
}
pub async fn from_existing<P: Borrow<Path>>(
path: P,
ownership: Ownership,
) -> Result<Self, Error> {
if !path.borrow().is_file() {
return Err(Error::InvalidFile);
}
Self::new_internal(path, ownership).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),
})
}
pub async fn open_ro(&self) -> Result<TempFile, Error> {
let file = OpenOptions::new()
.read(true)
.write(false)
.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 {
self.core.ownership
}
pub async fn drop_async(self) {
tokio::task::spawn_blocking(move || drop(self)).await.ok();
}
async fn new_internal<P: Borrow<Path>>(path: P, ownership: Ownership) -> Result<Self, Error> {
let path = path.borrow();
let core = TempFileCore {
file: ManuallyDrop::new(
OpenOptions::new()
.create(ownership == Ownership::Owned)
.read(false)
.write(true)
.open(path)
.await?,
),
ownership,
path: PathBuf::from(path),
};
let file = OpenOptions::new().read(true).write(true).open(path).await?;
Ok(Self {
file: ManuallyDrop::new(file),
core: ManuallyDrop::new(Arc::new(core)),
})
}
#[inline(always)]
fn default_dir() -> PathBuf {
std::env::temp_dir()
}
}
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 self.ownership != Ownership::Owned {
return;
}
drop(unsafe { ManuallyDrop::take(&mut self.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, std::io::Error>> {
Pin::new(self.file.deref_mut()).poll_write(cx, buf)
}
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Pin::new(self.file.deref_mut()).poll_flush(cx)
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::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, std::io::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)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random_name::RandomName;
#[test]
fn test_random_name() {
let name = RandomName::new(FILE_PREFIX);
assert!(name.as_ref().starts_with(FILE_PREFIX))
}
}