#[cfg(not(feature = "uuid"))]
use crate::RandomName;
use crate::{Error, Ownership};
use std::borrow::Borrow;
use std::fmt::{Debug, Formatter};
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[cfg(feature = "uuid")]
use uuid::Uuid;
const DIR_PREFIX: &str = "atmpd_";
pub struct TempDir {
dir: ManuallyDrop<PathBuf>,
core: ManuallyDrop<Arc<TempDirCore>>,
}
struct TempDirCore {
path: PathBuf,
ownership: Ownership,
}
impl TempDir {
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>>(root_dir: P) -> Result<Self, Error> {
#[cfg(feature = "uuid")]
{
let id = Uuid::new_v4();
Self::new_with_uuid_in(id, root_dir).await
}
#[cfg(not(feature = "uuid"))]
{
let name = RandomName::new(DIR_PREFIX);
Self::new_with_name_in(name, root_dir).await
}
}
pub async fn new_with_name_in<N: AsRef<str>, P: Borrow<Path>>(
name: N,
root_dir: P,
) -> Result<Self, Error> {
let dir = root_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, root_dir: P) -> Result<Self, Error> {
let file_name = format!("{}{}", DIR_PREFIX, uuid);
Self::new_with_name_in(file_name, root_dir).await
}
pub async fn from_existing(path: PathBuf, ownership: Ownership) -> Result<Self, Error> {
if !path.is_dir() {
return Err(Error::InvalidDirectory);
}
Self::new_internal(path, ownership).await
}
pub fn dir_path(&self) -> &PathBuf {
&self.core.path
}
#[allow(dead_code)]
pub async fn try_clone(&self) -> Result<TempDir, Error> {
Ok(TempDir {
core: self.core.clone(),
dir: self.dir.clone(),
})
}
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> {
tokio::fs::create_dir_all(path.borrow()).await?;
let core = TempDirCore {
ownership,
path: PathBuf::from(path.borrow()),
};
Ok(Self {
dir: ManuallyDrop::new(PathBuf::from(path.borrow())),
core: ManuallyDrop::new(Arc::new(core)),
})
}
#[inline(always)]
fn default_dir() -> PathBuf {
std::env::temp_dir()
}
}
impl Drop for TempDir {
fn drop(&mut self) {
drop(unsafe { ManuallyDrop::take(&mut self.dir) });
drop(unsafe { ManuallyDrop::take(&mut self.core) });
}
}
impl Drop for TempDirCore {
fn drop(&mut self) {
if self.ownership != Ownership::Owned {
return;
}
let _ = std::fs::remove_dir_all(&self.path);
}
}
impl Debug for TempDirCore {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.path)
}
}
impl Debug for TempDir {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.core)
}
}
impl Deref for TempDir {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.dir
}
}
impl Borrow<Path> for TempDir {
fn borrow(&self) -> &Path {
&self.dir
}
}
impl Borrow<Path> for &TempDir {
fn borrow(&self) -> &Path {
&self.dir
}
}
impl AsRef<Path> for TempDir {
fn as_ref(&self) -> &Path {
&self.dir
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TempFile;
#[tokio::test]
async fn test_new() -> Result<(), Error> {
let dir = TempDir::new().await?;
let dir_path = dir.dir_path().clone();
assert!(tokio::fs::metadata(dir_path.clone()).await.is_ok());
drop(dir);
assert!(tokio::fs::metadata(dir_path).await.is_err());
Ok(())
}
#[tokio::test]
#[cfg(not(target_os = "windows"))]
async fn test_files_in_dir() -> Result<(), Error> {
let dir = TempDir::new().await?;
let file = TempFile::new_in(&dir).await?;
let file2 = TempFile::new_in(&dir).await?;
let dir_path = dir.dir_path().clone();
assert!(tokio::fs::metadata(dir_path.clone()).await.is_ok());
let file_path = file.file_path().clone();
let file_path2 = file2.file_path().clone();
assert!(tokio::fs::metadata(file_path.clone()).await.is_ok());
assert!(tokio::fs::metadata(file_path2.clone()).await.is_ok());
drop(dir);
assert!(tokio::fs::metadata(file_path).await.is_err());
assert!(tokio::fs::metadata(file_path2).await.is_err());
assert!(tokio::fs::metadata(dir_path).await.is_err());
Ok(())
}
}