pict-rs/src/tmp_file.rs
asonix f61dac8187
Some checks failed
continuous-integration/drone/push Build is failing
Attempt to clean stray magick files
This change sets a unique temp directory for each invocation of imagemagick and spawns a task to remove that directory after the command terminates
2023-11-09 18:20:59 -06:00

175 lines
3.8 KiB
Rust

use std::{
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use tokio::io::AsyncRead;
use uuid::Uuid;
pub(crate) type ArcTmpDir = Arc<TmpDir>;
#[derive(Debug)]
pub(crate) struct TmpDir {
path: Option<PathBuf>,
}
impl TmpDir {
pub(crate) async fn init() -> std::io::Result<Arc<Self>> {
let path = std::env::temp_dir().join(Uuid::new_v4().to_string());
tokio::fs::create_dir(&path).await?;
Ok(Arc::new(TmpDir { path: Some(path) }))
}
fn build_tmp_file(&self, ext: Option<&str>) -> Arc<Path> {
if let Some(ext) = ext {
Arc::from(self.path.as_ref().expect("tmp path exists").join(format!(
"{}{}",
Uuid::new_v4(),
ext
)))
} else {
Arc::from(
self.path
.as_ref()
.expect("tmp path exists")
.join(Uuid::new_v4().to_string()),
)
}
}
pub(crate) fn tmp_file(&self, ext: Option<&str>) -> TmpFile {
TmpFile(self.build_tmp_file(ext))
}
pub(crate) async fn tmp_folder(&self) -> std::io::Result<TmpFolder> {
let path = self.build_tmp_file(None);
tokio::fs::create_dir(&path).await?;
Ok(TmpFolder(path))
}
pub(crate) async fn cleanup(self: Arc<Self>) -> std::io::Result<()> {
if let Some(path) = Arc::into_inner(self).and_then(|mut this| this.path.take()) {
tokio::fs::remove_dir_all(path).await?;
}
Ok(())
}
}
impl Drop for TmpDir {
fn drop(&mut self) {
if let Some(path) = self.path.as_ref() {
std::fs::remove_dir_all(path).expect("Removed directory");
}
}
}
#[must_use]
pub(crate) struct TmpFolder(Arc<Path>);
impl TmpFolder {
pub(crate) fn reader<R: AsyncRead>(self, reader: R) -> TmpFolderCleanup<R> {
TmpFolderCleanup {
inner: reader,
folder: self,
}
}
}
impl AsRef<Path> for TmpFolder {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl Deref for TmpFolder {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Drop for TmpFolder {
fn drop(&mut self) {
crate::sync::spawn(
"remove-tmpfolder",
tokio::fs::remove_dir_all(self.0.clone()),
);
}
}
#[must_use]
pub(crate) struct TmpFile(Arc<Path>);
impl TmpFile {
pub(crate) fn reader<R: AsyncRead>(self, reader: R) -> TmpFileCleanup<R> {
TmpFileCleanup {
inner: reader,
file: self,
}
}
}
impl AsRef<Path> for TmpFile {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl Deref for TmpFile {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Drop for TmpFile {
fn drop(&mut self) {
crate::sync::spawn("remove-tmpfile", tokio::fs::remove_file(self.0.clone()));
}
}
pin_project_lite::pin_project! {
pub(crate) struct TmpFileCleanup<R> {
#[pin]
inner: R,
file: TmpFile,
}
}
pin_project_lite::pin_project! {
pub(crate) struct TmpFolderCleanup<R> {
#[pin]
inner: R,
folder: TmpFolder,
}
}
impl<R: AsyncRead> AsyncRead for TmpFileCleanup<R> {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.as_mut().project();
this.inner.poll_read(cx, buf)
}
}
impl<R: AsyncRead> AsyncRead for TmpFolderCleanup<R> {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
let this = self.as_mut().project();
this.inner.poll_read(cx, buf)
}
}