1use anyhow::Result;
2use rusqlite::{params, Connection};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct UserConfigEntry {
7 pub key: String,
8 pub value: String,
9 pub value_type: String,
10 pub updated_at: String,
11}
12
13pub struct UserConfigDB {
14 conn: Connection,
15}
16
17impl UserConfigDB {
18 pub fn new(path: &str) -> Result<Self> {
19 let conn = Connection::open(path)?;
20 let db = Self { conn };
21 db.init()?;
22 Ok(db)
23 }
24
25 #[allow(clippy::should_implement_trait)]
26 pub fn default() -> Result<Self> {
27 let path = format!(
28 "{}/user_config.db",
29 std::env::var("HOME").unwrap_or_else(|_| ".".to_string())
30 );
31 Self::new(&path)
32 }
33
34 fn init(&self) -> Result<()> {
35 self.conn.execute_batch(
36 "
37 CREATE TABLE IF NOT EXISTS user_config (
38 key TEXT PRIMARY KEY NOT NULL,
39 value TEXT NOT NULL DEFAULT '',
40 value_type TEXT NOT NULL DEFAULT 'string',
41 updated_at TEXT DEFAULT (datetime('now')),
42 created_at TEXT DEFAULT (datetime('now'))
43 );
44
45 CREATE INDEX IF NOT EXISTS idx_user_config_key ON user_config(key);
46 CREATE INDEX IF NOT EXISTS idx_user_config_type ON user_config(value_type);
47
48 CREATE TABLE IF NOT EXISTS config_groups (
49 id INTEGER PRIMARY KEY AUTOINCREMENT,
50 name TEXT NOT NULL UNIQUE,
51 description TEXT,
52 created_at TEXT DEFAULT (datetime('now'))
53 );
54 ",
55 )?;
56 Ok(())
57 }
58
59 pub fn set_string(&self, key: &str, value: &str) -> Result<()> {
60 self.conn.execute(
61 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'string', datetime('now'))",
62 params![key, value],
63 )?;
64 Ok(())
65 }
66
67 pub fn get_string(&self, key: &str) -> Result<Option<String>> {
68 let mut stmt = self
69 .conn
70 .prepare("SELECT value FROM user_config WHERE key = ?1")?;
71 let mut rows = stmt.query_map(params![key], |row| row.get(0))?;
72 match rows.next() {
73 Some(result) => Ok(Some(result?)),
74 None => Ok(None),
75 }
76 }
77
78 pub fn set_bool(&self, key: &str, value: bool) -> Result<()> {
79 self.conn.execute(
80 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'bool', datetime('now'))",
81 params![key, if value { "true" } else { "false" }],
82 )?;
83 Ok(())
84 }
85
86 pub fn get_bool(&self, key: &str) -> Result<Option<bool>> {
87 match self.get_string(key)? {
88 Some(value) => Ok(Some(value == "true")),
89 None => Ok(None),
90 }
91 }
92
93 pub fn set_int(&self, key: &str, value: i64) -> Result<()> {
94 self.conn.execute(
95 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'int', datetime('now'))",
96 params![key, value.to_string()],
97 )?;
98 Ok(())
99 }
100
101 pub fn get_int(&self, key: &str) -> Result<Option<i64>> {
102 match self.get_string(key)? {
103 Some(value) => match value.parse::<i64>() {
104 Ok(v) => Ok(Some(v)),
105 Err(_) => Ok(None),
106 },
107 None => Ok(None),
108 }
109 }
110
111 pub fn set_float(&self, key: &str, value: f64) -> Result<()> {
112 self.conn.execute(
113 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'float', datetime('now'))",
114 params![key, value.to_string()],
115 )?;
116 Ok(())
117 }
118
119 pub fn get_float(&self, key: &str) -> Result<Option<f64>> {
120 match self.get_string(key)? {
121 Some(value) => match value.parse::<f64>() {
122 Ok(v) => Ok(Some(v)),
123 Err(_) => Ok(None),
124 },
125 None => Ok(None),
126 }
127 }
128
129 pub fn set_json<T: Serialize>(&self, key: &str, value: &T) -> Result<()> {
130 let json_str = serde_json::to_string(value)?;
131 self.conn.execute(
132 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'json', datetime('now'))",
133 params![key, json_str],
134 )?;
135 Ok(())
136 }
137
138 pub fn get_json<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<Option<T>> {
139 match self.get_string(key)? {
140 Some(value) => match serde_json::from_str(&value) {
141 Ok(v) => Ok(Some(v)),
142 Err(_) => Ok(None),
143 },
144 None => Ok(None),
145 }
146 }
147
148 pub fn delete(&self, key: &str) -> Result<bool> {
149 let rows_affected = self
150 .conn
151 .execute("DELETE FROM user_config WHERE key = ?1", params![key])?;
152 Ok(rows_affected > 0)
153 }
154
155 pub fn exists(&self, key: &str) -> Result<bool> {
156 let mut stmt = self
157 .conn
158 .prepare("SELECT COUNT(*) FROM user_config WHERE key = ?1")?;
159 let count: i64 = stmt.query_row(params![key], |row| row.get(0))?;
160 Ok(count > 0)
161 }
162
163 pub fn get_all_by_prefix(&self, prefix: &str) -> Result<Vec<UserConfigEntry>> {
164 let mut stmt = self.conn.prepare(
165 "SELECT key, value, value_type, updated_at FROM user_config WHERE key LIKE ?1 || '%' ORDER BY key"
166 )?;
167 let rows = stmt.query_map(params![prefix], |row| {
168 Ok(UserConfigEntry {
169 key: row.get(0)?,
170 value: row.get(1)?,
171 value_type: row.get(2)?,
172 updated_at: row.get(3)?,
173 })
174 })?;
175 rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
176 }
177
178 pub fn get_all_by_type(&self, value_type: &str) -> Result<Vec<UserConfigEntry>> {
179 let mut stmt = self.conn.prepare(
180 "SELECT key, value, value_type, updated_at FROM user_config WHERE value_type = ?1 ORDER BY key"
181 )?;
182 let rows = stmt.query_map(params![value_type], |row| {
183 Ok(UserConfigEntry {
184 key: row.get(0)?,
185 value: row.get(1)?,
186 value_type: row.get(2)?,
187 updated_at: row.get(3)?,
188 })
189 })?;
190 rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
191 }
192
193 pub fn get_all(&self) -> Result<Vec<UserConfigEntry>> {
194 let mut stmt = self
195 .conn
196 .prepare("SELECT key, value, value_type, updated_at FROM user_config ORDER BY key")?;
197 let rows = stmt.query_map([], |row| {
198 Ok(UserConfigEntry {
199 key: row.get(0)?,
200 value: row.get(1)?,
201 value_type: row.get(2)?,
202 updated_at: row.get(3)?,
203 })
204 })?;
205 rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
206 }
207
208 pub fn count(&self) -> Result<i64> {
209 let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM user_config")?;
210 let count: i64 = stmt.query_row([], |row| row.get(0))?;
211 Ok(count)
212 }
213
214 pub fn clear_all(&self) -> Result<u64> {
215 let affected = self.conn.execute("DELETE FROM user_config", [])?;
216 Ok(affected as u64)
217 }
218
219 pub fn export_to_json(&self) -> Result<String> {
220 let entries = self.get_all()?;
221 let json = serde_json::to_string_pretty(&entries)?;
222 Ok(json)
223 }
224
225 pub fn import_from_json(&self, json_str: &str) -> Result<u64> {
226 let entries: Vec<UserConfigEntry> = serde_json::from_str(json_str)?;
227 let mut count = 0u64;
228
229 for entry in entries {
230 self.conn.execute(
231 "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, ?3, ?4)",
232 params![entry.key, entry.value, entry.value_type, entry.updated_at],
233 )?;
234 count += 1;
235 }
236
237 Ok(count)
238 }
239}