From dc5adddd06ad9c6e63e4594bc2e7c2e5d487e032 Mon Sep 17 00:00:00 2001 From: Xuz Date: Sat, 10 Oct 2026 23:21:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=90=8E=E4=B8=8D=E4=B9=85?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E6=9B=B4=E6=96=B0=E6=97=B6=EF=BC=8C=E4=B8=8B?= =?UTF-8?q?=E5=A5=BD=E7=9A=84=E6=96=B0=E7=89=88=E6=9C=AC=E5=9C=A8=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E6=97=B6=E6=89=BE=E4=B8=8D=E5=88=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新程序启动 30 秒后清理旧的暂存,原来会删掉所有 .ShellRS.app.update-*(Linux 是 ..update-*),包括启动后手动检查、切换渠道刚下载暂存好的新版本,于是「重启并 安装」报找不到文件,重试才从下载目录重新暂存。现在清理只删不比当前版本新的暂存,和下载 目录的规则一致。 Co-Authored-By: Claude Opus 5.5 --- src/update/install.rs | 15 ++++++++--- src/update/install_linux.rs | 29 +++++++++++++++----- src/update/install_macos.rs | 50 ++++++++++++++++++++++++++++++----- src/update/install_windows.rs | 5 +++- src/update/updater.rs | 2 +- tests/ui/update.rs | 2 +- 6 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/update/install.rs b/src/update/install.rs index 7792fd6..c894818 100644 --- a/src/update/install.rs +++ b/src/update/install.rs @@ -207,8 +207,9 @@ pub trait Installer: Send + Sync { /// Remove what earlier updates left next to the installed copy. Called a /// while after start, so a version that crashes right away leaves the - /// previous one's backup behind. - fn clean_up(&self); + /// previous one's backup behind. An update newer than `current` stays: + /// it may have been staged since start and be waiting for the restart. + fn clean_up(&self, current: &Version); } /// For copies that cannot replace themselves. @@ -237,7 +238,7 @@ impl Installer for NoInstaller { )) } - fn clean_up(&self) {} + fn clean_up(&self, _: &Version) {} } /// The installer for this copy of ShellRS. `bundle` is `cx.app_path()`. @@ -296,6 +297,14 @@ pub(crate) fn sibling(path: &Path, name: &str) -> PathBuf { path.with_file_name(name) } +/// Whether `name` is an update staged as `` that this copy +/// no longer needs: one not newer than `current`, or not named by a version. +#[cfg(any(target_os = "macos", target_os = "linux", test))] +pub(crate) fn is_stale_update(name: &str, prefix: &str, current: &Version) -> bool { + name.strip_prefix(prefix) + .is_some_and(|version| Version::parse(version).map_or(true, |version| version <= *current)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/update/install_linux.rs b/src/update/install_linux.rs index 32a7dae..a3be4d3 100644 --- a/src/update/install_linux.rs +++ b/src/update/install_linux.rs @@ -10,7 +10,9 @@ use std::os::unix::fs::PermissionsExt as _; use std::path::{Path, PathBuf}; use super::error::UpdateError; -use super::install::{InstallKind, Installer, Relaunch, Staged, sibling}; +use semver::Version; + +use super::install::{InstallKind, Installer, Relaunch, Staged, is_stale_update, sibling}; use super::manifest::Release; pub struct AppImageInstaller { @@ -59,13 +61,13 @@ impl Installer for AppImageInstaller { }) } - fn clean_up(&self) { + fn clean_up(&self, current: &Version) { let Some(folder) = self.file.parent() else { return; }; let prefix = self.prefix(); for entry in fs::read_dir(folder).into_iter().flatten().flatten() { - if entry.file_name().to_string_lossy().starts_with(&prefix) { + if is_stale_update(&entry.file_name().to_string_lossy(), &prefix, current) { let _ = fs::remove_file(entry.path()); } } @@ -115,13 +117,26 @@ mod tests { "stale", ) .unwrap(); - installer.clean_up(); - let left: Vec<_> = fs::read_dir(folder.path()) + // Staged since start and waiting for the restart: kept. + fs::write( + sibling(&file, ".ShellRS-x86_64.AppImage.update-0.3.0"), + "next", + ) + .unwrap(); + installer.clean_up(&Version::new(0, 2, 0)); + let mut left: Vec<_> = fs::read_dir(folder.path()) .unwrap() .flatten() .map(|entry| entry.file_name().to_string_lossy().into_owned()) .collect(); - assert_eq!(left.len(), 2, "{left:?}"); - assert!(left.contains(&"ShellRS-x86_64.AppImage".to_string())); + left.sort(); + assert_eq!( + left, + [ + ".ShellRS-x86_64.AppImage.update-0.3.0", + "ShellRS-x86_64.AppImage", + "downloaded" + ] + ); } } diff --git a/src/update/install_macos.rs b/src/update/install_macos.rs index 097e6ca..94fc1c9 100644 --- a/src/update/install_macos.rs +++ b/src/update/install_macos.rs @@ -14,9 +14,11 @@ use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Arc; +use semver::Version; + use super::build_info::APPLE_TEAM_ID; use super::error::UpdateError; -use super::install::{InstallKind, Installer, Relaunch, Staged, sibling}; +use super::install::{InstallKind, Installer, Relaunch, Staged, is_stale_update, sibling}; use super::manifest::Release; use crate::i18n::t; @@ -217,7 +219,7 @@ impl Installer for MacInstaller { }) } - fn clean_up(&self) { + fn clean_up(&self, current: &Version) { let Some(folder) = self.bundle.parent() else { return; }; @@ -227,7 +229,7 @@ impl Installer for MacInstaller { ); for entry in fs::read_dir(folder).into_iter().flatten().flatten() { let name = entry.file_name().to_string_lossy().into_owned(); - if name.starts_with(&old) || name.starts_with(&update) { + if name.starts_with(&old) || is_stale_update(&name, &update, current) { let _ = fs::remove_dir_all(entry.path()); } } @@ -349,10 +351,40 @@ mod tests { assert_eq!(names.len(), 2, "{names:?}"); assert!(names[0].starts_with(".ShellRS.app.old-"), "{names:?}"); - installer.clean_up(); + // Run by 0.2.0, once it has started. + installer.clean_up(&Version::new(0, 2, 0)); assert_eq!(names_in(folder.path()), ["ShellRS.app"]); } + /// The clean-up runs a while after start; an update downloaded and + /// staged before then is still there to install on restart. + #[test] + fn the_clean_up_keeps_an_update_staged_for_the_restart() { + let (folder, bundle) = installed(); + let installer = MacInstaller::new(bundle.clone(), FakeTools::new("0.2.0", None)); + for stale in [ + ".ShellRS.app.update-0.1.0", + ".ShellRS.app.update-later", + ".ShellRS.app.old-1", + ] { + fs::create_dir(folder.path().join(stale)).unwrap(); + } + let staged = installer + .stage(Path::new("package.zip"), &release("0.2.0")) + .unwrap(); + + installer.clean_up(&Version::new(0, 1, 0)); + assert_eq!( + names_in(folder.path()), + [".ShellRS.app.update-0.2.0", "ShellRS.app"] + ); + assert_eq!( + installer.apply(&staged, true), + Ok(Relaunch::Restart(bundle.clone())) + ); + assert_eq!(version_at(&bundle), "0.2.0"); + } + #[test] fn a_failed_swap_puts_the_old_bundle_back() { let (folder, bundle) = installed(); @@ -367,8 +399,12 @@ mod tests { Err(UpdateError::Install(_)) )); assert_eq!(version_at(&bundle), "0.1.0"); - installer.clean_up(); - assert_eq!(names_in(folder.path()), ["ShellRS.app"]); + installer.clean_up(&Version::new(0, 1, 0)); + assert!( + !names_in(folder.path()) + .iter() + .any(|name| name.starts_with(".ShellRS.app.old-")) + ); } #[test] @@ -484,7 +520,7 @@ mod tests { .check_signature(&bundle, Some("SHELLRS")) .is_err() ); - installer.clean_up(); + installer.clean_up(&Version::new(0, 2, 0)); assert_eq!(names_in(&installed), ["ShellRS.app"]); } } diff --git a/src/update/install_windows.rs b/src/update/install_windows.rs index 9322535..7f2d038 100644 --- a/src/update/install_windows.rs +++ b/src/update/install_windows.rs @@ -11,6 +11,8 @@ use std::ffi::OsString; use std::path::{Path, PathBuf}; +use semver::Version; + use super::error::UpdateError; use super::install::{InstallKind, Installer, Launch, Relaunch, Staged}; use super::manifest::Release; @@ -99,7 +101,8 @@ impl Installer for SetupInstaller { } /// The setup program removes what the previous version left. - fn clean_up(&self) {} + // The package waits in the downloads folder, which the updater tidies. + fn clean_up(&self, _: &Version) {} } #[cfg(test)] diff --git a/src/update/updater.rs b/src/update/updater.rs index d68f8b4..875aad6 100644 --- a/src/update/updater.rs +++ b/src/update/updater.rs @@ -507,7 +507,7 @@ impl Updater { return; }; cx.background_spawn(async move { - services.installer.clean_up(); + services.installer.clean_up(&services.current); clean_downloads(&services.folder, &services.current); }) .detach(); diff --git a/tests/ui/update.rs b/tests/ui/update.rs index 0f18126..3c6682e 100644 --- a/tests/ui/update.rs +++ b/tests/ui/update.rs @@ -99,7 +99,7 @@ impl Installer for FakeInstaller { }) } - fn clean_up(&self) {} + fn clean_up(&self, _: &Version) {} } /// A copy of ShellRS that can install updates itself.