From 63f56b43e72a602c765f44b71df6615bd5ce139b Mon Sep 17 00:00:00 2001 From: Vika Date: Sun, 4 Aug 2024 07:46:46 +0300 Subject: kittybox-util: move out fs module --- util/src/fs.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 util/src/fs.rs (limited to 'util/src/fs.rs') diff --git a/util/src/fs.rs b/util/src/fs.rs new file mode 100644 index 0000000..6a7a5b4 --- /dev/null +++ b/util/src/fs.rs @@ -0,0 +1,56 @@ +use std::io::{self, Result}; +use std::path::{Path, PathBuf}; +use rand::{Rng, distributions::Alphanumeric}; +use tokio::fs; + +/// Create a temporary file named `temp.[a-zA-Z0-9]{length}` in +/// the given location and immediately open it. Returns the +/// filename and the corresponding file handle. It is the caller's +/// responsibility to clean up the temporary file when it is no +/// longer needed. +/// +/// Uses [`OpenOptions::create_new`][fs::OpenOptions::create_new] +/// to detect filename collisions, in which case it will +/// automatically retry until the operation succeeds. +/// +/// # Errors +/// +/// Returns the underlying [`io::Error`] if the operation fails +/// due to reasons other than filename collision. +pub async fn mktemp(dir: T, basename: B, length: usize) -> Result<(PathBuf, fs::File)> +where + T: AsRef, + B: Into> +{ + let dir = dir.as_ref(); + let basename = basename.into().unwrap_or(""); + fs::create_dir_all(dir).await?; + + loop { + let filename = dir.join(format!( + "{}{}{}", + basename, + if basename.is_empty() { "" } else { "." }, + { + let string = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(length) + .collect::>(); + String::from_utf8(string).unwrap() + } + )); + + match fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&filename) + .await + { + Ok(file) => return Ok((filename, file)), + Err(err) => match err.kind() { + io::ErrorKind::AlreadyExists => continue, + _ => return Err(err) + } + } + } +} -- cgit 1.4.1