1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
use crate::{ForcepError, Result};
use std::io;
use std::path;
use std::time;
/// Type definition for an array of bytes that make up an `md5` hash.
pub type Md5Bytes = [u8; 16];
/// Metadata information about a certain entry in the cache
///
/// This metadata contains information about when the entry was last modified, the size (in bytes)
/// of the entry, the `md5` integrity of the entry, etc.
///
/// # Examples
///
/// ```rust
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() {
/// use forceps::Cache;
///
/// let cache = Cache::new("./cache")
/// .build()
/// .await
/// .unwrap();
///
/// cache.write(&b"MY_KEY", &b"Hello World").await.unwrap();
///
/// let metadata = cache.read_metadata(&b"MY_KEY").unwrap();
/// # }
/// ```
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Metadata {
/// Size in bytes of the corresponding entry
size: u64,
/// Last time this entry was modified, milliseconds since epoch
last_modified: u64,
/// Last time since this entry was accessed, milliseconds since epoch
last_accessed: u64,
/// Number of times this entry has been HIT (total accesses)
hits: u64,
/// Md5 hash of the underlying data
integrity: Md5Bytes,
}
/// Database for cache entry metadata
#[derive(Debug)]
pub(crate) struct MetaDb {
db: sled::Db,
}
/// Milliseconds from epoch to now
fn now_since_epoch() -> u64 {
time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.map(|x| x.as_millis() as u64)
.unwrap_or(0)
}
impl Metadata {
/// Creates a new instance of [`Metadata`] from the given `data`
pub(crate) fn new(data: &[u8]) -> Self {
Self {
size: data.len() as u64,
last_modified: now_since_epoch(),
last_accessed: now_since_epoch(),
hits: 0,
integrity: md5::compute(data).into(),
}
}
/// Serializes the metadata into bytes
pub(crate) fn serialize(&self) -> Result<Vec<u8>> {
let document = bson::to_document(self).map_err(ForcepError::MetaSer)?;
// write document contents to memory stream
let mut buf = Vec::<u8>::new();
let mut writer = io::Cursor::new(&mut buf);
document
.to_writer(&mut writer)
.map_err(ForcepError::MetaSer)?;
Ok(buf)
}
/// Deserializes a slice of bytes into metadata
pub(crate) fn deserialize(buf: &[u8]) -> Result<Self> {
// create a reader so we can convert the document
let mut cursor = io::Cursor::new(buf);
let document = bson::Document::from_reader(&mut cursor).map_err(ForcepError::MetaDe)?;
bson::from_document(document).map_err(ForcepError::MetaDe)
}
/// The size in bytes of the corresponding cache entry.
#[inline]
pub fn get_size(&self) -> u64 {
self.size
}
/// Retrives the last time this entry was modified.
pub fn get_last_modified(&self) -> Option<time::SystemTime> {
match self.last_modified {
0 => None,
millis => Some(time::UNIX_EPOCH + time::Duration::from_millis(millis)),
}
}
/// Retrieves the raw `last_modified` time, which is the milliseconds since
/// [`time::UNIX_EPOCH`]. If the returned result is `0`, that means there is no `last_modified`
/// time.
#[inline]
pub fn get_last_modified_raw(&self) -> u64 {
self.last_modified
}
/// The total number of times this entry has been read.
///
/// **NOTE:** This will be 0 unless `track_access` is enabled from the [`CacheBuilder`]
///
/// [`CacheBuilder`]: crate::CacheBuilder
#[inline]
pub fn get_hits(&self) -> u64 {
self.hits
}
/// Retrives the last time this entry was accessed (read from).
///
/// **NOTE:** This will be the same as [`get_last_modified`] unless `track_access` is enabled from
/// the [`CacheBuilder`]
///
/// [`get_last_modified`]: Self::get_last_modified
/// [`CacheBuilder`]: crate::CacheBuilder
pub fn get_last_acccessed(&self) -> Option<time::SystemTime> {
match self.last_accessed {
0 => None,
millis => Some(time::UNIX_EPOCH + time::Duration::from_millis(millis)),
}
}
/// Retrieves the raw `last_accessed` time, which is the milliseconds since
/// [`time::UNIX_EPOCH`]. If the returned result is `0`, that means there is no `last_accessed`
/// time.
///
/// **NOTE:** This will be the same as [`get_last_modified_raw`] unless `track_access` is enabled
/// from the [`CacheBuilder`]
///
/// [`get_last_modified_raw`]: Self::get_last_modified_raw
/// [`CacheBuilder`]: crate::CacheBuilder
#[inline]
pub fn get_last_accessed_raw(&self) -> u64 {
self.last_accessed
}
/// Retrieves the internal [`Md5Bytes`] integrity of the corresponding metadata entry.
#[inline]
pub fn get_integrity(&self) -> &Md5Bytes {
&self.integrity
}
/// Verifies that the metadata integrity matches the integrity of the data provided.
#[inline]
pub fn check_integrity_of(&self, data: &[u8]) -> bool {
let other_integrity: Md5Bytes = md5::compute(data).into();
other_integrity == self.integrity
}
}
impl MetaDb {
/// Initializes a new metadata database with sled.
pub fn new(path: &path::Path) -> Result<Self> {
sled::open(path)
.map_err(ForcepError::MetaDb)
.map(|db| Self { db })
}
/// Retrieves an entry in the metadata database with the corresponding key.
pub fn get_metadata(&self, key: &[u8]) -> Result<Metadata> {
let data = match self.db.get(key) {
Ok(Some(data)) => data,
Ok(None) => return Err(ForcepError::MetaNotFound),
Err(e) => return Err(ForcepError::MetaDb(e)),
};
Metadata::deserialize(&data)
}
/// Inserts a new entry into the metadata database for the associated key and data.
///
/// If a previous entry exists, it is simply overwritten.
pub fn insert_metadata_for(&self, key: &[u8], data: &[u8]) -> Result<Metadata> {
let meta = Metadata::new(data);
let bytes = Metadata::serialize(&meta)?;
self.db
.insert(key, &bytes[..])
.map_err(ForcepError::MetaDb)?;
Ok(meta)
}
pub fn remove_metadata_for(&self, key: &[u8]) -> Result<Metadata> {
match self.db.remove(key) {
Ok(Some(m)) => Metadata::deserialize(&m[..]),
Ok(None) => Err(ForcepError::MetaNotFound),
Err(e) => Err(ForcepError::MetaDb(e)),
}
}
/// Will increment the `hits` counter and set the `last_accessed` value to now for the found
/// metadata key.
pub fn track_access_for(&self, key: &[u8]) -> Result<Metadata> {
let mut meta = match self.db.get(key) {
Ok(Some(entry)) => Metadata::deserialize(&entry[..])?,
Err(e) => return Err(ForcepError::MetaDb(e)),
Ok(None) => return Err(ForcepError::MetaNotFound),
};
meta.last_accessed = now_since_epoch();
meta.hits += 1;
self.db
.insert(key, Metadata::serialize(&meta)?)
.map_err(ForcepError::MetaDb)?;
Ok(meta)
}
/// Iterator over the entire metadata database
pub fn metadata_iter(&self) -> impl Iterator<Item = Result<(Vec<u8>, Metadata)>> {
self.db.iter().map(|x| match x {
Ok((key, data)) => Metadata::deserialize(&data[..]).map(|m| (key.to_vec(), m)),
Err(e) => Err(ForcepError::MetaDb(e)),
})
}
}
#[cfg(test)]
mod test {
use super::*;
const DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
fn create_db() -> Result<MetaDb> {
const META_TESTDIR: &str = "./cache/test-index";
let path = path::PathBuf::from(META_TESTDIR);
MetaDb::new(&path)
}
#[test]
fn create_metadb() {
create_db().unwrap();
}
#[test]
fn db_read_write() {
let db = create_db().unwrap();
db.insert_metadata_for(&DATA, &DATA).unwrap();
let meta = db.get_metadata(&DATA).unwrap();
assert_eq!(meta.get_size(), DATA.len() as u64);
}
#[test]
fn check_integrity() {
let db = create_db().unwrap();
let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
assert!(meta.check_integrity_of(&DATA));
}
#[test]
fn last_modified() {
let db = create_db().unwrap();
let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
// make sure last-modified date is within last second
assert!(
meta.get_last_modified()
.unwrap()
.elapsed()
.unwrap()
.as_secs()
== 0
);
}
#[test]
fn metadata_ser_de() {
let db = create_db().unwrap();
let meta = db.insert_metadata_for(&DATA, &DATA).unwrap();
let ser_bytes = meta.serialize().unwrap();
let de = Metadata::deserialize(&ser_bytes).unwrap();
assert_eq!(meta.get_integrity(), de.get_integrity());
}
}