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).
This commit is contained in:
SaladDay
2026-06-24 06:28:12 +00:00
parent 100a14d2ed
commit 041a627765
+39 -27
View File
@@ -270,6 +270,16 @@ pub fn run() {
// 拦截窗口关闭:根据设置决定是否最小化到托盘 // 拦截窗口关闭:根据设置决定是否最小化到托盘
.on_window_event(|window, event| { .on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = 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(); let settings = crate::settings::get_settings();
if settings.minimize_to_tray_on_close { if settings.minimize_to_tray_on_close {
@@ -403,39 +413,41 @@ pub fn run() {
// 说明:从 v3.8.* 升级的用户通常会走到这里的 SQLite schema 迁移, // 说明:从 v3.8.* 升级的用户通常会走到这里的 SQLite schema 迁移,
// 若迁移失败(数据库损坏/权限不足/user_version 过新等),需要给用户明确提示, // 若迁移失败(数据库损坏/权限不足/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 { let db = loop {
match crate::database::Database::init() { match crate::database::Database::init() {
Ok(db) => break Arc::new(db), Ok(db) => break Arc::new(db),
Err(e) => { Err(e) => {
log::error!("Failed to init database: {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()) if !show_database_init_error_dialog(app.handle(), &db_path, &e.to_string())
{ {
log::info!("用户选择退出程序"); log::info!("用户选择退出程序");