fix: 接入备份维护钩子并补齐 SQLX 缓存
Sync source to CNB / Sync Git ref (push) Has been cancelled

- 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 豁免
This commit is contained in:
AxTps committed 2026-10-03 10:59:16 +08:00
1 parent 4674f31dd3
commit 996044bdc2
7 files changed
+95 -4

No files matched your search

@@ -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,
@@ -103,6 +103,7 @@ pub async fn edit(
patch: EditInstance,
) -> crate::Result<InstanceMetadata> {
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)
+9
View File
@@ -75,6 +75,15 @@ async fn run_with_extra_launch_args_inner(
extra_launch_args: Option<Vec<String>>,
gc_intent: Option<GcLaunchIntent>,
) -> crate::Result<(ProcessMetadata, Option<GcLaunchReport>)> {
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?;
+26
View File
@@ -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,
@@ -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(|| {
+7
View File
@@ -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,