1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Temporary files and directories.

extern crate rand;

use std::{env, fs};
use std::io::{Error, ErrorKind, Result};
use std::path::{Path, PathBuf};

pub struct Directory {
    path: PathBuf,
    removed: bool,
}

impl Directory {
    /// Create a temporary directory. The directory will have a name starting
    /// from `prefix`, and it will be automatically removed when the object is
    /// destroyed.
    #[inline]
    pub fn new(prefix: &str) -> Result<Directory> {
        Directory::new_in(env::temp_dir(), prefix)
    }

    /// Create a temporary directory in the location specified by `root`. The
    /// directory will have a name starting from `prefix`, and it will be
    /// automatically removed when the object is destroyed.
    pub fn new_in<T: AsRef<Path>>(root: T, prefix: &str) -> Result<Directory> {
        use rand::Rng;

        const RETRIES: u32 = 1 << 31;
        const CHARS: usize = 12;

        let root = root.as_ref();
        if !root.is_absolute() {
            let current = try!(env::current_dir());
            return Directory::new_in(current.join(root), prefix);
        }

        let mut generator = rand::thread_rng();

        for _ in 0..RETRIES {
            let suffix: String = generator.gen_ascii_chars().take(CHARS).collect();

            let path = if prefix.is_empty() {
                root.join(&suffix)
            } else {
                root.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"))
    }

    /// Return the path to the directory.
    #[inline]
    pub fn path<'d>(&'d self) -> &'d Path {
        &self.path
    }

    /// Remove the directory.
    #[inline]
    pub fn remove(mut self) -> Result<()> {
        self.cleanup()
    }

    /// Dispose the object without removing the actual directory.
    #[inline]
    pub fn unwrap(mut self) -> Result<()> {
        self.removed = true;
        Ok(())
    }

    fn cleanup(&mut self) -> Result<()> {
        if self.removed {
            return Ok(());
        }
        self.removed = true;

        fs::remove_dir_all(&self.path)
    }
}

impl Drop for Directory {
    #[inline]
    fn drop(&mut self) {
        let _ = self.cleanup();
    }
}

#[cfg(test)]
mod tests {
    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());
    }
}