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}