file_tree/
file_tree.rs

1use std::collections::hash_map::HashMap;
2use std::env::temp_dir;
3use std::fs;
4use std::hash::Hash;
5use std::io::Result;
6use std::path::PathBuf;
7use uuid::Uuid;
8
9use tempdir::TempDir;
10
11/// Creates a directory structure suitable for storing large numbers of files.
12/// Optionally deletes the created directory and files when dropped.
13///
14/// Slots for new files are allocated using `get_new_file()`. This struct will
15/// create new subdirectories as needed to ensure that no subdirectory contains
16/// more than 1,000 files/subdirectories.
17pub struct FileTree {
18    tmp_dir: Option<TempDir>,
19    persistent_dir: Option<PathBuf>,
20    counter: u64
21}
22
23impl FileTree {
24    /// Create a new directory structure under `path`. If `persistent` is
25    /// `false` the directory and all it's contents will be deleted when
26    /// the returned `FileTree` is dropped
27    ///
28    /// # Examples
29    ///
30    /// Create a new temporary data structure and make sure the base path exists
31    ///
32    /// ```
33    /// use file_tree::FileTree;
34    /// use std::env::temp_dir;
35    ///
36    /// let file_tree = FileTree::new_in(temp_dir(), false).unwrap();
37    /// assert!(file_tree.get_root().exists());
38    /// ```
39    ///
40    /// # Errors
41    ///
42    /// If `persistent` is `false`, the directory will be created using
43    /// `tempdir::TempDir`, and any related errors will be returned here
44    pub fn new_in(path: PathBuf, persistent: bool) -> Result<FileTree> {
45        if persistent {
46            Ok(FileTree { tmp_dir: None,
47                          persistent_dir: Some(path),
48                          counter: 0 })
49        } else {
50            Ok(FileTree { tmp_dir: Some(TempDir::new_in(path, "file_tree")?),
51                          persistent_dir: None,
52                          counter: 0 })
53        }
54    }
55
56    /// Create a new directory structure. If `persistent` is `false` the
57    /// directory and all it's contents will be deleted when the returned
58    /// `FileTree` is dropped.    
59    ///
60    /// # Examples
61    ///
62    /// Create a new temporary data structure and make sure the base path exists
63    ///
64    /// ```
65    /// use file_tree::FileTree;
66    ///
67    /// let file_tree = FileTree::new(false).unwrap();
68    /// assert!(file_tree.get_root().exists());
69    /// ```
70    ///
71    /// # Errors
72    ///
73    /// If `persistent` is `false`, the directory will be created using
74    /// `tempdir::TempDir`, and any related errors will be returned here
75    pub fn new(persistent: bool) -> Result<FileTree> {
76        if persistent {
77            let uuid = Uuid::new_v4().hyphenated().to_string();
78
79            Ok(FileTree { tmp_dir: None,
80                          persistent_dir: Some(temp_dir().join(uuid)),
81                          counter: 0 })
82        } else {
83            Ok(FileTree { tmp_dir: Some(TempDir::new("file_tree")?),
84                          persistent_dir: None,
85                          counter: 0 })
86        }
87    }
88
89    /// Creates a `FileTree` from an existing directory structure. `path` should
90    /// be equivalent to the result of calling `get_root()` on the previous
91    /// (persistent) `FileTree`.
92    ///
93    /// # Examples
94    ///
95    /// Re-create a `FileTree` using an existing file structure
96    ///
97    /// ```
98    /// use file_tree::FileTree;
99    /// use std::fs::File;
100    ///
101    /// // create a `FileTree` with one file
102    /// let mut ft = FileTree::new(true).unwrap();
103    /// let file_path = ft.get_new_file().unwrap();
104    /// File::create(file_path.clone()).unwrap();
105    /// let base = ft.get_root();
106    /// drop(ft);
107    ///
108    /// // create a `FileTree` using the existing path, and make sure that the
109    /// // files we pull back don't overwrite the existing one
110    /// let mut ft2 = FileTree::from_existing(base);
111    /// let file2 = ft2.get_new_file().unwrap();
112    /// assert_eq!(file_path.file_name().unwrap(), "000000000000");
113    /// assert_eq!(file2.file_name().unwrap(), "000000000001");
114    /// ```
115    pub fn from_existing(path: PathBuf) -> FileTree {
116        FileTree { tmp_dir: None,
117                   persistent_dir: Some(path),
118                   counter: 0 }
119    }
120
121    /// Returns a PathBuf pointing to an available slot in the file tree. The
122    /// file pointed to by the returned `PathBuf` will not be created by
123    /// this method call, but a new directory will be created if necessary.
124    ///
125    /// This method will ensure that the file pointed to by the returned
126    /// `PathBuf` does not exist. If this struct was created using an existing
127    /// directory structure existing files will be skipped over when generating
128    /// new file names to return.
129    ///
130    /// File paths are generated such that each new leaf directory (starting
131    /// with `000/000/000/`) will be filled entirely before creating a new
132    /// directory (next would be `000/000/001/`).
133    ///
134    ///
135    /// # Examples
136    ///
137    /// Retrieve two distinct file paths via `get_new_file()`
138    ///
139    /// ```
140    /// use file_tree::FileTree;
141    ///
142    /// let mut file_tree = FileTree::new(false).unwrap();
143    ///
144    /// let writeable_path = file_tree.get_new_file().unwrap();
145    /// assert_eq!(
146    ///     writeable_path,
147    ///     file_tree.get_root().join("000/000/000/000000000000")
148    /// );
149    ///
150    /// let writeable_path_2 = file_tree.get_new_file().unwrap();
151    /// assert_eq!(
152    ///     writeable_path_2,
153    ///     file_tree.get_root().join("000/000/000/000000000001")
154    /// );
155    /// ```
156    ///
157    /// # Errors
158    ///
159    /// If a new subdirectory is required, `fs::create_dir_all` will be called.
160    /// Any errors from that call will be returned here
161    pub fn get_new_file(&mut self) -> Result<PathBuf> {
162        let mut new_file = self.get_new_file_uniq()?;
163        while new_file.exists() {
164            new_file = self.get_new_file_uniq()?;
165        }
166        Ok(new_file)
167    }
168
169    fn get_new_file_uniq(&mut self) -> Result<PathBuf> {
170        let uid = format!("{:012}", self.counter);
171        self.counter += 1;
172        let mut buff = String::with_capacity(3);
173        let mut parts = Vec::with_capacity(4);
174        for c in uid.chars() {
175            if buff.chars().count() >= 3 {
176                parts.push(buff);
177                buff = String::with_capacity(3);
178            }
179            buff.push(c);
180        }
181        if buff.chars().count() > 0 {
182            parts.push(buff);
183        }
184        let path_str = format!("{0}/{1}/{2}", parts[0], parts[1], parts[2]);
185        let path = self.get_root().join(path_str);
186        match fs::create_dir_all(&path) {
187            Ok(_) => Ok(path.join(uid)),
188            Err(e) => Err(e)
189        }
190    }
191
192    /// Return the root path for the file tree
193    pub fn get_root(&self) -> PathBuf {
194        match self.tmp_dir {
195            Some(ref p) => p.path().to_path_buf(),
196            None => self.persistent_dir.as_ref().unwrap().to_path_buf()
197        }
198    }
199}
200
201/// Retrieves paths from a `FileTree` using `Hash` key.
202///
203/// File paths are stored in memory, and associated with a key. When requesting
204/// paths from a `KeyedFileTree`, an existing path will be returned if the key
205/// has been seen before. Otherwise a new path will be created in the directory
206/// structure and returned.
207///
208/// # Examples
209///
210/// ```
211/// extern crate file_tree;
212///
213/// use file_tree::KeyedFileTree;
214///
215/// let mut file_tree = KeyedFileTree::new(false).unwrap();
216///
217/// let writeable_path_1 = file_tree.get(String::from("key1")).unwrap();
218/// let writeable_path_2 = file_tree.get(String::from("key2")).unwrap();
219///
220/// assert_ne!(writeable_path_1, writeable_path_2);
221/// ```
222pub struct KeyedFileTree<T>
223    where T: Hash + Eq {
224    paths: HashMap<T, PathBuf>,
225    file_tree: FileTree
226}
227
228impl<T> KeyedFileTree<T>
229    where T: Hash + Eq
230{
231    /// Create a new instance. If `persistence` is `false`, the backing
232    /// directory structure will be removed when the returned instance is
233    /// dropped.
234    pub fn new(persistent: bool) -> Result<KeyedFileTree<T>> {
235        Ok(KeyedFileTree { paths: HashMap::new(),
236                           file_tree: FileTree::new(persistent)? })
237    }
238
239    /// Create a new instance, storing the directory structure in `path`. If
240    /// `persistence` is `false`, the backing directory structure will be
241    /// removed when the returned instance is dropped.
242    pub fn new_in(path: PathBuf, persistent: bool) -> Result<KeyedFileTree<T>> {
243        Ok(KeyedFileTree { paths: HashMap::new(),
244                           file_tree: FileTree::new_in(path, persistent)? })
245    }
246
247    /// Creates a new instance from an existing directory structure. `path`
248    /// should be equivalent to the result of calling `get_root()` on the
249    /// previous (persistent) `KeyedFileTree`, and `existing_files` should be
250    /// equivalent to calling `get_existing_files()`.
251    pub fn from_existing(path: PathBuf, existing_files: HashMap<T, PathBuf>) -> KeyedFileTree<T> {
252        KeyedFileTree { paths: existing_files,
253                        file_tree: FileTree::from_existing(path) }
254    }
255
256    /// Reserve a spot in the directory structure for `key`, and return the
257    /// associated `PathBuf`. If `key` has already been seen, the existing
258    /// `PathBuf` will be returned.
259    pub fn get(&mut self, key: T) -> Result<PathBuf> {
260        Ok(self.paths.entry(key)
261               .or_insert(self.file_tree.get_new_file()?)
262               .clone())
263    }
264
265    /// Return the root path for the file tree.
266    pub fn get_root(&self) -> PathBuf { self.file_tree.get_root() }
267
268    /// Gets the map of keys to `PathBuf`s. Useful for re-creating an instance
269    /// later with `from_existing()`.
270    pub fn get_existing_files(self) -> HashMap<T, PathBuf> { self.paths }
271}