#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(unsafe_code)]
mod errors;
pub use errors::Error;
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::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::SystemTime;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
#[cfg(feature = "uuid")]
use uuid::Uuid;
const FILE_PREFIX: &str = "atmp_";
pub struct TempFile {
file: ManuallyDrop<File>,
core: ManuallyDrop<Arc<TempFileCore>>,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Ownership {
Owned,
Borrowed,
}
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<PathBuf>>(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();
Self::new_with_name_in(name, dir).await
}
}
pub async fn new_with_name_in<N: AsRef<str>, P: Borrow<PathBuf>>(
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 = dir.clone();
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<PathBuf>>(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(path: PathBuf, ownership: Ownership) -> Result<Self, Error> {
if !path.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
}
async fn new_internal(path: PathBuf, ownership: Ownership) -> Result<Self, Error> {
let core = TempFileCore {
file: ManuallyDrop::new(
OpenOptions::new()
.create(ownership == Ownership::Owned)
.read(false)
.write(true)
.open(path.clone())
.await?,
),
ownership,
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(core)),
})
}
#[inline(always)]
fn default_dir() -> PathBuf {
std::env::temp_dir()
}
}
struct RandomName {
name: String,
}
impl RandomName {
#[allow(dead_code)]
fn new() -> Self {
let pid = std::process::id();
let marker = &pid as *const _ as usize;
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(std::time::Duration::from_secs(0));
let (secs, subsec_nanos) = (now.as_secs(), now.subsec_nanos());
Self {
name: format!(
"{}{}{:x}{:x}{:x}",
FILE_PREFIX, pid, marker, secs, subsec_nanos
),
}
}
}
impl AsRef<str> for RandomName {
fn as_ref(&self) -> &str {
&self.name
}
}
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::*;
#[test]
fn test_random_name() {
let name = RandomName::new();
assert!(name.as_ref().starts_with(FILE_PREFIX))
}
}