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}