Compare commits

..
2 Commits
Author SHA1 Message Date
XuzandClaude Opus 5.5 9799f88ade 版本号改为 0.1.8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:22:26 +08:00
XuzandClaude Opus 5.5 dc5adddd06 启动后不久检查更新时,下好的新版本在重启时找不到
更新程序启动 30 秒后清理旧的暂存,原来会删掉所有 .ShellRS.app.update-*(Linux 是
.<AppImage>.update-*),包括启动后手动检查、切换渠道刚下载暂存好的新版本,于是「重启并
安装」报找不到文件,重试才从下载目录重新暂存。现在清理只删不比当前版本新的暂存,和下载
目录的规则一致。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:21:47 +08:00
9 changed files with 100 additions and 22 deletions

No files matched your search

+15
View File
@@ -2,6 +2,21 @@
每个版本一节,标题写成 `## [版本号]`,可以在后面加日期。发布流程(`.github/workflows/release.yml`)把与 tag 同版本的那一节原样作为更新说明:显示在 GitHub Release 和官网的更新日志里(ShellRS 的更新对话框用「查看更新内容」打开官网的更新日志),用 Markdown 书写,面向使用者。
## [0.1.8]
### 新增
- 设置 › 终端新增「终端交互」:选中即复制,右键可设为粘贴、显示菜单或无动作。
### 改进
- macOS 安装包分为 Apple Silicon 和 Intel 两种,下载更小。
### 修复
- 修复 ESXi 等主机「测试连接」报需要键盘交互式认证、保存的密码不起作用。
- 修复启动后不久检查更新时,「重启并安装」报找不到文件。
## [0.1.8-beta.1]
### 新增
Generated
+1 -1
View File
@@ -7738,7 +7738,7 @@ dependencies = [
[[package]]
name = "shellrs"
version = "0.1.8-beta.1"
version = "0.1.8"
dependencies = [
"alacritty_terminal",
"anyhow",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "shellrs"
version = "0.1.8-beta.1"
version = "0.1.8"
edition = "2024"
publish = false
description = "SSH client and host manager: terminal, SFTP, port forwarding"
+12 -3
View File
@@ -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 `<prefix><version>` 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::*;
+22 -7
View File
@@ -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"
]
);
}
}
+43 -7
View File
@@ -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"]);
}
}
+4 -1
View File
@@ -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)]
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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.