From 041a62776524d492adffabec80daaf5c67c1ed85 Mon Sep 17 00:00:00 2001 From: SaladDay Date: Wed, 24 Jun 2026 06:28:12 +0000 Subject: [PATCH] fix(db): pre-check too-new DB before schema writes; exit on close in recovery mode Addresses review feedback on the too-new-DB recovery flow. - P1: stored_user_version_exceeds_supported() is now checked BEFORE Database::init(), so create_tables()'s DDL (incl. the unconditional DROP INDEX/DROP TABLE IF EXISTS failover_queue) never runs against a database whose user_version we cannot understand. The earlier post-init-failure recovery branch is removed; the pre-check is the single authoritative guard, so "don't write to a DB we can't read" now holds from the very first DB access. - P2: the window CloseRequested handler now detects recovery mode (init_error kind = db_version_too_new) and exits the app instead of honoring minimize_to_tray_on_close. Recovery mode returns before the tray is created, so hiding the window would leave the app running with no tray to bring it back; native-close now quits cleanly. Verified: cargo fmt --check clean; cross-model Rust review of both fixes (compile-correctness + no normal-startup/close regression). --- src-tauri/src/lib.rs | 66 ++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2ead0294d..b6e0deee5 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -270,6 +270,16 @@ pub fn run() { // 拦截窗口关闭:根据设置决定是否最小化到托盘 .on_window_event(|window, event| { if let tauri::WindowEvent::CloseRequested { api, .. } = event { + // 数据库版本过新的恢复模式下没有托盘可唤回,关闭即退出,避免应用隐身后台 + let in_db_recovery = crate::init_status::get_init_error() + .map(|p| p.kind.as_deref() == Some("db_version_too_new")) + .unwrap_or(false); + if in_db_recovery { + api.prevent_close(); + window.app_handle().exit(0); + return; + } + let settings = crate::settings::get_settings(); if settings.minimize_to_tray_on_close { @@ -403,39 +413,41 @@ pub fn run() { // 说明:从 v3.8.* 升级的用户通常会走到这里的 SQLite schema 迁移, // 若迁移失败(数据库损坏/权限不足/user_version 过新等),需要给用户明确提示, // 否则表现可能只是“应用打不开/闪退”。 + // + // 预检:数据库版本过新时,必须先于任何 schema 写操作(create_tables 内含 + // DROP/ALTER 等 DDL)进入恢复界面,避免旧应用对读不懂的更新版 DB 落写。 + match crate::database::Database::stored_user_version_exceeds_supported(&db_path) { + Ok(Some(version)) => { + log::warn!("数据库版本过新(v{version}),引导用户在应用内升级应用"); + crate::init_status::set_init_error(crate::init_status::InitErrorPayload { + path: db_path.display().to_string(), + error: format!( + "数据库版本过新({version}),当前应用仅支持 {},请升级应用后再尝试。", + crate::database::SCHEMA_VERSION + ), + kind: Some("db_version_too_new".to_string()), + db_version: Some(version), + supported_version: Some(crate::database::SCHEMA_VERSION), + }); + // 主窗口默认 visible:false,恢复界面必须强制显示 + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + return Ok(()); + } + Ok(None) => {} + Err(e) => { + log::warn!("预检数据库版本失败,继续正常初始化流程: {e}"); + } + } + let db = loop { match crate::database::Database::init() { Ok(db) => break Arc::new(db), Err(e) => { log::error!("Failed to init database: {e}"); - // 数据库版本过新(应用过旧):无法向下迁移,反复重试也无效。 - // 记录可恢复错误并进入应用内「升级应用」恢复界面,而不是死循环弹窗。 - if let Ok(Some(version)) = - crate::database::Database::stored_user_version_exceeds_supported( - &db_path, - ) - { - log::warn!("数据库版本过新(v{version}),引导用户在应用内升级应用"); - crate::init_status::set_init_error( - crate::init_status::InitErrorPayload { - path: db_path.display().to_string(), - error: e.to_string(), - kind: Some("db_version_too_new".to_string()), - db_version: Some(version), - supported_version: Some( - crate::database::SCHEMA_VERSION, - ), - }, - ); - // 主窗口默认 visible:false,恢复界面必须强制显示 - if let Some(window) = app.get_webview_window("main") { - let _ = window.show(); - let _ = window.set_focus(); - } - return Ok(()); - } - if !show_database_init_error_dialog(app.handle(), &db_path, &e.to_string()) { log::info!("用户选择退出程序");