extern crate random;
use random::Source;
use std::io::{Error, ErrorKind, Result};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::{env, fmt, fs};
pub struct Directory {
path: PathBuf,
removed: bool,
}
impl Directory {
#[inline]
pub fn new(prefix: &str) -> Result<Directory> {
Directory::with_parent(env::temp_dir(), prefix)
}
pub fn with_parent<T: AsRef<Path>>(parent: T, prefix: &str) -> Result<Directory> {
const RETRIES: u32 = 1 << 31;
const CHARS: usize = 12;
let parent = parent.as_ref();
if !parent.is_absolute() {
let current = try!(env::current_dir());
return Directory::with_parent(current.join(parent), prefix);
}
let mut source = random::default().seed(random_seed(parent, prefix));
for _ in 0..RETRIES {
let suffix: String = random_string(CHARS, &mut source);
let path = if prefix.is_empty() {
parent.join(&suffix)
} else {
parent.join(&format!("{}.{}", prefix, suffix))
};
match fs::create_dir(&path) {
Ok(_) => return Ok(Directory {
path: path.to_path_buf(),
removed: false,
}),
Err(error) => match error.kind() {
ErrorKind::AlreadyExists => {},
_ => return Err(error),
},
}
}
Err(Error::new(ErrorKind::AlreadyExists, "failed to find a vacant name"))
}
#[inline]
pub fn path(&self) -> &Path {
self.as_ref()
}
#[inline]
pub fn into_path(mut self) -> PathBuf {
self.removed = true;
self.path.clone()
}
#[inline]
pub fn remove(mut self) -> Result<()> {
self.cleanup()
}
fn cleanup(&mut self) -> Result<()> {
if self.removed {
return Ok(());
}
self.removed = true;
fs::remove_dir_all(&self.path)
}
}
impl AsRef<Path> for Directory {
#[inline]
fn as_ref(&self) -> &Path {
&self.path
}
}
impl fmt::Debug for Directory {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.path.fmt(formatter)
}
}
impl Deref for Directory {
type Target = Path;
#[inline]
fn deref(&self) -> &Path {
&self.path
}
}
impl Drop for Directory {
#[allow(unused_must_use)]
#[inline]
fn drop(&mut self) {
self.cleanup();
}
}
fn random_seed(_: &Path, _: &str) -> [u64; 2] {
use std::mem::uninitialized as rand;
unsafe { [rand::<u64>() ^ 0x12345678, rand::<u64>() ^ 0x87654321] }
}
fn random_string<S: Source>(length: usize, source: &mut S) -> String {
unsafe { String::from_utf8_unchecked((0..length).map(|_| random_letter(source)).collect()) }
}
fn random_letter<S: Source>(source: &mut S) -> u8 {
b'a' + (source.read::<u64>() % 26) as u8
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::Directory;
#[test]
fn new() {
use std::fs;
let path = {
let directory = Directory::new("foo").unwrap();
assert!(fs::metadata(directory.path()).is_ok());
directory.path().to_path_buf()
};
assert!(fs::metadata(path).is_err());
}
#[test]
fn deref() {
let directory = Directory::new("bar").unwrap();
work(&directory);
fn work(_: &Path) {
}
}
}