1use std::path::PathBuf;
12
13use redb::ReadableDatabase as _;
14
15use crate::client::{default_base_url, fetch_event_by_id, fetch_event_by_key};
16use crate::error::GlwEventCacheError;
17use crate::types::{EventId, GlwEvent, GlwEventKey};
18
19type CachedEvent = (Option<GlwEvent>, http_cache_semantics::CachePolicy);
21
22const GLW_EVENT_BY_ID_TABLE: redb::TableDefinition<u32, String> =
25 redb::TableDefinition::new("glw_event_by_id");
26
27const GLW_EVENT_BY_ID_POLICY_TABLE: redb::TableDefinition<u32, String> =
30 redb::TableDefinition::new("glw_event_by_id_policy");
31
32const GLW_EVENT_BY_KEY_TABLE: redb::TableDefinition<String, String> =
35 redb::TableDefinition::new("glw_event_by_key");
36
37const GLW_EVENT_BY_KEY_POLICY_TABLE: redb::TableDefinition<String, String> =
40 redb::TableDefinition::new("glw_event_by_key_policy");
41
42#[expect(
48 clippy::module_name_repetitions,
49 reason = "GlwEventCache is the primary public type of this module"
50)]
51#[derive(Debug)]
52pub struct GlwEventCache {
53 client: reqwest::Client,
55 base_url: url::Url,
57 db: redb::Database,
59 memory_by_id: lru::LruCache<EventId, CachedEvent>,
61 memory_by_key: lru::LruCache<GlwEventKey, CachedEvent>,
63}
64
65impl GlwEventCache {
66 pub fn new(
78 cache_directory: PathBuf,
79 base_url: Option<url::Url>,
80 ) -> Result<Self, GlwEventCacheError> {
81 let client = crate::client::build_http_client();
82 let base_url = match base_url {
83 Some(u) => u,
84 None => default_base_url().map_err(GlwEventCacheError::FetchError)?,
85 };
86 let db = redb::Database::create(cache_directory.join("glw_event.redb"))?;
87 Ok(Self {
88 client,
89 base_url,
90 db,
91 memory_by_id: lru::LruCache::unbounded(),
92 memory_by_key: lru::LruCache::unbounded(),
93 })
94 }
95
96 #[tracing::instrument(skip(self))]
105 pub async fn get_event_by_id(
106 &mut self,
107 id: EventId,
108 ) -> Result<Option<GlwEvent>, GlwEventCacheError> {
109 tracing::debug!("Retrieving GLW event by id {id}");
110 let cached = self.load_by_id_from_caches(id)?;
111 let (event, cache_policy) =
112 fetch_event_by_id(&self.client, &self.base_url, id, cached).await?;
113 self.store_by_id(id, event.as_ref(), &cache_policy)?;
114 Ok(event)
115 }
116
117 #[tracing::instrument(skip(self))]
124 pub async fn get_event_by_key(
125 &mut self,
126 key: &GlwEventKey,
127 ) -> Result<Option<GlwEvent>, GlwEventCacheError> {
128 tracing::debug!("Retrieving GLW event by key {key}");
129 let cached = self.load_by_key_from_caches(key)?;
130 let (event, cache_policy) =
131 fetch_event_by_key(&self.client, &self.base_url, key, cached).await?;
132 self.store_by_key(key, event.as_ref(), &cache_policy)?;
133 Ok(event)
134 }
135
136 pub async fn refresh_event_by_id(
143 &mut self,
144 id: EventId,
145 ) -> Result<Option<GlwEvent>, GlwEventCacheError> {
146 let (event, cache_policy) =
147 fetch_event_by_id(&self.client, &self.base_url, id, None).await?;
148 self.store_by_id(id, event.as_ref(), &cache_policy)?;
149 Ok(event)
150 }
151
152 pub async fn refresh_event_by_key(
159 &mut self,
160 key: &GlwEventKey,
161 ) -> Result<Option<GlwEvent>, GlwEventCacheError> {
162 let (event, cache_policy) =
163 fetch_event_by_key(&self.client, &self.base_url, key, None).await?;
164 self.store_by_key(key, event.as_ref(), &cache_policy)?;
165 Ok(event)
166 }
167
168 fn load_by_id_from_caches(
171 &mut self,
172 id: EventId,
173 ) -> Result<Option<CachedEvent>, GlwEventCacheError> {
174 if let Some(hit) = self.memory_by_id.get(&id) {
175 return Ok(Some(hit.to_owned()));
176 }
177 let read_txn = self.db.begin_read()?;
178 let policy = match read_txn.open_table(GLW_EVENT_BY_ID_POLICY_TABLE) {
179 Ok(table) => match table.get(id.get())? {
180 Some(value) => {
181 let policy: http_cache_semantics::CachePolicy =
182 serde_json::from_str(&value.value())?;
183 Some(policy)
184 }
185 None => None,
186 },
187 Err(_) => None,
188 };
189 let Some(policy) = policy else {
190 return Ok(None);
191 };
192 let value = match read_txn.open_table(GLW_EVENT_BY_ID_TABLE) {
193 Ok(table) => match table.get(id.get())? {
194 Some(value) => {
195 let event: Option<GlwEvent> = serde_json::from_str(&value.value())?;
196 event
197 }
198 None => None,
199 },
200 Err(_) => None,
201 };
202 Ok(Some((value, policy)))
203 }
204
205 fn load_by_key_from_caches(
208 &mut self,
209 key: &GlwEventKey,
210 ) -> Result<Option<CachedEvent>, GlwEventCacheError> {
211 if let Some(hit) = self.memory_by_key.get(key) {
212 return Ok(Some(hit.to_owned()));
213 }
214 let read_txn = self.db.begin_read()?;
215 let policy = match read_txn.open_table(GLW_EVENT_BY_KEY_POLICY_TABLE) {
216 Ok(table) => match table.get(key.as_str().to_owned())? {
217 Some(value) => {
218 let policy: http_cache_semantics::CachePolicy =
219 serde_json::from_str(&value.value())?;
220 Some(policy)
221 }
222 None => None,
223 },
224 Err(_) => None,
225 };
226 let Some(policy) = policy else {
227 return Ok(None);
228 };
229 let value = match read_txn.open_table(GLW_EVENT_BY_KEY_TABLE) {
230 Ok(table) => match table.get(key.as_str().to_owned())? {
231 Some(value) => {
232 let event: Option<GlwEvent> = serde_json::from_str(&value.value())?;
233 event
234 }
235 None => None,
236 },
237 Err(_) => None,
238 };
239 Ok(Some((value, policy)))
240 }
241
242 fn store_by_id(
246 &mut self,
247 id: EventId,
248 event: Option<&GlwEvent>,
249 cache_policy: &http_cache_semantics::CachePolicy,
250 ) -> Result<(), GlwEventCacheError> {
251 let write_txn = self.db.begin_write()?;
252 if cache_policy.is_storable() {
253 {
254 let mut policy_table = write_txn.open_table(GLW_EVENT_BY_ID_POLICY_TABLE)?;
255 policy_table.insert(id.get(), serde_json::to_string(cache_policy)?)?;
256 }
257 {
258 let mut value_table = write_txn.open_table(GLW_EVENT_BY_ID_TABLE)?;
259 value_table.insert(id.get(), serde_json::to_string(&event)?)?;
260 }
261 write_txn.commit()?;
262 self.memory_by_id
263 .put(id, (event.cloned(), cache_policy.clone()));
264 } else {
265 tracing::debug!("GLW event by id is not storable; evicting any stale entry");
266 {
267 let mut policy_table = write_txn.open_table(GLW_EVENT_BY_ID_POLICY_TABLE)?;
268 policy_table.remove(id.get())?;
269 }
270 {
271 let mut value_table = write_txn.open_table(GLW_EVENT_BY_ID_TABLE)?;
272 value_table.remove(id.get())?;
273 }
274 write_txn.commit()?;
275 self.memory_by_id.pop(&id);
276 }
277 Ok(())
278 }
279
280 fn store_by_key(
282 &mut self,
283 key: &GlwEventKey,
284 event: Option<&GlwEvent>,
285 cache_policy: &http_cache_semantics::CachePolicy,
286 ) -> Result<(), GlwEventCacheError> {
287 let write_txn = self.db.begin_write()?;
288 if cache_policy.is_storable() {
289 {
290 let mut policy_table = write_txn.open_table(GLW_EVENT_BY_KEY_POLICY_TABLE)?;
291 policy_table.insert(
292 key.as_str().to_owned(),
293 serde_json::to_string(cache_policy)?,
294 )?;
295 }
296 {
297 let mut value_table = write_txn.open_table(GLW_EVENT_BY_KEY_TABLE)?;
298 value_table.insert(key.as_str().to_owned(), serde_json::to_string(&event)?)?;
299 }
300 write_txn.commit()?;
301 self.memory_by_key
302 .put(key.to_owned(), (event.cloned(), cache_policy.clone()));
303 } else {
304 tracing::debug!("GLW event by key is not storable; evicting any stale entry");
305 {
306 let mut policy_table = write_txn.open_table(GLW_EVENT_BY_KEY_POLICY_TABLE)?;
307 policy_table.remove(key.as_str().to_owned())?;
308 }
309 {
310 let mut value_table = write_txn.open_table(GLW_EVENT_BY_KEY_TABLE)?;
311 value_table.remove(key.as_str().to_owned())?;
312 }
313 write_txn.commit()?;
314 self.memory_by_key.pop(key);
315 }
316 Ok(())
317 }
318}