forceps/
metadata.rs

1use crate::{ForcepError, HashBytes, Result};
2use std::path;
3use std::time;
4
5/// Metadata information about a certain entry in the cache
6///
7/// This metadata contains information about when the entry was last modified, the size (in bytes)
8/// of the entry, the hash integrity of the entry, etc.
9///
10/// # Examples
11///
12/// ```rust
13/// # #[tokio::main(flavor = "current_thread")]
14/// # async fn main() {
15/// use forceps::Cache;
16///
17/// let cache = Cache::new("./cache")
18///     .build()
19///     .await
20///     .unwrap();
21///
22/// cache.write(&b"MY_KEY", &b"Hello World").await.unwrap();
23///
24/// let metadata = cache.read_metadata(&b"MY_KEY").unwrap();
25/// # }
26/// ```
27#[derive(Debug)]
28pub struct Metadata {
29    /// Size in bytes of the corresponding entry
30    size: u64,
31    /// Last time this entry was modified, milliseconds since epoch
32    last_modified: u64,
33    /// Last time since this entry was accessed, milliseconds since epoch
34    last_accessed: u64,
35    /// Number of times this entry has been HIT (total accesses)
36    hits: u64,
37    /// Hash of the underlying data
38    integrity: HashBytes,
39}
40
41/// Database for cache entry metadata
42#[derive(Debug)]
43pub(crate) struct MetaDb {
44    db: sled::Db,
45}
46
47/// Milliseconds from epoch to now
48fn now_since_epoch() -> u64 {
49    time::SystemTime::now()
50        .duration_since(time::UNIX_EPOCH)
51        .map(|x| x.as_millis() as u64)
52        .unwrap_or(0)
53}
54
55impl Metadata {
56    /// Creates a new instance of [`Metadata`] from the given `data`
57    pub(crate) fn new(data: &[u8]) -> Self {
58        Self::new_with_hash(data, crate::hash::create(data))
59    }
60
61    pub(crate) fn new_with_hash(data: &[u8], hash: HashBytes) -> Self {
62        Self {
63            size: data.len() as u64,
64            last_modified: now_since_epoch(),
65            last_accessed: now_since_epoch(),
66            hits: 0,
67            integrity: hash,
68        }
69    }
70
71    /// Serializes the metadata into bytes
72    pub(crate) fn serialize(&self) -> Vec<u8> {
73        use bson::{
74            cstr,
75            raw::{RawBinaryRef, RawBson, RawDocumentBuf},
76        };
77
78        let mut doc = RawDocumentBuf::new();
79        doc.append(cstr!("size"), RawBson::Int64(self.size as i64));
80        doc.append(
81            cstr!("last_modified"),
82            RawBson::Int64(self.last_modified as i64),
83        );
84        doc.append(
85            cstr!("last_accessed"),
86            RawBson::Int64(self.last_accessed as i64),
87        );
88        doc.append(cstr!("hits"), RawBson::Int64(self.hits as i64));
89        doc.append(
90            cstr!("integrity"),
91            RawBinaryRef {
92                subtype: bson::spec::BinarySubtype::Generic,
93                bytes: &self.integrity,
94            },
95        );
96        doc.into_bytes()
97    }
98
99    /// Deserializes a slice of bytes into metadata
100    pub(crate) fn deserialize(buf: &[u8]) -> Result<Self> {
101        use bson::{error::Error as BsonError, raw::RawDocument, spec::BinarySubtype};
102
103        let doc = RawDocument::from_bytes(buf).map_err(ForcepError::MetaDe)?;
104
105        let make_error = |key: &str, msg: &str| -> ForcepError {
106            let io_err = std::io::Error::new(std::io::ErrorKind::InvalidData, msg.to_owned());
107            let mut err = BsonError::from(io_err);
108            err.key = Some(key.to_owned());
109            ForcepError::MetaDe(err)
110        };
111
112        let read_u64 = |key: &str| -> Result<u64> {
113            doc.get_i64(key)
114                .map(|v| v as u64)
115                .map_err(ForcepError::MetaDe)
116        };
117
118        let size = read_u64("size")?;
119        let last_modified = read_u64("last_modified")?;
120        let last_accessed = read_u64("last_accessed")?;
121        let hits = read_u64("hits")?;
122
123        let binary = doc.get_binary("integrity").map_err(ForcepError::MetaDe)?;
124        if binary.subtype != BinarySubtype::Generic {
125            return Err(make_error("integrity", "expected generic binary subtype"));
126        }
127        if binary.bytes.len() != crate::hash::HASH_LEN {
128            return Err(make_error(
129                "integrity",
130                "integrity must contain HASH_LEN bytes",
131            ));
132        }
133        let mut integrity: HashBytes = [0u8; crate::hash::HASH_LEN];
134        integrity.copy_from_slice(binary.bytes);
135
136        Ok(Self {
137            size,
138            last_modified,
139            last_accessed,
140            hits,
141            integrity,
142        })
143    }
144
145    /// The size in bytes of the corresponding cache entry.
146    #[inline]
147    pub fn get_size(&self) -> u64 {
148        self.size
149    }
150
151    /// Retrives the last time this entry was modified.
152    #[inline]
153    pub fn get_last_modified(&self) -> Option<time::SystemTime> {
154        match self.last_modified {
155            0 => None,
156            millis => Some(time::UNIX_EPOCH + time::Duration::from_millis(millis)),
157        }
158    }
159    /// Retrieves the raw `last_modified` time, which is the milliseconds since
160    /// [`time::UNIX_EPOCH`]. If the returned result is `0`, that means there is no `last_modified`
161    /// time.
162    #[inline]
163    pub fn get_last_modified_raw(&self) -> u64 {
164        self.last_modified
165    }
166
167    /// The total number of times this entry has been read.
168    ///
169    /// **NOTE:** This will be 0 unless `track_access` is enabled from the [`CacheBuilder`]
170    ///
171    /// [`CacheBuilder`]: crate::CacheBuilder
172    #[inline]
173    pub fn get_hits(&self) -> u64 {
174        self.hits
175    }
176
177    /// Retrives the last time this entry was accessed (read from).
178    ///
179    /// **NOTE:** This will be the same as [`get_last_modified`] unless `track_access` is enabled from
180    /// the [`CacheBuilder`]
181    ///
182    /// [`get_last_modified`]: Self::get_last_modified
183    /// [`CacheBuilder`]: crate::CacheBuilder
184    #[inline]
185    pub fn get_last_acccessed(&self) -> Option<time::SystemTime> {
186        match self.last_accessed {
187            0 => None,
188            millis => Some(time::UNIX_EPOCH + time::Duration::from_millis(millis)),
189        }
190    }
191    /// Retrieves the raw `last_accessed` time, which is the milliseconds since
192    /// [`time::UNIX_EPOCH`]. If the returned result is `0`, that means there is no `last_accessed`
193    /// time.
194    ///
195    /// **NOTE:** This will be the same as [`get_last_modified_raw`] unless `track_access` is enabled
196    /// from the [`CacheBuilder`]
197    ///
198    /// [`get_last_modified_raw`]: Self::get_last_modified_raw
199    /// [`CacheBuilder`]: crate::CacheBuilder
200    #[inline]
201    pub fn get_last_accessed_raw(&self) -> u64 {
202        self.last_accessed
203    }
204
205    /// Retrieves the internal [`HashBytes`] integrity of the corresponding metadata entry.
206    #[inline]
207    pub fn get_integrity(&self) -> &HashBytes {
208        &self.integrity
209    }
210
211    /// Verifies that the metadata integrity matches the integrity of the data provided.
212    #[inline]
213    pub fn check_integrity_of(&self, data: &[u8]) -> bool {
214        self.integrity == crate::hash::create(data)
215    }
216}
217
218impl MetaDb {
219    /// Initializes a new metadata database with sled.
220    pub fn new(path: &path::Path) -> Result<Self> {
221        sled::open(path)
222            .map_err(ForcepError::MetaDb)
223            .map(|db| Self { db })
224    }
225
226    pub fn get_db_ref(&self) -> &sled::Db {
227        &self.db
228    }
229
230    /// Retrieves an entry in the metadata database with the corresponding key.
231    pub fn get_metadata(&self, key: &[u8]) -> Result<Metadata> {
232        let data = match self.db.get(key) {
233            Ok(Some(data)) => data,
234            Ok(None) => return Err(ForcepError::MetaNotFound),
235            Err(e) => return Err(ForcepError::MetaDb(e)),
236        };
237        Metadata::deserialize(&data)
238    }
239
240    /// Inserts a new entry into the metadata database for the associated key and data.
241    ///
242    /// If a previous entry exists, it is simply overwritten.
243    pub fn insert_metadata_for(&self, key: &[u8], data: &[u8]) -> Result<Metadata> {
244        let meta = Metadata::new(data);
245        let bytes = Metadata::serialize(&meta);
246        self.db
247            .insert(key, &bytes[..])
248            .map_err(ForcepError::MetaDb)?;
249        Ok(meta)
250    }
251
252    pub fn remove_metadata_for(&self, key: &[u8]) -> Result<Metadata> {
253        match self.db.remove(key) {
254            Ok(Some(m)) => Metadata::deserialize(&m[..]),
255            Ok(None) => Err(ForcepError::MetaNotFound),
256            Err(e) => Err(ForcepError::MetaDb(e)),
257        }
258    }
259
260    /// Will increment the `hits` counter and set the `last_accessed` value to now for the found
261    /// metadata key.
262    pub fn track_access_for(&self, key: &[u8], existing: Option<Metadata>) -> Result<Metadata> {
263        let mut meta = match existing {
264            Some(existing) => existing,
265            None => {
266                let entry = self
267                    .db
268                    .get(key)
269                    .map_err(ForcepError::MetaDb)?
270                    .ok_or(ForcepError::MetaNotFound)?;
271                Metadata::deserialize(&entry[..])?
272            }
273        };
274        meta.last_accessed = now_since_epoch();
275        meta.hits += 1;
276        self.db
277            .insert(key, Metadata::serialize(&meta))
278            .map_err(ForcepError::MetaDb)?;
279        Ok(meta)
280    }
281
282    /// Iterator over the entire metadata database
283    pub fn metadata_iter(&self) -> impl Iterator<Item = Result<(Vec<u8>, Metadata)>> {
284        self.db.iter().map(|x| match x {
285            Ok((key, data)) => Metadata::deserialize(&data[..]).map(|m| (key.to_vec(), m)),
286            Err(e) => Err(ForcepError::MetaDb(e)),
287        })
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use super::*;
294    const DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
295
296    fn create_db() -> Result<MetaDb> {
297        const META_TESTDIR: &str = "./cache/test-index";
298        let path = path::PathBuf::from(META_TESTDIR);
299        MetaDb::new(&path)
300    }
301
302    #[test]
303    fn create_metadb() {
304        create_db().unwrap();
305    }
306
307    #[test]
308    fn db_read_write() {
309        let db = create_db().unwrap();
310        db.insert_metadata_for(&DATA, &DATA).unwrap();
311        let meta = db.get_metadata(&DATA).unwrap();
312        assert_eq!(meta.get_size(), DATA.len() as u64);
313    }
314
315    #[test]
316    fn check_integrity() {
317        let db = create_db().unwrap();
318        let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
319        assert!(meta.check_integrity_of(&DATA));
320    }
321
322    #[test]
323    fn last_modified() {
324        let db = create_db().unwrap();
325        let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
326        // make sure last-modified date is within last second
327        assert_eq!(
328            meta.get_last_modified()
329                .unwrap()
330                .elapsed()
331                .unwrap()
332                .as_secs(),
333            0
334        );
335    }
336
337    #[test]
338    fn metadata_ser_de() {
339        let db = create_db().unwrap();
340        let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
341        let ser_bytes = meta.serialize();
342        let de = Metadata::deserialize(&ser_bytes).unwrap();
343        assert_eq!(meta.get_integrity(), de.get_integrity());
344    }
345}