relay_knowledge/watcher/hash_cache/
mod.rs1use std::collections::{HashMap, VecDeque};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone)]
5pub struct ContentHashCache {
6 capacity: usize,
7 entries: HashMap<PathBuf, u64>,
8 insertion_order: VecDeque<PathBuf>,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ContentHashObservation {
13 pub changed: bool,
14 pub hash: u64,
15}
16
17impl ContentHashCache {
18 pub fn new(capacity: usize) -> Self {
19 Self {
20 capacity,
21 entries: HashMap::new(),
22 insertion_order: VecDeque::new(),
23 }
24 }
25
26 pub fn check_and_update(&mut self, path: PathBuf, content: &[u8]) -> ContentHashObservation {
27 self.check_hash_and_update(path, content_hash64(content))
28 }
29
30 pub fn check_hash_and_update(
31 &mut self,
32 path: PathBuf,
33 new_hash: u64,
34 ) -> ContentHashObservation {
35 let observation = self.observe_hash(&path, new_hash);
36 if observation.changed {
37 self.record_hash(path, new_hash);
38 }
39 observation
40 }
41
42 pub fn observe_hash(&self, path: &PathBuf, new_hash: u64) -> ContentHashObservation {
43 if self.capacity == 0 {
44 return ContentHashObservation {
45 changed: true,
46 hash: new_hash,
47 };
48 }
49 let changed = match self.entries.get(path) {
50 Some(&existing) => existing != new_hash,
51 None => true,
52 };
53 ContentHashObservation {
54 changed,
55 hash: new_hash,
56 }
57 }
58
59 pub fn record_hash(&mut self, path: PathBuf, new_hash: u64) {
60 if self.capacity == 0 {
61 return;
62 }
63 if self.entries.len() >= self.capacity && !self.entries.contains_key(&path) {
64 self.evict_oldest();
65 }
66 if !self.entries.contains_key(&path) {
67 self.insertion_order.push_back(path.clone());
68 }
69 self.entries.insert(path, new_hash);
70 }
71
72 pub fn remove(&mut self, path: &PathBuf) {
73 self.entries.remove(path);
74 self.insertion_order.retain(|entry| entry != path);
75 }
76
77 pub fn len(&self) -> usize {
78 self.entries.len()
79 }
80
81 pub fn is_empty(&self) -> bool {
82 self.entries.is_empty()
83 }
84
85 pub fn capacity(&self) -> usize {
86 self.capacity
87 }
88
89 pub fn clear(&mut self) {
90 self.entries.clear();
91 self.insertion_order.clear();
92 }
93
94 fn evict_oldest(&mut self) {
95 while let Some(oldest_key) = self.insertion_order.pop_front() {
96 if self.entries.remove(&oldest_key).is_some() {
97 break;
98 }
99 }
100 }
101}
102
103pub(super) fn content_hash64(bytes: &[u8]) -> u64 {
104 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
105 const FNV_PRIME: u64 = 0x100000001b3;
106
107 let mut hash = FNV_OFFSET_BASIS;
108 for byte in bytes {
109 hash ^= u64::from(*byte);
110 hash = hash.wrapping_mul(FNV_PRIME);
111 }
112 hash
113}
114
115#[cfg(test)]
116#[path = "mod_tests.rs"]
117mod tests;