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:
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(§ion.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)
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user