131 lines
3.9 KiB
Rust
131 lines
3.9 KiB
Rust
//! SQLite 本地键值配置(`app_data_dir/local_config.db`)。
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::Mutex;
|
|
|
|
use rusqlite::{params, Connection};
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum LocalConfigError {
|
|
#[error("{0}")]
|
|
Db(#[from] rusqlite::Error),
|
|
#[error("{0}")]
|
|
Message(String),
|
|
}
|
|
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct LocalConfigItem {
|
|
pub name: String,
|
|
pub value: String,
|
|
pub mark: Option<String>,
|
|
pub updated_at: i64,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LocalConfigDb {
|
|
conn: Mutex<Connection>,
|
|
}
|
|
|
|
impl LocalConfigDb {
|
|
pub fn open(path: PathBuf) -> Result<Self, LocalConfigError> {
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)
|
|
.map_err(|e| LocalConfigError::Message(format!("创建配置目录失败: {e}")))?;
|
|
}
|
|
let conn = Connection::open(path)?;
|
|
conn.execute_batch(
|
|
r#"
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE IF NOT EXISTS local_configs (
|
|
name TEXT PRIMARY KEY NOT NULL,
|
|
value TEXT NOT NULL,
|
|
mark TEXT,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
"#,
|
|
)?;
|
|
Ok(Self {
|
|
conn: Mutex::new(conn),
|
|
})
|
|
}
|
|
|
|
pub fn list_all_map(&self) -> Result<HashMap<String, String>, LocalConfigError> {
|
|
let conn = self.conn.lock().map_err(|_| {
|
|
LocalConfigError::Message("本地数据库锁异常".into())
|
|
})?;
|
|
let mut stmt = conn.prepare(
|
|
"SELECT name, value FROM local_configs ORDER BY name ASC",
|
|
)?;
|
|
let rows = stmt.query_map([], |row| {
|
|
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
})?;
|
|
let mut map = HashMap::new();
|
|
for row in rows {
|
|
let (k, v) = row?;
|
|
map.insert(k, v);
|
|
}
|
|
Ok(map)
|
|
}
|
|
|
|
pub fn list_items(&self) -> Result<Vec<LocalConfigItem>, LocalConfigError> {
|
|
let conn = self.conn.lock().map_err(|_| {
|
|
LocalConfigError::Message("本地数据库锁异常".into())
|
|
})?;
|
|
let mut stmt = conn.prepare(
|
|
"SELECT name, value, mark, updated_at FROM local_configs ORDER BY name ASC",
|
|
)?;
|
|
let rows = stmt.query_map([], |row| {
|
|
Ok(LocalConfigItem {
|
|
name: row.get(0)?,
|
|
value: row.get(1)?,
|
|
mark: row.get(2)?,
|
|
updated_at: row.get(3)?,
|
|
})
|
|
})?;
|
|
let mut items = Vec::new();
|
|
for row in rows {
|
|
items.push(row?);
|
|
}
|
|
Ok(items)
|
|
}
|
|
|
|
pub fn upsert(
|
|
&self,
|
|
name: &str,
|
|
value: &str,
|
|
mark: Option<&str>,
|
|
) -> Result<(), LocalConfigError> {
|
|
let name = name.trim();
|
|
if name.is_empty() {
|
|
return Err(LocalConfigError::Message("配置键名不能为空".into()));
|
|
}
|
|
let now = chrono::Utc::now().timestamp();
|
|
let conn = self.conn.lock().map_err(|_| {
|
|
LocalConfigError::Message("本地数据库锁异常".into())
|
|
})?;
|
|
conn.execute(
|
|
r#"
|
|
INSERT INTO local_configs (name, value, mark, updated_at)
|
|
VALUES (?1, ?2, ?3, ?4)
|
|
ON CONFLICT(name) DO UPDATE SET
|
|
value = excluded.value,
|
|
mark = excluded.mark,
|
|
updated_at = excluded.updated_at
|
|
"#,
|
|
params![name, value, mark, now],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn delete(&self, name: &str) -> Result<(), LocalConfigError> {
|
|
let conn = self.conn.lock().map_err(|_| {
|
|
LocalConfigError::Message("本地数据库锁异常".into())
|
|
})?;
|
|
conn.execute("DELETE FROM local_configs WHERE name = ?1", params![name])?;
|
|
Ok(())
|
|
}
|
|
}
|