refactor(core): integrate SQLite database into application core

- Initialize database on app startup with migration from JSON config
- Update AppState to include Database instance alongside MultiAppConfig
- Simplify store module by removing unused session management code
- Add database initialization to app setup flow
- Support both database and legacy config during transition
This commit is contained in:
YoVinchen
2025-11-22 23:26:41 +08:00
parent 5d1eed563d
commit 529051f0e8
4 changed files with 57 additions and 54 deletions

View File

@@ -79,6 +79,7 @@ pub fn get_app_config_path() -> PathBuf {
} }
/// 清理供应商名称,确保文件名安全 /// 清理供应商名称,确保文件名安全
#[allow(dead_code)]
pub fn sanitize_provider_name(name: &str) -> String { pub fn sanitize_provider_name(name: &str) -> String {
name.chars() name.chars()
.map(|c| match c { .map(|c| match c {
@@ -90,6 +91,7 @@ pub fn sanitize_provider_name(name: &str) -> String {
} }
/// 获取供应商配置文件路径 /// 获取供应商配置文件路径
#[allow(dead_code)]
pub fn get_provider_config_path(provider_id: &str, provider_name: Option<&str>) -> PathBuf { pub fn get_provider_config_path(provider_id: &str, provider_name: Option<&str>) -> PathBuf {
let base_name = provider_name let base_name = provider_name
.map(sanitize_provider_name) .map(sanitize_provider_name)

View File

@@ -13,7 +13,9 @@ fn cell() -> &'static RwLock<Option<InitErrorPayload>> {
INIT_ERROR.get_or_init(|| RwLock::new(None)) INIT_ERROR.get_or_init(|| RwLock::new(None))
} }
#[allow(dead_code)]
pub fn set_init_error(payload: InitErrorPayload) { pub fn set_init_error(payload: InitErrorPayload) {
#[allow(clippy::unwrap_used)]
if let Ok(mut guard) = cell().write() { if let Ok(mut guard) = cell().write() {
*guard = Some(payload); *guard = Some(payload);
} }

View File

@@ -6,6 +6,7 @@ mod claude_plugin;
mod codex_config; mod codex_config;
mod commands; mod commands;
mod config; mod config;
mod database;
mod deeplink; mod deeplink;
mod error; mod error;
mod gemini_config; // 新增 mod gemini_config; // 新增
@@ -206,8 +207,6 @@ fn create_tray_menu(
let app_settings = crate::settings::get_settings(); let app_settings = crate::settings::get_settings();
let tray_texts = TrayTexts::from_language(app_settings.language.as_deref().unwrap_or("zh")); let tray_texts = TrayTexts::from_language(app_settings.language.as_deref().unwrap_or("zh"));
let config = app_state.config.read().map_err(AppError::from)?;
let mut menu_builder = MenuBuilder::new(app); let mut menu_builder = MenuBuilder::new(app);
// 顶部:打开主界面 // 顶部:打开主界面
@@ -218,13 +217,20 @@ fn create_tray_menu(
// 直接添加所有供应商到主菜单(扁平化结构,更简单可靠) // 直接添加所有供应商到主菜单(扁平化结构,更简单可靠)
for section in TRAY_SECTIONS.iter() { for section in TRAY_SECTIONS.iter() {
menu_builder = append_provider_section( let app_type_str = section.app_type.as_str();
app, let providers = app_state.db.get_all_providers(app_type_str)?;
menu_builder, let current_id = app_state
config.get_manager(&section.app_type), .db
section, .get_current_provider(app_type_str)?
&tray_texts, .unwrap_or_default();
)?;
let manager = crate::provider::ProviderManager {
providers,
current: current_id,
};
menu_builder =
append_provider_section(app, menu_builder, Some(&manager), section, &tray_texts)?;
} }
// 分隔符和退出菜单 // 分隔符和退出菜单
@@ -523,42 +529,47 @@ pub fn run() {
// 预先刷新 Store 覆盖配置,确保 AppState 初始化时可读取到最新路径 // 预先刷新 Store 覆盖配置,确保 AppState 初始化时可读取到最新路径
app_store::refresh_app_config_dir_override(app.handle()); app_store::refresh_app_config_dir_override(app.handle());
// 初始化应用状态(仅创建一次,并在本函数末尾注入 manage // 初始化数据库
// 如果配置解析失败,则向前端发送错误事件并提前结束 setup不落盘、不覆盖配置 let app_config_dir = crate::config::get_app_config_dir();
let app_state = match AppState::try_new() { let db_path = app_config_dir.join("cc-switch.db");
Ok(state) => state, let json_path = app_config_dir.join("config.json");
Err(err) => {
let path = crate::config::get_app_config_path(); // Check if migration is needed (DB doesn't exist but JSON does)
let payload_json = serde_json::json!({ let migration_needed = !db_path.exists() && json_path.exists();
"path": path.display().to_string(),
"error": err.to_string(), let db = match crate::database::Database::init() {
}); Ok(db) => Arc::new(db),
// 事件通知(可能早于前端订阅,不保证送达) Err(e) => {
if let Err(e) = app.emit("configLoadError", payload_json) { log::error!("Failed to init database: {e}");
log::error!("发射配置加载错误事件失败: {e}"); // 这里的错误处理比较棘手,因为 setup 返回 Result<Box<dyn Error>>
} // 我们暂时记录日志并让应用继续运行(可能会崩溃)或者返回错误
// 同时缓存错误,供前端启动阶段主动拉取 return Err(Box::new(e));
crate::init_status::set_init_error(crate::init_status::InitErrorPayload {
path: path.display().to_string(),
error: err.to_string(),
});
// 不再继续构建托盘/命令依赖的状态,交由前端提示后退出。
return Ok(());
} }
}; };
if migration_needed {
log::info!("Starting migration from config.json to SQLite...");
match crate::app_config::MultiAppConfig::load() {
Ok(config) => {
if let Err(e) = db.migrate_from_json(&config) {
log::error!("Migration failed: {e}");
} else {
log::info!("Migration successful");
// Optional: Rename config.json
// let _ = std::fs::rename(&json_path, json_path.with_extension("json.bak"));
}
}
Err(e) => log::error!("Failed to load config.json for migration: {e}"),
}
}
let app_state = AppState::new(db);
// 迁移旧的 app_config_dir 配置到 Store // 迁移旧的 app_config_dir 配置到 Store
if let Err(e) = app_store::migrate_app_config_dir_from_settings(app.handle()) { if let Err(e) = app_store::migrate_app_config_dir_from_settings(app.handle()) {
log::warn!("迁移 app_config_dir 失败: {e}"); log::warn!("迁移 app_config_dir 失败: {e}");
} }
// 确保配置结构就绪(已移除旧版本的副本迁移逻辑)
{
let mut config_guard = app_state.config.write().unwrap();
config_guard.ensure_app(&app_config::AppType::Claude);
config_guard.ensure_app(&app_config::AppType::Codex);
}
// 启动阶段不再无条件保存,避免意外覆盖用户配置。 // 启动阶段不再无条件保存,避免意外覆盖用户配置。
// 注册 deep-link URL 处理器(使用正确的 DeepLinkExt API // 注册 deep-link URL 处理器(使用正确的 DeepLinkExt API

View File

@@ -1,26 +1,14 @@
use crate::app_config::MultiAppConfig; use crate::database::Database;
use crate::error::AppError; use std::sync::Arc;
use std::sync::RwLock;
/// 全局应用状态 /// 全局应用状态
pub struct AppState { pub struct AppState {
pub config: RwLock<MultiAppConfig>, pub db: Arc<Database>,
} }
impl AppState { impl AppState {
/// 创建新的应用状态 /// 创建新的应用状态
/// 注意:仅在配置成功加载时返回;不会在失败时回退默认值。 pub fn new(db: Arc<Database>) -> Self {
pub fn try_new() -> Result<Self, AppError> { Self { db }
let config = MultiAppConfig::load()?;
Ok(Self {
config: RwLock::new(config),
})
}
/// 保存配置到文件
pub fn save(&self) -> Result<(), AppError> {
let config = self.config.read().map_err(AppError::from)?;
config.save()
} }
} }