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
extern crate crypto; extern crate tempfile; use std::fs; use std::fs::File; use std::io; use std::io::{Error, ErrorKind, Read}; use std::path::Path; mod hash; pub trait Store { fn put(&self, item: &mut Read) -> Result<String, Error>; fn get(&self, hash: &str) -> Result<File, Error>; fn remove(&self, hash: &str) -> Result<(), Error>; } pub struct BlobStore { path: String } impl BlobStore { pub fn new(path: String) -> BlobStore { BlobStore { path: path } } fn file_path(&self, hash: &str) -> Result<String, Error> { let dir_name: &str = self.path.as_ref(); let dir = Path::new(dir_name).join(&hash[..2]); let path = dir.join(&hash[2..]); fs::create_dir_all(dir)?; match path.to_str() { None => Err(Error::new(ErrorKind::Other, "invalid path")), Some(value) => Ok(value.to_string()) } } } impl Store for BlobStore { fn put(&self, source: &mut Read) -> Result<String, Error> { let mut reader = hash::HashedReader::new(source); fs::create_dir_all(&self.path)?; let mut writer = tempfile::NamedTempFile::new_in(&self.path)?; io::copy(&mut reader, &mut writer)?; let hash = reader.digest(); let dest = self.file_path(&hash)?; fs::rename(writer.path(), dest)?; Ok(hash) } fn get(&self, hash: &str) -> Result<File, Error> { let path = self.file_path(hash)?; File::open(path) } fn remove(&self, hash: &str) -> Result<(), Error> { let path = self.file_path(hash)?; fs::remove_file(path)?; Ok(()) } } #[cfg(test)] mod tests { use super::{BlobStore, Store}; use std::fs; use std::io::Read; #[test] fn put() { let mut source = "foo".as_bytes(); let store = BlobStore::new("./put_test".to_string()); let hash = store.put(&mut source).unwrap(); fs::remove_dir_all(store.path).unwrap(); assert_eq!(hash, "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"); } #[test] fn get() { let mut source = "bar".as_bytes(); let store = BlobStore::new("./get_test".to_string()); let hash = store.put(&mut source).unwrap(); let mut value = String::new(); store.get(hash.as_ref()).unwrap().read_to_string(&mut value).unwrap(); fs::remove_dir_all(store.path).unwrap(); assert_eq!(value, "bar"); } #[test] fn remove() { let mut source = "baz".as_bytes(); let store = BlobStore::new("./remove_test".to_string()); let hash = store.put(&mut source).unwrap(); store.remove(hash.as_ref()).unwrap(); let error = match store.get(hash.as_ref()) { Err(_) => true, _ => false }; fs::remove_dir_all(store.path).unwrap(); assert_eq!(error, true); } }