diff --git a/src/storage.rs b/src/storage.rs index 312a47b..4e31e7f 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1,22 +1,95 @@ -use std::cmp::Ordering; -use std::path::Path; +use std::collections::HashMap; +use std::collections::HashSet; #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] pub struct Version(pub u64); -struct Index; +impl Version { + fn next(self) -> Self { + Version(self.0 + 1) + } +} -impl Index { +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct Hash([u8; 32]); + +#[derive(Clone)] +struct IndexHashEntry { + source_paths: HashSet, + storage_path: String, +} + +#[derive(Clone)] +struct IndexPathEntry { + hash: Hash, + version: Version, + storage_path: String, +} + +struct Index<'a> { + file_hashes: HashMap, + file_paths: HashMap<&'a str, IndexPathEntry>, +} + +impl<'a> Index<'a> { fn new() -> Self { - Self {} + Self { + file_hashes: HashMap::new(), + file_paths: HashMap::new() + } } - fn store(&mut self, path: &str, hash: &[u8]) -> (Version, String) { - (Version(0), "".to_string()) + fn store(&mut self, source_path: &'a str, hash: Hash) -> (Version, String) { + let path_entry = { + self.file_paths.get(source_path).map_or_else( + || IndexPathEntry { + hash, + version: Version(0), + storage_path: format!("{:X?}", hash.0), + }, + |old_entry| { + if old_entry.hash == hash { + old_entry.clone() // FIXME optimise + } else { + IndexPathEntry { + hash, + version: old_entry.version.next(), + storage_path: old_entry.storage_path.clone(), + } + } + }, + ) + }; + + self.file_paths.insert(source_path, path_entry.clone()); + + let hash_entry = { + self.file_hashes.get(&hash).map_or_else( + || { + let mut source_paths = HashSet::new(); + source_paths.insert(source_path.to_string()); + IndexHashEntry { + source_paths, + storage_path: format!("{:X?}", hash.0), + } + }, + |old_entry| { + let mut source_paths = old_entry.source_paths.clone(); + source_paths.insert(source_path.to_string()); + IndexHashEntry { + source_paths, + storage_path: old_entry.storage_path.clone() + } + }, + ) + }; + + self.file_hashes.insert(hash, hash_entry); + (path_entry.version, path_entry.storage_path.to_string()) } - fn version(&self, hash: &[u8]) -> Version { - Version(0) + fn latest_version_for_path(&self, path: &str) -> Option { + self.file_paths.get(path).map_or(None, |entry| Some(entry.version)) } } @@ -25,14 +98,16 @@ mod should { use super::*; + const SOME_HASH: Hash = Hash([1; 32]); + const SOME_OTHER_HASH: Hash = Hash([2; 32]); + #[test] fn support_file_versions() { let mut index = Index::new(); - let (v1, _) = index.store("/some/path", "some hash".as_bytes()); - let (v2, _) = index.store("/some/path", "some other hash".as_bytes()); + let (v1, _) = index.store("/some/path", SOME_HASH); + let (v2, _) = index.store("/some/path", SOME_OTHER_HASH); - assert_eq!(v1, index.version("some hash".as_bytes())); - assert_eq!(v2, index.version("some other hash".as_bytes())); + assert_eq!(v2, index.latest_version_for_path("/some/path").unwrap()); assert!(v2 > v1); } @@ -40,11 +115,11 @@ mod should { #[test] fn support_deduplication() { let mut index = Index::new(); - let (_, storage_path1) = index.store("/some/path", "same hash".as_bytes()); - let (_, storage_path2) = index.store("/some/path", "same hash".as_bytes()); - let (_, storage_path3) = index.store("/some/other/path", "same hash".as_bytes()); + let (_, storage_path1) = index.store("/some/path", SOME_HASH); + let (_, storage_path2) = index.store("/some/path", SOME_HASH); + let (_, storage_path3) = index.store("/some/other/path", SOME_HASH); assert_eq!(storage_path1, storage_path2); - assert_ne!(storage_path1, storage_path3); + assert_eq!(storage_path1, storage_path3); } }