diff options
Diffstat (limited to 'util/src/fs.rs')
-rw-r--r-- | util/src/fs.rs | 56 |
1 files changed, 56 insertions, 0 deletions
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<T, B>(dir: T, basename: B, length: usize) -> Result<(PathBuf, fs::File)> +where + T: AsRef<Path>, + B: Into<Option<&'static str>> +{ + 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::<Vec<u8>>(); + 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) + } + } + } +} |