hexroll3_scroll/
repository.rs

1/*
2// Copyright (C) 2020-2025 Pen, Dice & Paper
3//
4// This program is dual-licensed under the following terms:
5//
6// Option 1: (Non-Commercial) GNU Affero General Public License (AGPL)
7// This program is free software: you can redistribute it and/or modify
8// it under the terms of the GNU Affero General Public License as
9// published by the Free Software Foundation, either version 3 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU Affero General Public License for more details.
16//
17// You should have received a copy of the GNU Affero General Public License
18// along with this program. If not, see <http://www.gnu.org/licenses/>.
19//
20// Option 2: Commercial License
21// For commercial use, you are required to obtain a separate commercial
22// license. Please contact ithai at pendicepaper.com
23// for more information about commercial licensing terms.
24*/
25use anyhow::{anyhow, Result};
26use redb::{ReadableTable, Savepoint};
27use serde::{Deserialize, Serialize};
28use std::{
29    cell::RefCell,
30    collections::HashMap,
31    sync::{Arc, Mutex},
32};
33
34pub struct Repository {
35    pub db: Option<Arc<Mutex<redb::Database>>>,
36}
37
38impl Repository {
39    pub fn new() -> Self {
40        Repository { db: None }
41    }
42
43    pub fn create(&mut self, filename: &str) -> Result<&mut Self> {
44        self.db = Some(Arc::new(Mutex::new(redb::Database::create(filename)?)));
45        Ok(self)
46    }
47
48    pub fn open(&mut self, filename: &str) -> Result<&mut Self> {
49        self.db = Some(Arc::new(Mutex::new(redb::Database::open(filename)?)));
50        Ok(self)
51    }
52
53    pub fn load(&self, uid: &str) -> Result<serde_json::Value> {
54        let tx = self
55            .db
56            .as_ref()
57            .ok_or_else(|| anyhow!("Database reference is missing"))?
58            .lock()
59            .map_err(|_| anyhow!("Failed to acquire lock"))?
60            .begin_read()
61            .map_err(|_| anyhow!("Failed to begin read transaction"))?;
62
63        const TABLE: redb::TableDefinition<String, JsonValue> =
64            redb::TableDefinition::new("my_data2");
65
66        let table = tx
67            .open_table(TABLE)
68            .map_err(|_| anyhow!("Failed to open table"))?;
69
70        match table.get(uid.to_string()) {
71            Ok(Some(ret)) => Ok(ret.value().value),
72            Ok(None) => Err(anyhow!("No entry found for uid: {}", uid)),
73            Err(_) => Err(anyhow!("Error retrieving entry for uid: {}", uid)),
74        }
75    }
76
77    pub fn mutate<F, R>(&self, mut f: F) -> Result<R>
78    where
79        F: FnMut(&mut ReadWriteTransaction) -> Result<R>,
80    {
81        let tx = self
82            .db
83            .as_ref()
84            .ok_or_else(|| anyhow!("Database not initialized"))?
85            .lock()
86            .map_err(|_| anyhow!("Failed to acquire lock"))?
87            .begin_write()
88            .map_err(|_| anyhow!("Failed to begin write transaction"))?;
89        const TABLE: redb::TableDefinition<String, JsonValue> =
90            redb::TableDefinition::new("my_data2");
91
92        let closure_result = {
93            let table = tx.open_table(TABLE)?;
94            let mut repo_tx = ReadWriteTransaction {
95                cache: HashMap::new(),
96                table,
97            };
98            f(&mut repo_tx)?
99        };
100
101        tx.commit()
102            .map_err(|_| anyhow!("Failed to commit transaction"))?;
103        Ok(closure_result)
104    }
105
106    pub fn savepoint(&self) -> Result<Savepoint> {
107        let tx = self
108            .db
109            .as_ref()
110            .ok_or_else(|| anyhow!("Database not initialized"))?
111            .lock()
112            .map_err(|_| anyhow!("Failed to acquire lock"))?
113            .begin_write()
114            .map_err(|_| anyhow!("Failed to begin write transaction"))?;
115        Ok(tx.ephemeral_savepoint()?)
116    }
117
118    pub fn restore(&self, savepoint: &Savepoint) -> Result<()> {
119        let mut tx = self
120            .db
121            .as_ref()
122            .ok_or_else(|| anyhow!("Database not initialized"))?
123            .lock()
124            .map_err(|_| anyhow!("Failed to acquire lock"))?
125            .begin_write()
126            .map_err(|_| anyhow!("Failed to begin write transaction"))?;
127        tx.restore_savepoint(savepoint)?;
128        Ok(tx.commit()?)
129    }
130
131    pub fn inspect<F, R>(&self, f: F) -> Result<R>
132    where
133        F: FnMut(&mut ReadOnlyTransaction) -> Result<R>,
134    {
135        let f = RefCell::new(f);
136        let tx = self
137            .db
138            .as_ref()
139            .ok_or_else(|| anyhow!("Database reference is missing"))?
140            .lock()
141            .map_err(|_| anyhow!("Failed to acquire database lock"))?
142            .begin_read()
143            .map_err(|_| anyhow!("Failed to begin read transaction"))?;
144        const TABLE: redb::TableDefinition<String, JsonValue> =
145            redb::TableDefinition::new("my_data2");
146        let closure_result: R;
147        {
148            let table = tx.open_table(TABLE)?;
149            let mut repo_tx = ReadOnlyTransaction {
150                cache: HashMap::new(),
151                table,
152            };
153            closure_result = f.borrow_mut()(&mut repo_tx)?;
154        }
155        Ok(closure_result)
156    }
157}
158
159impl Default for Repository {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165pub trait ReadOnlyLoader {
166    fn retrieve(&self, uid: &str) -> Result<JsonValue>;
167}
168
169pub struct ReadWriteTransaction<'a> {
170    cache: HashMap<String, serde_json::Value>,
171    pub table: redb::Table<'a, String, JsonValue>,
172}
173
174pub struct ReadOnlyTransaction {
175    pub cache: HashMap<String, serde_json::Value>,
176    pub table: redb::ReadOnlyTable<String, JsonValue>,
177}
178
179impl<'a> ReadOnlyLoader for ReadWriteTransaction<'a> {
180    fn retrieve(&self, uid: &str) -> Result<JsonValue> {
181        if let Some(cached) = self.cache.get(uid) {
182            Ok(JsonValue {
183                value: cached.clone(),
184            })
185        } else if let Ok(Some(ret)) = self.table.get(uid.to_string()) {
186            Ok(ret.value())
187        } else {
188            Err(anyhow!("error in loading {}", uid))
189        }
190    }
191}
192
193impl<'a> ReadWriteTransaction<'a> {
194    pub fn _has_cache(&mut self, uid: &str) -> bool {
195        self.cache.contains_key(uid)
196    }
197    pub fn create(&mut self, uid: &str) -> Result<&mut serde_json::Value> {
198        if !(self.cache.contains_key(uid)) {
199            self.cache
200                .insert(uid.to_string(), serde_json::json!({"uid": uid}));
201        }
202        Ok(self.cache.get_mut(uid).unwrap())
203    }
204    pub fn load(&mut self, uid: &str) -> Result<&mut serde_json::Value> {
205        if !(self.cache.contains_key(uid)) {
206            self.cache
207                .insert(uid.to_string(), self.retrieve(uid)?.value);
208        }
209        Ok(self.cache.get_mut(uid).unwrap())
210    }
211    pub fn store(&mut self, uid: &str, value: &serde_json::Value) -> Result<()> {
212        self.table
213            .insert(
214                uid.to_string(),
215                JsonValue {
216                    value: value.clone(),
217                },
218            )
219            .map_err(|e| anyhow!(e))?;
220        Ok(())
221    }
222    pub fn save(&mut self, uid: &str) -> Result<()> {
223        if let Some(e) = self.cache.get(uid) {
224            self.table
225                .insert(uid.to_string(), &JsonValue { value: e.clone() })
226                .map_err(|e| anyhow!(e))?;
227            Ok(())
228        } else {
229            Err(anyhow!("Entity not found in cache"))
230        }
231    }
232    pub fn remove(&mut self, uid: &str) -> Result<()> {
233        self.table.remove(uid.to_string())?;
234        if self.cache.contains_key(uid) {
235            self.cache.remove(uid);
236        }
237        Ok(())
238    }
239    pub fn emplace_and_save(&mut self, uid: &str, v: serde_json::Value) -> Result<()> {
240        self.cache.insert(uid.to_string(), v);
241        self.save(uid)
242    }
243}
244
245impl ReadOnlyLoader for ReadOnlyTransaction {
246    fn retrieve(&self, uid: &str) -> Result<JsonValue> {
247        if let Some(cached) = self.cache.get(uid) {
248            Ok(JsonValue {
249                value: cached.clone(),
250            })
251        } else if let Ok(Some(ret)) = self.table.get(uid.to_string()) {
252            Ok(ret.value())
253        } else {
254            Err(anyhow!("error in loading {}", uid))
255        }
256    }
257}
258
259impl ReadOnlyTransaction {
260    pub fn fetch(&mut self, uid: &str) -> Result<&mut serde_json::Value> {
261        if !(self.cache.contains_key(uid)) {
262            self.cache
263                .insert(uid.to_string(), self.retrieve(uid)?.value);
264        }
265        Ok(self.cache.get_mut(uid).unwrap())
266    }
267    pub fn load(&self, uid: &str) -> Result<JsonValue> {
268        if let Some(cached) = self.cache.get(uid) {
269            Ok(JsonValue {
270                value: cached.clone(),
271            })
272        } else if let Ok(Some(ret)) = self.table.get(uid.to_string()) {
273            Ok(ret.value())
274        } else {
275            Err(anyhow!("error in loading {}", uid))
276        }
277    }
278}
279
280pub trait Entity {
281    fn values(&self) -> &serde_json::Value;
282    fn values_mut(&mut self) -> &mut serde_json::Value;
283    fn is_missing(&self, attr: &str) -> bool {
284        !self.values().as_object().unwrap().contains_key(attr)
285    }
286    fn clear(&mut self, attr: &str) {
287        self.values_mut().as_object_mut().unwrap().swap_remove(attr);
288    }
289}
290
291impl Entity for serde_json::Value {
292    fn values(&self) -> &serde_json::Value {
293        self
294    }
295    fn values_mut(&mut self) -> &mut serde_json::Value {
296        self
297    }
298}
299
300fn json_bytes<T>(structure: T) -> Vec<u8>
301where
302    T: serde::Serialize,
303{
304    let mut bytes: Vec<u8> = Vec::new();
305    ciborium::into_writer(&structure, &mut bytes).unwrap();
306    #[cfg(feature = "zstd")]
307    {
308        zstd::encode_all(bytes.as_slice(), 0).unwrap()
309    }
310    #[cfg(not(feature = "zstd"))]
311    {
312        bytes
313    }
314}
315
316fn bytes_json(bytes: &[u8]) -> serde_json::Value {
317    #[cfg(feature = "zstd")]
318    let cbor = {
319        let bytes = zstd::decode_all(bytes).unwrap();
320        ciborium::from_reader(bytes.as_slice()).unwrap();
321    };
322
323    #[cfg(not(feature = "zstd"))]
324    let cbor: ciborium::Value = ciborium::from_reader(bytes).unwrap();
325
326    serde_json::to_value(cbor).unwrap()
327}
328
329#[derive(Debug, Serialize, Deserialize)]
330pub struct JsonValue {
331    pub value: serde_json::Value,
332}
333
334impl redb::Value for JsonValue {
335    type SelfType<'a> = JsonValue
336        where
337        Self: 'a;
338    type AsBytes<'a> = Vec<u8>
339        where
340        Self: 'a;
341
342    fn fixed_width() -> Option<usize> {
343        None
344    }
345
346    fn from_bytes<'a>(data: &'a [u8]) -> JsonValue
347    where
348        Self: 'a,
349    {
350        JsonValue {
351            value: bytes_json(data),
352        }
353    }
354
355    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
356    where
357        Self: 'a,
358        Self: 'b,
359    {
360        json_bytes(&value.value)
361    }
362
363    fn type_name() -> redb::TypeName {
364        redb::TypeName::new("test::JsonValue")
365    }
366}