Skip to main content

relay_knowledge/watcher/hash_cache/
mod.rs

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