From 996044bdc2bdca6cb3950bf5295402a2b7f62274 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sat, 3 Oct 2026 10:59:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8E=A5=E5=85=A5=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E9=92=A9=E5=AD=90=E5=B9=B6=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=20SQLX=20=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ProcessManager 新增 has_instance_process(备份启动守卫依赖) - 删除/启动/编辑/迁移目录时接入备份维护钩子(lock_instance_maintenance + begin_instance_deletion + delete_instance_backups),remove_instance 拆出 remove_instance_files_and_state - run 启动前检查活跃备份操作并加维护锁 - 目录迁移时搬移默认备份仓库并入队清理 - 更新 settings SELECT 的 SQLX 离线缓存(补 ui_font/backup_repository_path/mono_font) - create_recorded_symlink 非 Windows 未用参数加 cfg_attr 豁免 --- ...bde439c49582e1e81f5acb7b52d769a1bc18.json} | 26 ++++++++++++++--- packages/app-lib/src/api/instance/backup.rs | 1 + .../app-lib/src/api/instance/lifecycle.rs | 1 + packages/app-lib/src/api/instance/run.rs | 9 ++++++ packages/app-lib/src/state/dirs.rs | 26 +++++++++++++++++ .../instances/commands/remove_instance.rs | 29 +++++++++++++++++++ packages/app-lib/src/state/process.rs | 7 +++++ 7 files changed, 95 insertions(+), 4 deletions(-) rename packages/app-lib/.sqlx/{query-a00177609aec1a056ec47d46d4acd6d1a6244434991d3624b70c7734a2ab85c3.json => query-52d2d61ed9239875d6188417f74bbde439c49582e1e81f5acb7b52d769a1bc18.json} (93%) diff --git a/packages/app-lib/.sqlx/query-a00177609aec1a056ec47d46d4acd6d1a6244434991d3624b70c7734a2ab85c3.json b/packages/app-lib/.sqlx/query-52d2d61ed9239875d6188417f74bbde439c49582e1e81f5acb7b52d769a1bc18.json similarity index 93% rename from packages/app-lib/.sqlx/query-a00177609aec1a056ec47d46d4acd6d1a6244434991d3624b70c7734a2ab85c3.json rename to packages/app-lib/.sqlx/query-52d2d61ed9239875d6188417f74bbde439c49582e1e81f5acb7b52d769a1bc18.json index 683062a..437365d 100644 --- a/packages/app-lib/.sqlx/query-a00177609aec1a056ec47d46d4acd6d1a6244434991d3624b70c7734a2ab85c3.json +++ b/packages/app-lib/.sqlx/query-52d2d61ed9239875d6188417f74bbde439c49582e1e81f5acb7b52d769a1bc18.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n auto_concurrent_downloads, minecraft_metadata_source,\n minecraft_file_source, modrinth_source, curseforge_source, mojang_auth_source,\n theme, locale, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n\t\t\t\tdiscord_rpc, developer_mode, telemetry, telemetry_consent_version, personalized_ads,\n onboarded, onboarding_version, onboarding_instance_tour_completed,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_memory_auto, mc_force_fullscreen, mc_maximize_window, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start, enter_lightweight_mode_on_game_launch,\n auto_set_java_high_performance_mode,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates, accent_color,\n custom_background_path, custom_background_blur, custom_background_opacity,\n transparent_background, transparent_background_opacity, transparent_background_blur,\n sidebar_instance_count, home_layout, minimal_home_instance_id,\n json(home_widgets) as \"home_widgets?: String\", auto_hide_downloads_button,\n json(terracotta_public_nodes) terracotta_public_nodes,\n ui_scale,\n version\n FROM settings\n ", + "query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n auto_concurrent_downloads, minecraft_metadata_source,\n minecraft_file_source, modrinth_source, curseforge_source, mojang_auth_source,\n theme, locale, default_page, collapsed_navigation, hide_nametag_skins_page, advanced_rendering, native_decorations,\n\t\t\t\tdiscord_rpc, developer_mode, telemetry, telemetry_consent_version, personalized_ads,\n onboarded, onboarding_version, onboarding_instance_tour_completed,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_memory_auto, mc_force_fullscreen, mc_maximize_window, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start, enter_lightweight_mode_on_game_launch,\n auto_set_java_high_performance_mode,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated, json(feature_flags) feature_flags, toggle_sidebar,\n skipped_update, pending_update_toast_for_version, auto_download_updates, accent_color,\n custom_background_path, custom_background_blur, custom_background_opacity,\n transparent_background, transparent_background_opacity, transparent_background_blur,\n sidebar_instance_count, home_layout, minimal_home_instance_id,\n json(home_widgets) as \"home_widgets?: String\", auto_hide_downloads_button,\n json(terracotta_public_nodes) terracotta_public_nodes,\n ui_scale,\n ui_font,\n backup_repository_path,\n mono_font,\n version\n FROM settings\n ", "describe": { "columns": [ { @@ -299,8 +299,23 @@ "type_info": "Float" }, { - "name": "version", + "name": "ui_font", "ordinal": 59, + "type_info": "Text" + }, + { + "name": "backup_repository_path", + "ordinal": 60, + "type_info": "Text" + }, + { + "name": "mono_font", + "ordinal": 61, + "type_info": "Text" + }, + { + "name": "version", + "ordinal": 62, "type_info": "Integer" } ], @@ -367,8 +382,11 @@ false, null, false, + null, + null, + null, false ] }, - "hash": "a00177609aec1a056ec47d46d4acd6d1a6244434991d3624b70c7734a2ab85c3" -} + "hash": "52d2d61ed9239875d6188417f74bbde439c49582e1e81f5acb7b52d769a1bc18" +} \ No newline at end of file diff --git a/packages/app-lib/src/api/instance/backup.rs b/packages/app-lib/src/api/instance/backup.rs index d2b53a5..e1bbb12 100644 --- a/packages/app-lib/src/api/instance/backup.rs +++ b/packages/app-lib/src/api/instance/backup.rs @@ -3632,6 +3632,7 @@ async fn verify_object( Ok(()) } +#[cfg_attr(not(windows), allow(unused_variables))] async fn create_recorded_symlink( target: PathBuf, link: PathBuf, diff --git a/packages/app-lib/src/api/instance/lifecycle.rs b/packages/app-lib/src/api/instance/lifecycle.rs index a8b14d6..097fbc3 100644 --- a/packages/app-lib/src/api/instance/lifecycle.rs +++ b/packages/app-lib/src/api/instance/lifecycle.rs @@ -103,6 +103,7 @@ pub async fn edit( patch: EditInstance, ) -> crate::Result { let state = State::get().await?; + super::ensure_backup_eligible_edit(instance_id, &patch).await?; crate::state::edit_instance(instance_id, patch, &state.pool).await?; let instance = crate::state::get_instance(instance_id, &state.pool) diff --git a/packages/app-lib/src/api/instance/run.rs b/packages/app-lib/src/api/instance/run.rs index 7d51a16..b32fcb8 100644 --- a/packages/app-lib/src/api/instance/run.rs +++ b/packages/app-lib/src/api/instance/run.rs @@ -75,6 +75,15 @@ async fn run_with_extra_launch_args_inner( extra_launch_args: Option>, gc_intent: Option, ) -> crate::Result<(ProcessMetadata, Option)> { + if crate::api::instance::has_active_instance_operation(instance_id) { + return Err(crate::ErrorKind::InputError( + "Wait for the active backup operation to finish before launching this instance" + .to_string(), + ) + .into()); + } + let _maintenance_guard = + crate::api::instance::lock_instance_maintenance(instance_id).await; let _hosted_guard = crate::pack::hosted::prepare_launch(instance_id, offline_mode).await?; let state = State::get().await?; diff --git a/packages/app-lib/src/state/dirs.rs b/packages/app-lib/src/state/dirs.rs index 5e173bb..eae7211 100644 --- a/packages/app-lib/src/state/dirs.rs +++ b/packages/app-lib/src/state/dirs.rs @@ -363,6 +363,32 @@ impl DirectoryInfo { return Err(crate::ErrorKind::DirectoryMoveError(format!("Cannot move directory to {}: directory is not writable", move_dir.display())).into()); } + // If the user hasn't chosen a custom backup repository, the + // default one lives next to the launcher data. Move it along + // with the rest of the directory and queue a cleanup of the + // old copy on next startup. + if settings.backup_repository_path.is_none() { + let old_repository = + prev_dir.join("Backups").join("instances"); + let new_repository = + move_dir.join("Backups").join("instances"); + if let Some(path) = crate::api::instance::move_default_repository_for_launcher_directory( + &old_repository, + &new_repository, + ) + .await? + { + sqlx::query( + "INSERT INTO pending_backup_repository_cleanups (path, created_at) + VALUES (?, unixepoch('subsec') * 1000) + ON CONFLICT(path) DO UPDATE SET created_at = excluded.created_at", + ) + .bind(path.to_string_lossy().to_string()) + .execute(exec) + .await?; + } + } + const MOVE_DIRS: &[&str] = &[ CACHES_FOLDER_NAME, INSTANCES_FOLDER_NAME, diff --git a/packages/app-lib/src/state/instances/commands/remove_instance.rs b/packages/app-lib/src/state/instances/commands/remove_instance.rs index 4b18b0b..8103ba1 100644 --- a/packages/app-lib/src/state/instances/commands/remove_instance.rs +++ b/packages/app-lib/src/state/instances/commands/remove_instance.rs @@ -9,7 +9,36 @@ pub(crate) async fn remove_instance( state: &State, ) -> crate::Result<()> { let _instance_lock = state.lock_instance_content(instance_id).await; + let _maintenance_guard = + crate::api::instance::lock_instance_maintenance(instance_id).await; + crate::api::instance::begin_instance_deletion(instance_id).await?; + let result = remove_instance_files_and_state(instance_id, state).await; + + match result { + Ok(()) => { + crate::api::instance::delete_instance_backups(instance_id).await + } + Err(error) => { + if let Err(cancel_error) = + crate::api::instance::cancel_instance_deletion(instance_id) + .await + { + tracing::warn!( + instance_id, + %cancel_error, + "Failed to clear pending backup deletion marker" + ); + } + Err(error) + } + } +} + +async fn remove_instance_files_and_state( + instance_id: &str, + state: &State, +) -> crate::Result<()> { let instance = instance_rows::get_instance_by_id(instance_id, &state.pool) .await? .ok_or_else(|| { diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index 7d9a18e..adf5a7d 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -533,6 +533,13 @@ impl ProcessManager { .collect() } + /// Whether any tracked process belongs to the given instance. + pub fn has_instance_process(&self, instance_id: &str) -> bool { + self.processes + .iter() + .any(|entry| entry.value().metadata.instance_id == instance_id) + } + pub fn try_wait( &self, id: Uuid,