dinglebit_store/
blob.rs

1//! A Storage system for blobs (arbitrary bytes of data).
2
3use std::fs::{remove_file, File};
4use std::io::{copy, Cursor, ErrorKind};
5
6use bincode;
7use chrono::{DateTime, Utc};
8use serde_derive::{Deserialize, Serialize};
9use sha2::{Digest, Sha512};
10use sled;
11
12/// The errors this package returns.
13#[derive(Debug)]
14pub enum Error {
15    /// This is more of a generic error that something bad happened.
16    SystemError(String),
17
18    /// The requested path was not valid (e.g. not UTF-8).
19    InvalidPath,
20
21    /// The process did not have permissions to perform the operation.
22    PermissionDenied,
23
24    /// The requested blob was not found.
25    NotFound,
26}
27
28/// A blob contains general information about the blob.
29pub trait Blob {
30    /// The key (e.g. name) of the blob.
31    fn key(&self) -> &str;
32
33    /// The number of bytes in the blob.
34    fn size(&self) -> usize;
35
36    /// When the blob was originally created.
37    fn created(&self) -> DateTime<Utc>;
38
39    /// When the blob was last accessed.
40    fn accessed(&self) -> Option<DateTime<Utc>>;
41}
42
43/// A generic Storage system for blobs.
44pub trait Store<B, I>
45where
46    B: Blob,
47    I: std::iter::Iterator<Item = Result<B, Error>>,
48{
49    /// Save the content of r into the storage system with the given
50    /// key.
51    fn put_from<R: std::io::Read>(&mut self, key: &str, r: &mut R) -> Result<usize, Error>;
52
53    /// Similar to put_from but uses the give data instead of a
54    /// reader.
55    fn put(&mut self, key: &str, data: &[u8]) -> Result<(), Error> {
56        match self.put_from(key, &mut Cursor::new(data)) {
57            Ok(_) => Ok(()),
58            Err(e) => Err(e),
59        }
60    }
61
62    /// Read the contents of the blob with the given key into the
63    /// given writer.
64    fn get_to<W: std::io::Write>(&mut self, key: &str, w: &mut W) -> Result<usize, Error>;
65
66    /// Similar to get_to but reads the contents into a byte vector.
67    fn get(&mut self, key: &str) -> Result<Vec<u8>, Error> {
68        let mut buf: Vec<u8> = Vec::new();
69        match self.get_to(key, &mut buf) {
70            Ok(_) => Ok(buf),
71            Err(e) => Err(e),
72        }
73    }
74
75    /// Find all blobs that have the given prefix and iterate over
76    /// them.
77    fn prefix(&mut self, prefix: &str) -> I;
78
79    /// Delete the blob with the given key.
80    fn delete(&mut self, key: &str) -> Result<(), Error>;
81
82    /// Similar to delete but it does not return an error if the error
83    /// is `NotFound`.
84    fn remove(&mut self, name: &str) -> Result<(), Error> {
85        match self.delete(name) {
86            Ok(_) => Ok(()),
87            Err(e) => match e {
88                Error::NotFound => Ok(()),
89                _ => Err(e),
90            },
91        }
92    }
93}
94
95/// A simple Blob implementation.
96#[derive(Debug, Serialize, Deserialize, Eq, Ord, PartialEq, PartialOrd)]
97pub struct SimpleBlob {
98    k: String,
99    s: usize,
100    c: DateTime<Utc>,
101    a: Option<DateTime<Utc>>,
102}
103
104impl SimpleBlob {
105    // Create a new SimpleBlob with the given values.
106    pub fn new(k: String, s: usize, c: DateTime<Utc>, a: Option<DateTime<Utc>>) -> Self {
107        Self { k, s, c, a }
108    }
109}
110
111impl Blob for SimpleBlob {
112    fn key(&self) -> &str {
113        self.k.as_str()
114    }
115
116    fn size(&self) -> usize {
117        self.s
118    }
119
120    fn created(&self) -> DateTime<Utc> {
121        self.c
122    }
123
124    fn accessed(&self) -> Option<DateTime<Utc>> {
125        self.a
126    }
127}
128
129/// An implementation of a BlobStore using a folder on a file system.
130#[derive(Debug)]
131pub struct FileSystem {
132    db: sled::Db,
133    path: std::path::PathBuf,
134    hasher: Sha512,
135}
136
137impl FileSystem {
138    /// Create an instance of a FileSystem that uses the given path to
139    /// store blobs and metadata.
140    pub fn new(path: &str) -> Result<Self, Error> {
141        let path = std::path::PathBuf::from(path);
142        let db = path.join("blobs.db");
143        let db = match db.to_str() {
144            Some(path) => path,
145            None => return Err(Error::InvalidPath),
146        };
147        let db = match sled::open(db) {
148            Ok(db) => db,
149            Err(e) => return Err(Error::SystemError(e.to_string())),
150        };
151
152        Ok(Self {
153            db: db,
154            path: path,
155            hasher: Sha512::new(),
156        })
157    }
158
159    /// Helper function to get the key as a sha512 hash.
160    fn path_hash(&mut self, key: &str) -> String {
161        self.hasher.update(key);
162        self.path
163            .join(format!("{:x}", self.hasher.finalize_reset()))
164            .to_str()
165            .unwrap()
166            .to_string()
167    }
168}
169
170impl Store<SimpleBlob, FileSystemIter> for FileSystem {
171    fn put_from<R: std::io::Read>(&mut self, key: &str, r: &mut R) -> Result<usize, Error> {
172        // Determine the path location and open up a file handle.
173        let path = self.path_hash(key);
174        let mut f = match File::create(path.clone()) {
175            Ok(f) => f,
176            Err(e) => match e.kind() {
177                ErrorKind::NotFound => return Err(Error::NotFound),
178                ErrorKind::PermissionDenied => return Err(Error::PermissionDenied),
179                _ => return Err(Error::SystemError(e.to_string())),
180            },
181        };
182
183        // Copy to the file.
184        let w = match copy(r, &mut f) {
185            Ok(w) => w as usize,
186            Err(e) => {
187                return match e.kind() {
188                    ErrorKind::NotFound => Err(Error::NotFound),
189                    ErrorKind::PermissionDenied => Err(Error::PermissionDenied),
190                    _ => Err(Error::SystemError(e.to_string())),
191                }
192            }
193        };
194
195        // Create the blob and then store it in the database. We use
196        // the original key here.
197        let o = SimpleBlob::new(key.to_string(), w, Utc::now(), None);
198        let data = match bincode::serialize(&o) {
199            Ok(d) => d,
200            Err(e) => return Err(Error::SystemError(e.to_string())),
201        };
202        match self.db.insert(key, data) {
203            Ok(_) => Ok(w),
204            Err(e) => Err(Error::SystemError(e.to_string())),
205        }
206    }
207
208    fn get_to<W: std::io::Write>(&mut self, key: &str, w: &mut W) -> Result<usize, Error> {
209        // NOTE: no need to modify the database here. We could
210        // eventually add an access time.
211
212        // Open the file at the given path.
213        let key = self.path_hash(key);
214        let mut f = match File::open(key) {
215            Ok(f) => f,
216            Err(e) => match e.kind() {
217                ErrorKind::NotFound => return Err(Error::NotFound),
218                ErrorKind::PermissionDenied => return Err(Error::PermissionDenied),
219                _ => return Err(Error::SystemError(e.to_string())),
220            },
221        };
222
223        // Copy from the file to the given write.
224        match copy(&mut f, w) {
225            Ok(w) => Ok(w as usize),
226            Err(e) => match e.kind() {
227                ErrorKind::NotFound => Err(Error::NotFound),
228                ErrorKind::PermissionDenied => Err(Error::PermissionDenied),
229                _ => Err(Error::SystemError(e.to_string())),
230            },
231        }
232    }
233
234    fn delete(&mut self, key: &str) -> Result<(), Error> {
235        // Determine the file path
236        let path = self.path_hash(key);
237
238        // Remove it from the database. We use the given name here,
239        // not the entire path.
240        match self.db.remove(key.clone()) {
241            Ok(_) => (),
242            Err(e) => return Err(Error::SystemError(e.to_string())),
243        };
244
245        // Remove it from the file system.
246        match remove_file(path) {
247            Ok(_) => Ok(()),
248            Err(e) => match e.kind() {
249                ErrorKind::NotFound => Err(Error::NotFound),
250                ErrorKind::PermissionDenied => Err(Error::PermissionDenied),
251                _ => Err(Error::SystemError(e.to_string())),
252            },
253        }
254    }
255
256    fn prefix(&mut self, prefix: &str) -> FileSystemIter {
257        FileSystemIter {
258            iter: self.db.scan_prefix(prefix),
259        }
260    }
261}
262
263pub struct FileSystemIter {
264    iter: sled::Iter,
265}
266
267impl Iterator for FileSystemIter {
268    type Item = Result<SimpleBlob, Error>;
269    fn next(&mut self) -> Option<Self::Item> {
270        let data = match self.iter.next() {
271            Some(r) => match r {
272                Ok(data) => data,
273                Err(e) => return Some(Err(Error::SystemError(e.to_string()))),
274            },
275            None => return None,
276        };
277        let o: SimpleBlob = match bincode::deserialize(&(data.1)) {
278            Ok(o) => o,
279            Err(e) => return Some(Err(Error::SystemError(e.to_string()))),
280        };
281        return Some(Ok(o));
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use crate::blob::*;
288    use tempfile::tempdir;
289
290    #[test]
291    fn simple_blob() {
292        let now = Utc::now();
293        let so = SimpleBlob::new("test".to_string(), 1043, now, None);
294        assert_eq!(so.key(), "test".to_string());
295        assert_eq!(so.size(), 1043);
296        assert_eq!(so.created(), now);
297        assert_eq!(so.accessed(), None);
298    }
299
300    #[test]
301    fn it_works() {
302        let td = tempdir().expect("unable to make tempdir");
303        let mut fs = FileSystem::new(td.path().to_str().expect("unable to convert tempdir"))
304            .expect("failed opening filesystem");
305
306        fs.put("test-1", b"test-1").expect("failed to put test-1");
307        fs.put("test-2", b"test-2").expect("failed to put test-2");
308        fs.put("test-3", b"test-3").expect("failed to put test-3");
309        fs.put("test-4", b"test-4").expect("failed to put test-4");
310        fs.put("a", b"a").expect("failed to put a");
311        fs.put("b", b"c").expect("failed to put b");
312        fs.put("c", b"c").expect("failed to put c");
313
314        assert_eq!(fs.get("test-1").expect("failed to get test-1"), b"test-1");
315        assert_eq!(fs.get("test-2").expect("failed to get test-2"), b"test-2");
316        assert_eq!(fs.get("test-3").expect("failed to get test-3"), b"test-3");
317        assert_eq!(fs.get("test-4").expect("failed to get test-4"), b"test-4");
318        assert!(fs.get("not found").is_err());
319
320        fs.remove("test-1").expect("failed to remove test-1");
321        fs.remove("test-1").expect("failed to remove test-1"); // ignore not found
322        assert!(fs.get("test-1").is_err());
323
324        let mut list: Vec<String> = Vec::new();
325        for res in fs.prefix("test") {
326            let f = res.expect("failed to get in loop");
327            list.push(f.key().to_string());
328        }
329        list.sort();
330        assert_eq!(list, vec!["test-2", "test-3", "test-4"]);
331    }
332}