1use std::{
2 fmt::Display,
3 path::Path,
4 sync::{Arc, Mutex},
5};
6
7use crate::FileDbConfig;
8use anyhow::Context;
9use log::debug;
10use serde::de::DeserializeOwned;
11
12const ADANA_DB_DIR: &str = "adana/db";
13
14use super::{
15 FileDb, FileLock, FileLockError, InMemoryDb, Key, Value, file_lock,
16};
17
18fn get_default_db_path() -> Option<Box<Path>> {
19 let mut db_dir = dirs::data_dir().or_else(dirs::home_dir)?;
20 db_dir.push(ADANA_DB_DIR);
21 debug!("db dir: {}", db_dir.as_path().to_string_lossy());
22 if !db_dir.exists() {
23 std::fs::create_dir_all(&db_dir).ok()?;
24 }
25 db_dir.push("adana.db");
26
27 Some(db_dir.into_boxed_path())
28}
29
30#[derive(Debug)]
31pub struct Config {
32 path: Option<Box<Path>>,
33 in_memory: bool,
34 fall_back_in_memory: bool,
35}
36impl Config {
37 pub fn new<P: AsRef<Path>>(
38 path: Option<P>,
39 in_memory: bool,
40 fall_back_in_memory: bool,
41 ) -> Config {
42 if in_memory {
43 Config { in_memory, path: None, fall_back_in_memory: false }
44 } else {
45 Config {
46 in_memory,
47 path: path
48 .map(|p| {
49 let path: Box<Path> = p.as_ref().into();
50 path
51 })
52 .or_else(get_default_db_path),
53 fall_back_in_memory,
54 }
55 }
56 }
57}
58
59impl Default for Config {
60 fn default() -> Self {
61 Self {
62 path: get_default_db_path(),
63 in_memory: false,
64 fall_back_in_memory: true,
65 }
66 }
67}
68
69pub enum Db<K: Key, V: Value> {
70 FileBased(FileDb<K, V>),
71 InMemory(InMemoryDb<K, V>),
72}
73
74impl<K, V> Db<K, V>
75where
76 K: 'static + Key + DeserializeOwned + std::fmt::Debug,
77 V: 'static + Value + DeserializeOwned + std::fmt::Debug,
78{
79 fn in_memory_fallback(e: impl Display) -> anyhow::Result<Db<K, V>> {
80 eprintln!("Warning! {e} \nAttempt to open a temporary db...\n",);
81 Ok(Db::InMemory(Default::default()))
82 }
83 pub fn open(config: Config) -> anyhow::Result<Db<K, V>> {
84 if config.in_memory {
85 return Ok(Db::InMemory(Default::default()));
86 }
87 let path = config.path.context("not in memory but path empty")?;
88
89 let file_lock = FileLock::open(path.as_ref());
90 match file_lock {
91 Err(e) if !config.fall_back_in_memory => {
92 Err(anyhow::Error::msg(e.to_string()))
93 }
94 Err(pid_exist @ FileLockError::PidExist(_)) => {
95 eprintln!(
96 "Warning! {pid_exist} \nAttempt to open a temporary db...\n",
97 );
98 let p = path.as_ref();
99 let pb = p.to_path_buf();
100 match file_lock::read_file(&pb) {
101 Ok(reader) => {
102 match bincode::deserialize_from::<_, InMemoryDb<K, V>>(
103 reader,
104 ) {
105 Ok(inner_db) => Ok(Db::InMemory(inner_db)),
106 Err(e) => {
107 eprintln!(
108 "Warning! {e:?} \nAttempt to deserialize db, could be because it is the first time you use it\n",
109 );
110 Self::in_memory_fallback(e)
111 }
112 }
113 }
114 Err(e) if config.fall_back_in_memory => {
115 Self::in_memory_fallback(e)
116 }
117 Err(e) => Err(e),
118 }
119 }
120 Err(e) => Self::in_memory_fallback(e),
121 Ok(file_lock) => {
122 let inner = match file_lock.read() {
123 Ok(reader) => {
124 match bincode::deserialize_from::<_, InMemoryDb<K, V>>(
125 reader,
126 ) {
127 Ok(inner_db) => Arc::new(Mutex::new(inner_db)),
128 Err(e) => {
129 eprintln!(
130 "Warning! {e:?} \nAttempt to deserialize db, could be because it is the first time you use it\n",
131 );
132 Arc::new(Mutex::new(Default::default()))
133 }
134 }
135 }
136 Err(e) if config.fall_back_in_memory => {
137 return Self::in_memory_fallback(e);
138 }
139 Err(e) => return Err(e),
140 };
141
142 let db_config =
143 FileDbConfig { file_lock: Arc::new(file_lock), inner };
144 match FileDb::try_from(db_config) {
145 Ok(file_db) => Ok(Db::FileBased(file_db)),
146 Err(e) if config.fall_back_in_memory => {
147 Self::in_memory_fallback(e)
148 }
149 Err(e) => Err(e),
150 }
151 }
152 }
153 }
154}
155
156#[cfg(test)]
157mod test {
158
159 use std::fs::File;
160
161 use crate::{Config, Db, DbOp, Op};
162
163 #[test]
164 fn test_file_db_lock() {
165 let _ = File::create("/tmp/adana.db"); let file_db: Db<u64, String> =
168 Db::open(Config::new(Some("/tmp/adana.db"), false, false)).unwrap();
169
170 let mut file_db = match file_db {
171 Db::FileBased(file_db) => file_db,
172 _ => {
173 panic!("error, should be file db")
174 }
175 };
176 file_db.open_tree("rust");
177
178 for i in 1..100u64 {
179 file_db.insert(i, format!("ok mani{i}"));
180 file_db.insert(i * 100, format!("ok rebenga{i}"));
181 }
182 assert_eq!(Some(198), file_db.len());
183
184 drop(file_db); let file_db: Db<u64, String> =
187 Db::open(Config::new(Some("/tmp/adana.db"), false, false)).unwrap();
188
189 let mut file_db = match file_db {
190 Db::FileBased(file_db) => file_db,
191 _ => {
192 panic!("error, should be file db")
193 }
194 };
195
196 file_db.open_tree("rust");
197
198 file_db.insert(39912u64, "new!".to_string());
199
200 assert_eq!(Some(199), file_db.len());
201 }
202}