1use crate::{ForcepError, HashBytes, Result};
2use std::path;
3use std::time;
4
5#[derive(Debug)]
28pub struct Metadata {
29 size: u64,
31 last_modified: u64,
33 last_accessed: u64,
35 hits: u64,
37 integrity: HashBytes,
39}
40
41#[derive(Debug)]
43pub(crate) struct MetaDb {
44 db: sled::Db,
45}
46
47fn 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 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 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 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 #[inline]
147 pub fn get_size(&self) -> u64 {
148 self.size
149 }
150
151 #[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 #[inline]
163 pub fn get_last_modified_raw(&self) -> u64 {
164 self.last_modified
165 }
166
167 #[inline]
173 pub fn get_hits(&self) -> u64 {
174 self.hits
175 }
176
177 #[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 #[inline]
201 pub fn get_last_accessed_raw(&self) -> u64 {
202 self.last_accessed
203 }
204
205 #[inline]
207 pub fn get_integrity(&self) -> &HashBytes {
208 &self.integrity
209 }
210
211 #[inline]
213 pub fn check_integrity_of(&self, data: &[u8]) -> bool {
214 self.integrity == crate::hash::create(data)
215 }
216}
217
218impl MetaDb {
219 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 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 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 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 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 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}