nodb/nodb.rs
1use std::{
2 fs::{read, rename, write, DirBuilder},
3 path::{Path, PathBuf},
4 time::{Duration, Instant, SystemTime, UNIX_EPOCH},
5};
6
7use anyhow::{anyhow, Result};
8use serde::{de::DeserializeOwned, Serialize};
9
10use crate::{
11 crypto::B64,
12 ext::NoDbExt,
13 iter::{NoDbIter, NoDbListIter},
14 ser::{SerializationMethod, SerializeMethod, Serializer},
15 DbListMap, DbMap,
16};
17
18const B64: B64 = B64::new();
19
20/// An enum that determines the policy of dumping NoDb changes into the file
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum DumpPolicy {
23 /// Never dump the changes into the file
24 Never,
25 /// Every change will be dumped immediately and automatically to the file
26 Auto,
27 #[default]
28 /// Data won't be dumped unless the developer calls [NoDb::dump()](struct.NoDb.html#method.dump) proactively to dump the data
29 OnCall,
30 /// Changes will be dumped to the file periodically, no sooner than the Duration provided by the developer.
31 /// The way this mechanism works is as follows: each time there is a DB change the last DB dump time is checked.
32 /// If the time that has passed since the last dump is higher than Duration, changes will be dumped,
33 /// otherwise changes will not be dumped.
34 Periodic(Duration),
35}
36
37/// A struct that represents a NoDb object.
38pub struct NoDb {
39 pub map: DbMap,
40 pub list_map: DbListMap,
41 ser: Serializer,
42 pub path: PathBuf,
43 pub policy: DumpPolicy,
44 pub last_dump: Instant,
45}
46
47impl NoDb {
48 /// Constructs a new `NoDb` instance.
49 ///
50 /// # Examples
51 ///
52 /// ```no_run
53 /// use nodb::{NoDb, DumpPolicy, SerializationMethod};
54 ///
55 /// let mut db = NoDb::new("example.db", DumpPolicy::AutoDump, SerializationMethod::Json);
56 /// ```
57
58 pub fn new<P: AsRef<Path>>(
59 db_path: P,
60 policy: DumpPolicy,
61 ser_method: SerializationMethod,
62 ) -> Self {
63 let path = db_path.as_ref().to_path_buf();
64
65 if !path.exists() {
66 let parent = path.parent().unwrap();
67 DirBuilder::new().recursive(true).create(parent).unwrap();
68 }
69
70 NoDb {
71 map: DbMap::new(),
72 list_map: DbListMap::new(),
73 ser: Serializer::from(ser_method),
74 path,
75 policy,
76 last_dump: Instant::now(),
77 }
78 }
79
80 /// Loads a `NoDb` instance from a file.
81 ///
82 /// This method tries to load a DB from a file. Upon success an instance of `Ok(NoDb)` is returned,
83 /// otherwise an `anyhow::Error` object is returned.
84 ///
85 /// # Examples
86 ///
87 /// ```no_run
88 /// use nodb::{NoDb, DumpPolicy, SerializationMethod};
89 /// let nodb = NoDb::load("example.db", DumpPolicy::Auto, SerializationMethod::Json).unwrap();
90 /// ```
91
92 pub fn load<P: AsRef<Path>>(
93 db_path: P,
94 policy: DumpPolicy,
95 ser_method: SerializationMethod,
96 ) -> Result<Self> {
97 let content = read(&db_path)?;
98 let decrypted_content = B64.decrypt(content)?;
99 let ser = Serializer::from(ser_method);
100 let (map, list_map) = ser.deserialized_db(&decrypted_content)?;
101 let path_buf = db_path.as_ref().to_path_buf();
102
103 Ok(NoDb {
104 map,
105 list_map,
106 ser,
107 path: path_buf,
108 policy,
109 last_dump: Instant::now(),
110 })
111 }
112
113 /// Dump the data to the file.
114 ///
115 /// Calling this method is necessary only if the DB is loaded or created with a dump policy other than
116 /// [DumpPolicy::Auto](enum.DumpPolicy.html#variant.Auto), otherwise the data
117 /// is dumped to the file upon every change unless the dump policy is
118 /// [DumpPolicy::Never](enum.DumpPolicy.html#variant.Never).
119 ///
120 /// This method returns `Ok(())` if dump is successful, Or an `anyhow::Error` otherwise.
121
122 pub fn dump(&mut self) -> Result<()> {
123 if let DumpPolicy::Never = self.policy {
124 return Ok(());
125 }
126 let data = self.ser.serialize_db(&self.map, &self.list_map)?;
127 let encrypted_data = B64.encrypt(data);
128 let tmp = format!(
129 "{}.tmp.{}",
130 self.path.to_str().unwrap_or("db"),
131 SystemTime::now()
132 .duration_since(UNIX_EPOCH)
133 .unwrap()
134 .as_secs()
135 );
136
137 write(&tmp, encrypted_data)?;
138 rename(&tmp, &self.path)?;
139 if let DumpPolicy::Periodic(_) = self.policy {
140 self.last_dump = Instant::now();
141 }
142 Ok(())
143 }
144 fn dumpdb(&mut self) -> Result<()> {
145 match self.policy {
146 DumpPolicy::Auto => self.dump(),
147 DumpPolicy::Periodic(dur) => {
148 let now = Instant::now();
149 if now.duration_since(self.last_dump) >= dur {
150 self.dump()
151 } else {
152 Ok(())
153 }
154 }
155 _ => Ok(()),
156 }
157 }
158
159 /// Set a key-value pair.
160 ///
161 /// The key has to be a string but the value can be of any type that is serializable.
162 /// That includes all primitive types, vectors, tuples, enums and every struct that
163 /// has the `#[derive(Serialize, Deserialize)` attribute.
164 ///
165 /// This method returns `Ok(())` if set is successful, Or an `anyhow::Error`
166 /// otherwise. An error is not likely to happen but may occur mostly in cases where this
167 /// action triggers a DB dump (which is decided according to the dump policy).
168
169 pub fn set<K: AsRef<str>, V: Serialize>(&mut self, key: K, value: V) -> Result<()> {
170 let key = key.as_ref();
171 if self.list_map.contains_key(key) {
172 self.list_map.remove(key);
173 }
174 let data = self.ser.serialize_data(&value)?;
175 let orig_val = self.map.insert(key.to_string(), data);
176 match self.dumpdb() {
177 Ok(_) => Ok(()),
178 Err(err) => {
179 match orig_val {
180 Some(val) => self.map.insert(String::from(key), val),
181 None => self.map.remove(key),
182 };
183 Err(err)
184 }
185 }
186 }
187
188 /// Get a value of a key.
189 ///
190 /// The key is always a string but the value can be of any type. It's the developer's
191 /// responsibility to know the value type and give it while calling this method.
192 /// If the key doesn't exist or if the type is wrong, `None` will be returned.
193 /// Otherwise `Some(V)` will be returned.
194 ///
195 /// Since the values are stored in a serialized way the returned object is
196 /// not a reference to the value stored in a DB but actually a new instance
197 /// of it.
198
199 pub fn get<K: AsRef<str>, V: DeserializeOwned>(&self, key: K) -> Option<V> {
200 let key = key.as_ref();
201 let res = self.map.get(key);
202 if let Some(v) = res {
203 self.ser.deserialize_data(v)
204 } else {
205 None
206 }
207 }
208
209 /// Check if a key exists.
210 ///
211 /// This method returns `true` if the key exists and `false` otherwise.
212
213 pub fn exists<K: AsRef<str>>(&self, key: K) -> bool {
214 self.map.contains_key(key.as_ref()) || self.list_map.contains_key(key.as_ref())
215 }
216
217 /// Get a vector of all the keys in the DB.
218 ///
219 /// The keys returned in the vector are not references to the actual key string
220 /// objects but rather a clone of them.
221
222 pub fn get_all(&self) -> Vec<String> {
223 [
224 self.map.keys().cloned().collect::<Vec<String>>(),
225 self.list_map.keys().cloned().collect::<Vec<String>>(),
226 ]
227 .concat()
228 }
229
230 /// Get the total number of keys in the DB.
231
232 pub fn total_keys(&self) -> usize {
233 self.map.iter().len() + self.list_map.iter().len()
234 }
235
236 /// Remove a key-value pair or a list from the DB.
237 ///
238 /// This methods returns `Ok(true)` if the key was found in the DB or `Ok(false)` if it wasn't found.
239 /// It may also return `anyhow::Error` if key was found but removal failed.
240 /// Removal error is not likely to happen but may occur mostly in cases where this action triggers a DB dump
241 /// (which is decided according to the dump policy).
242
243 pub fn rem<K: AsRef<str>>(&mut self, key: K) -> Result<bool> {
244 let key = key.as_ref();
245 let rm_map = match self.map.remove(key) {
246 None => None,
247 Some(val) => match self.dumpdb() {
248 Ok(_) => Some(val),
249 Err(err) => {
250 self.map.insert(String::from(key), val);
251 return Err(err);
252 }
253 },
254 };
255 let rm_list_map = match self.list_map.remove(key) {
256 None => None,
257 Some(val) => match self.dumpdb() {
258 Ok(_) => Some(val),
259 Err(err) => {
260 self.list_map.insert(String::from(key), val);
261 return Err(err);
262 }
263 },
264 };
265 Ok(rm_map.is_some() || rm_list_map.is_some())
266 }
267
268 /// Create a new list.
269 ///
270 /// This method just creates a new list, it doesn't add any elements to it.
271 /// If another list or value is already set under this key, they will be overridden,
272 /// meaning the new list will override the old list or value.
273 ///
274 /// Upon success, the method returns an object of type
275 /// [NoDbExt](struct.NoDbExt.html) that enables to add
276 /// items to the newly created list. Alternatively you can use [ladd()](#method.ladd)
277 /// or [lextend()](#method.lextend) to add items to the list.
278
279 pub fn lcreate<N: AsRef<str>>(&mut self, name: N) -> Result<NoDbExt> {
280 let new_list = Vec::new();
281 let name = name.as_ref();
282 if self.map.contains_key(name) {
283 self.map.remove(name);
284 }
285 self.list_map.insert(String::from(name), new_list);
286 self.dumpdb()?;
287 Ok(NoDbExt {
288 db: self,
289 list_name: name.to_string(),
290 })
291 }
292
293 /// Check if a list exists.
294 ///
295 /// This method returns `true` if the list name exists and `false` otherwise.
296 /// The difference between this method and [exists()](#method.exists) is that this methods checks only
297 /// for lists with that name (key) and [exists()](#method.exists) checks for both values and lists.
298
299 pub fn lexists<N: AsRef<str>>(&self, name: N) -> bool {
300 self.list_map.contains_key(name.as_ref())
301 }
302
303 /// Add a single item to an existing list.
304 ///
305 /// As mentioned before, the lists are heterogeneous, meaning a single list can contain
306 /// items of different types. That means that the item can be of any type that is serializable.
307 /// That includes all primitive types, vectors, tuples and every struct that has the
308 /// `#[derive(Serialize, Deserialize)` attribute.
309 ///
310 /// If the item was added successfully the method returns
311 /// `Some(`[NoDbExt](struct.NoDbExt.html)`)` which enables to add more
312 /// items to the list. Alternatively the method returns `None` if the list isn't found in the DB
313 /// or if a failure happened while extending the list. Failures are not likely to happen but may
314 /// occur mostly in cases where this action triggers a DB dump (which is decided according to the dump policy).
315
316 pub fn ladd<K: AsRef<str>, V: Serialize>(&mut self, name: K, value: &V) -> Option<NoDbExt> {
317 self.lextend(name, &[value])
318 }
319
320 /// Add multiple items to an existing list.
321 ///
322 /// As mentioned before, the lists are heterogeneous, meaning a single list can contain
323 /// items of different types. That means that the item can be of any type that is serializable.
324 /// That includes all primitive types, vectors, tuples and every struct that has the
325 /// `#[derive(Serialize, Deserialize)` attribute.
326 /// This method adds multiple items to the list, but since they're in a vector that means all
327 /// of them are of the same type. Of course it doesn't mean that the list cannot contain items
328 /// of other types as well, as you can see in the example below.
329 ///
330 /// If all items were added successfully the method returns
331 /// `Some(`[NoDbExt](struct.NoDbExt.html)`)` which enables to add more
332 /// items to the list. Alternatively the method returns `None` if the list isn't found in the DB
333 /// or if a failure happened while extending the list. Failures are not likely to happen but may
334 /// occur mostly in cases where this action triggers a DB dump (which is decided according to the dump policy).
335
336 pub fn lextend<'a, N: AsRef<str>, V, I>(&mut self, name: N, seq: I) -> Option<NoDbExt>
337 where
338 V: 'a + Serialize,
339 I: IntoIterator<Item = &'a V>,
340 {
341 let ser = &self.ser;
342 match self.list_map.get_mut(name.as_ref()) {
343 Some(list) => {
344 let orig_len = list.len();
345 let serialized = seq
346 .into_iter()
347 .map(|v| ser.serialize_data(v).unwrap())
348 .collect::<Vec<_>>();
349 list.extend(serialized);
350 if self.dumpdb().is_err() {
351 let same_list = self.list_map.get_mut(name.as_ref()).unwrap();
352 same_list.truncate(orig_len);
353 return None;
354 }
355 Some(NoDbExt {
356 db: self,
357 list_name: name.as_ref().to_string(),
358 })
359 }
360 None => None,
361 }
362 }
363
364 /// Get an item of of a certain list in a certain position.
365 ///
366 /// This method takes a list name and a position inside the list
367 /// and retrieves the item in this position. It's the developer's responsibility
368 /// to know what is the correct type of the item and give it while calling this method.
369 /// Since the item in the lists are stored in a serialized way the returned object
370 /// is not a reference to the item stored in a DB but actually a new instance of it.
371 /// If the list is not found in the DB or the given position is out of bounds
372 /// of the list `None` will be returned. Otherwise `Some(V)` will be returned.
373
374 pub fn lget<V: DeserializeOwned, N: AsRef<str>>(&self, name: N, pos: usize) -> Option<V> {
375 match self.list_map.get(name.as_ref()) {
376 Some(list) => match list.get(pos) {
377 Some(val) => self.ser.deserialize_data::<V>(val),
378 None => None,
379 },
380 None => None,
381 }
382 }
383
384 /// Get the length of a list.
385 ///
386 /// If the list is empty or if it doesn't exist the value of 0 is returned.
387
388 pub fn llen<N: AsRef<str>>(&self, name: N) -> usize {
389 match self.list_map.get(name.as_ref()) {
390 Some(list) => list.len(),
391 None => 0,
392 }
393 }
394
395 /// Remove a list.
396 ///
397 /// This method is somewhat similar to [rem()](#method.rem) but with 2 small differences:
398 /// * This method only removes lists and not key-value pairs
399 /// * The return value of this method is the number of items that were in
400 /// the list that was removed. If the list doesn't exist a value of zero (0) is
401 /// returned. In case of a failure an `anyhow::Error` is returned.
402 /// Failures are not likely to happen but may occur mostly in cases where this action triggers a
403 /// DB dump (which is decided according to the dump policy).
404
405 pub fn lrem_list<N: AsRef<str>>(&mut self, name: N) -> Result<usize> {
406 let res = self.llen(&name);
407 let name = name.as_ref();
408 match self.list_map.remove(name) {
409 Some(list) => match self.dumpdb() {
410 Ok(_) => Ok(res),
411 Err(err) => {
412 self.list_map.insert(String::from(name), list);
413 Err(err)
414 }
415 },
416 None => Ok(res),
417 }
418 }
419
420 /// Pop an item out of a list.
421 ///
422 /// This method takes a list name and a position inside the list, removes the
423 /// item in this position and returns it to the user. It's the user's responsibility
424 /// to know what is the correct type of the item and give it while calling this method.
425 /// Since the item in the lists are stored in a serialized way the returned object
426 /// is not a reference to the item stored in a DB but actually a new instance of it.
427 ///
428 /// If the list is not found in the DB or the given position is out of bounds no item
429 /// will be removed and `None` will be returned. `None` may also be returned
430 /// if removing the item fails, which may happen mostly in cases where this action
431 /// triggers a DB dump (which is decided according to the dump policy).
432 /// Otherwise the item will be removed and `Some(V)` will be returned.
433 ///
434 /// This method is very similar to [lrem_value()](#method.lrem_value), the only difference is that this
435 /// methods returns the value and [lrem_value()](#method.lrem_value) returns only an indication whether
436 /// the item was removed or not.
437
438 pub fn lpop<V: DeserializeOwned, N: AsRef<str>>(&mut self, name: N, pos: usize) -> Option<V> {
439 let name = name.as_ref();
440 match self.list_map.get_mut(name) {
441 Some(list) => {
442 if pos < list.len() {
443 let res = list.remove(pos);
444 match self.dumpdb() {
445 Ok(_) => self.ser.deserialize_data::<V>(&res),
446 Err(_) => {
447 let same_list = self.list_map.get_mut(name).unwrap();
448 same_list.insert(pos, res);
449 None
450 }
451 }
452 } else {
453 None
454 }
455 }
456 None => None,
457 }
458 }
459
460 /// Remove an item out of a list.
461 ///
462 /// This method takes a list name and a reference to a value, removes the first instance of the
463 /// value if it exists in the list, and returns an indication whether the item was removed or not.
464 ///
465 /// If the list is not found in the DB or the given value isn't found in the list, no item will
466 /// be removed and `Ok(false)` will be returned.
467 /// If removing the item fails, which may happen mostly in cases where this action triggers
468 /// a DB dump (which is decided according to the dump policy), an
469 /// `anyhow::Error` is returned. Otherwise the item will be removed and `Ok(true)` will be returned.
470 ///
471 /// This method is very similar to [lpop()](#method.lpop), the only difference is that this
472 /// methods returns an indication and [lpop()](#method.lpop) returns the actual item that was removed.
473
474 pub fn lrem_value<V: Serialize, N: AsRef<str>>(&mut self, name: N, value: &V) -> Result<bool> {
475 let name = name.as_ref();
476 match self.list_map.get_mut(name) {
477 Some(list) => {
478 let serialized_value = match self.ser.serialize_data(&value) {
479 Ok(val) => val,
480 Err(err) => {
481 return Err(anyhow!(
482 "Error serializing value: {}",
483 err.to_string().replace('\n', "")
484 ))
485 }
486 };
487 match list.iter().position(|x| *x == serialized_value) {
488 Some(pos) => {
489 list.remove(pos);
490 match self.dumpdb() {
491 Ok(_) => Ok(true),
492 Err(err) => {
493 let same_list = self.list_map.get_mut(name).unwrap();
494 same_list.insert(pos, serialized_value);
495 Err(err)
496 }
497 }
498 }
499 None => Ok(false),
500 }
501 }
502 None => Ok(false),
503 }
504 }
505
506 /// Return an iterator over the keys and values in the DB.
507
508 pub fn iter(&self) -> NoDbIter {
509 NoDbIter {
510 map_iter: self.map.iter(),
511 ser: &self.ser,
512 }
513 }
514
515 /// Return an iterator over the items in certain list.
516
517 pub fn liter<N: AsRef<str>>(&self, name: N) -> NoDbListIter {
518 let name = name.as_ref();
519 match self.list_map.get(name) {
520 Some(list) => NoDbListIter {
521 list_iter: list.iter(),
522 ser: &self.ser,
523 },
524 None => NoDbListIter {
525 list_iter: [].iter(),
526 ser: &self.ser,
527 },
528 }
529 }
530}
531
532impl Drop for NoDb {
533 fn drop(&mut self) {
534 if !matches!(self.policy, DumpPolicy::Never | DumpPolicy::OnCall) {
535 let _ = self.dump();
536 }
537 }
538}