Skip to main content

relay_knowledge/watcher/hash_cache/
mod.rs

1use 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;