1use std::path::Path;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use bytes::Bytes;
5use redb::{Database, DatabaseError, ReadableDatabase, TableDefinition};
6
7pub enum CachePolicy {
9 None,
11 Full,
13 Read,
15 Update,
17}
18
19pub trait Cache {
21 fn get(&self, query: &str) -> Option<Bytes>;
23 fn set(&self, query: &str, response: &Bytes);
25}
26
27const TABLE: TableDefinition<&str, (u64, &[u8])> = TableDefinition::new("tmdb_responses");
28
29pub struct RedbCache {
31 db: Database,
32}
33
34impl RedbCache {
35 pub fn new(path: &Path) -> Result<Self, DatabaseError> {
37 Ok(Self {
38 db: Database::create(path)?,
39 })
40 }
41
42 fn write(&self, timestamp: u64, query: &str, response: &Bytes) -> Option<()> {
44 let write_txn = self.db.begin_write().ok()?;
45 {
46 let mut table = write_txn.open_table(TABLE).ok()?;
47 table
48 .insert(query, (timestamp, response.iter().as_slice()))
49 .ok()?;
50 }
51 write_txn.commit().ok()
52 }
53}
54
55impl Cache for RedbCache {
56 fn get(&self, query: &str) -> Option<Bytes> {
57 let read_txn = self.db.begin_read().ok()?;
58 let table = read_txn.open_table(TABLE).ok()?;
59
60 let result = table.get(query).ok()??;
61 let (timestamp, data) = result.value();
62
63 let now = SystemTime::now()
64 .duration_since(UNIX_EPOCH)
65 .map(|d| d.as_secs())
66 .unwrap_or(0);
67
68 if now.saturating_sub(timestamp) >= 60 * 60 * 24 * 30 * 6 {
69 None
70 } else {
71 Some(Bytes::copy_from_slice(data))
72 }
73 }
74
75 fn set(&self, query: &str, response: &Bytes) {
76 let now = SystemTime::now()
77 .duration_since(UNIX_EPOCH)
78 .map(|d| d.as_secs())
79 .unwrap_or(0);
80
81 self.write(now, query, response);
82 }
83}