//! 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, pub updated_at: i64, } #[derive(Debug)] pub struct LocalConfigDb { conn: Mutex, } impl LocalConfigDb { pub fn open(path: PathBuf) -> Result { 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, 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, 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(()) } }