gadget_sdk/store/local_database.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
use serde::{de::DeserializeOwned, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
/// A local database for storing key-value pairs.
///
/// The database is stored in a JSON file, which is updated every time a key-value pair is added or updated.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
///
/// db.set("key", 42);
/// assert_eq!(db.get("key"), Some(42));
/// ```
#[derive(Debug)]
pub struct LocalDatabase<T> {
path: PathBuf,
data: Mutex<HashMap<String, T>>,
}
impl<T> LocalDatabase<T>
where
T: Serialize + DeserializeOwned + Clone + Default,
{
/// Reads a `LocalDatabase` from the given path.
///
/// If the file does not exist, an empty database is created.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
/// assert!(db.is_empty());
/// ```
#[must_use]
pub fn open<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref();
let parent_dir = path.parent().expect("Failed to get parent directory");
// Create the parent directory if it doesn't exist
fs::create_dir_all(parent_dir).expect("Failed to create parent directory");
let data = if path.exists() {
let content = fs::read_to_string(path).expect("Failed to read the file");
serde_json::from_str(&content).unwrap_or_default()
} else {
HashMap::new()
};
Self {
path: path.to_owned(),
data: Mutex::new(data),
}
}
/// Returns the number of key-value pairs in the database.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
/// assert_eq!(db.len(), 0);
///
/// db.set("key", 42);
/// assert_eq!(db.len(), 1);
/// ```
pub fn len(&self) -> usize {
let data = self.data.lock().unwrap();
data.len()
}
/// Checks if the database is empty.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
/// assert!(db.is_empty());
///
/// db.set("key", 42);
/// assert!(!db.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
let data = self.data.lock().unwrap();
data.is_empty()
}
/// Adds or updates a key-value pair in the database.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
///
/// db.set("key", 42);
/// assert_eq!(db.get("key"), Some(42));
/// ```
pub fn set(&self, key: &str, value: T) {
let mut data = self.data.lock().unwrap();
let _old = data.insert(key.to_string(), value);
// Save the data while the lock is held
let json_string = serde_json::to_string(&*data).expect("Failed to serialize data to JSON");
fs::write(&self.path, json_string).expect("Failed to write to the file");
}
/// Retrieves a value associated with the given key.
///
/// # Example
///
/// ```no_run
/// use gadget_sdk::store::LocalDatabase;
///
/// let db = LocalDatabase::<u64>::open("data.json");
///
/// db.set("key", 42);
/// assert_eq!(db.get("key"), Some(42));
/// ```
pub fn get(&self, key: &str) -> Option<T> {
let data = self.data.lock().unwrap();
data.get(key).cloned()
}
}