mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-12 01:24:12 +08:00
🐛 fix(update): 修复 Windows 更新安装卡住并将安装包落到应用目录
- Windows 安装前预检当前安装目录写权限,避免脚本启动后主进程先退出 - 已下载更新补充打开安装目录入口,便于手动执行安装包 - 更新工作区优先改为当前应用运行目录,并补充回归测试与多语言文案
This commit is contained in:
@@ -34,10 +34,12 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
updateFetchLatestRelease = fetchLatestRelease
|
||||
updateFetchDevRelease = fetchDevRelease
|
||||
updateFetchReleaseSHA256 = fetchReleaseSHA256
|
||||
updateLogCheckError = func(err error) { logger.Error(err, "检查更新失败") }
|
||||
updateFetchLatestRelease = fetchLatestRelease
|
||||
updateFetchDevRelease = fetchDevRelease
|
||||
updateFetchReleaseSHA256 = fetchReleaseSHA256
|
||||
updateLogCheckError = func(err error) { logger.Error(err, "检查更新失败") }
|
||||
updateResolveInstallTarget = resolveUpdateInstallTarget
|
||||
updateLaunchInstallScript = launchUpdateScript
|
||||
)
|
||||
|
||||
type updateState struct {
|
||||
@@ -244,7 +246,18 @@ func (a *App) InstallUpdateAndRestart() connection.QueryResult {
|
||||
return connection.QueryResult{Success: false, Message: a.appText("app.update.backend.message.no_downloaded_package", nil)}
|
||||
}
|
||||
|
||||
if err := launchUpdateScript(staged); err != nil {
|
||||
if stdRuntime.GOOS == "windows" {
|
||||
if err := ensureWindowsUpdateTargetWritable(updateResolveInstallTarget()); err != nil {
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: a.appText("app.update.backend.message.install_launch_failed", map[string]any{
|
||||
"detail": a.localizedUpdateError(err),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateLaunchInstallScript(staged); err != nil {
|
||||
logger.Error(err, "启动更新脚本失败")
|
||||
detail := a.localizedUpdateError(err)
|
||||
msg := a.appText("app.update.backend.message.install_launch_failed", map[string]any{"detail": detail})
|
||||
@@ -956,8 +969,7 @@ func resolveLegacyUpdateWorkspaceDir() string {
|
||||
}
|
||||
|
||||
func resolveUpdateWorkspaceDir(version string) string {
|
||||
// 默认使用系统临时目录作为更新工作区,避免目录权限与锁冲突。
|
||||
// macOS 用户要求更新包默认保存在桌面:Desktop/GoNavi-<version>/。
|
||||
// macOS 更新包继续保存在桌面版本目录根级,方便用户直接处理 DMG。
|
||||
if stdRuntime.GOOS == "darwin" {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && strings.TrimSpace(homeDir) != "" {
|
||||
@@ -967,6 +979,16 @@ func resolveUpdateWorkspaceDir(version string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Windows / Linux 更新包优先落到当前应用运行目录,方便用户直接找到下载产物。
|
||||
targetPath := strings.TrimSpace(updateResolveInstallTarget())
|
||||
if targetPath != "" {
|
||||
targetDir := strings.TrimSpace(filepath.Dir(targetPath))
|
||||
if targetDir != "" && targetDir != "." {
|
||||
return targetDir
|
||||
}
|
||||
}
|
||||
|
||||
return resolveLegacyUpdateWorkspaceDir()
|
||||
}
|
||||
|
||||
@@ -1094,6 +1116,33 @@ func resolveUpdateInstallTarget() string {
|
||||
return exePath
|
||||
}
|
||||
|
||||
func ensureWindowsUpdateTargetWritable(targetExe string) error {
|
||||
targetExe = strings.TrimSpace(targetExe)
|
||||
targetDir := strings.TrimSpace(filepath.Dir(targetExe))
|
||||
if targetExe == "" || targetDir == "" || targetDir == "." {
|
||||
return localizedUpdateError{key: "app.update.backend.error.install_target_unresolved"}
|
||||
}
|
||||
|
||||
probePath := filepath.Join(targetDir, fmt.Sprintf(".gonavi-update-write-probe-%d.tmp", time.Now().UnixNano()))
|
||||
file, err := os.OpenFile(probePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
||||
if err != nil {
|
||||
return localizedUpdateError{
|
||||
key: "app.update.backend.error.install_target_not_writable",
|
||||
params: map[string]any{
|
||||
"path": targetDir,
|
||||
"detail": err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
if closeErr := file.Close(); closeErr != nil {
|
||||
logger.Warnf("关闭 Windows 更新写入探针失败:%v", closeErr)
|
||||
}
|
||||
if removeErr := os.Remove(probePath); removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
|
||||
logger.Warnf("清理 Windows 更新写入探针失败:%v", removeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) emitUpdateDownloadProgress(status string, downloaded, total int64, message string) {
|
||||
if a.ctx == nil {
|
||||
return
|
||||
|
||||
@@ -340,6 +340,87 @@ func TestDownloadUpdateUsesCurrentLanguageForBackendMessage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureWindowsUpdateTargetWritableAcceptsWritableDirectory(t *testing.T) {
|
||||
if stdRuntime.GOOS != "windows" {
|
||||
t.Skip("windows-only update target validation")
|
||||
}
|
||||
|
||||
target := filepath.Join(t.TempDir(), "GoNavi.exe")
|
||||
if err := ensureWindowsUpdateTargetWritable(target); err != nil {
|
||||
t.Fatalf("ensureWindowsUpdateTargetWritable returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallUpdateAndRestartFailsBeforeLaunchWhenWindowsTargetDirIsNotWritable(t *testing.T) {
|
||||
if stdRuntime.GOOS != "windows" {
|
||||
t.Skip("windows-only update target validation")
|
||||
}
|
||||
|
||||
stagedDir := t.TempDir()
|
||||
assetPath := filepath.Join(stagedDir, "GoNavi-0.8.2-Windows-Amd64.exe")
|
||||
if err := os.WriteFile(assetPath, []byte("12345678"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile returned error: %v", err)
|
||||
}
|
||||
|
||||
app := NewApp()
|
||||
app.updateState.staged = &stagedUpdate{
|
||||
Channel: updateChannelLatest,
|
||||
Version: "0.8.2",
|
||||
AssetName: filepath.Base(assetPath),
|
||||
FilePath: assetPath,
|
||||
StagedDir: stagedDir,
|
||||
}
|
||||
|
||||
originalResolveInstallTarget := updateResolveInstallTarget
|
||||
originalLaunchInstallScript := updateLaunchInstallScript
|
||||
t.Cleanup(func() {
|
||||
updateResolveInstallTarget = originalResolveInstallTarget
|
||||
updateLaunchInstallScript = originalLaunchInstallScript
|
||||
})
|
||||
|
||||
updateResolveInstallTarget = func() string {
|
||||
return filepath.Join(stagedDir, "missing", "GoNavi.exe")
|
||||
}
|
||||
|
||||
launched := false
|
||||
updateLaunchInstallScript = func(*stagedUpdate) error {
|
||||
launched = true
|
||||
return nil
|
||||
}
|
||||
|
||||
result := app.InstallUpdateAndRestart()
|
||||
if result.Success {
|
||||
t.Fatalf("expected InstallUpdateAndRestart to fail, got %#v", result)
|
||||
}
|
||||
if launched {
|
||||
t.Fatal("expected launch script to be skipped when install target is not writable")
|
||||
}
|
||||
if !strings.Contains(result.Message, "not writable") {
|
||||
t.Fatalf("expected install target write failure in message, got %q", result.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveUpdateWorkspaceDirPrefersCurrentInstallDirectory(t *testing.T) {
|
||||
if stdRuntime.GOOS == "darwin" {
|
||||
t.Skip("macOS keeps update downloads on Desktop")
|
||||
}
|
||||
|
||||
targetDir := t.TempDir()
|
||||
originalResolveInstallTarget := updateResolveInstallTarget
|
||||
t.Cleanup(func() {
|
||||
updateResolveInstallTarget = originalResolveInstallTarget
|
||||
})
|
||||
|
||||
updateResolveInstallTarget = func() string {
|
||||
return filepath.Join(targetDir, "GoNavi.exe")
|
||||
}
|
||||
|
||||
got := resolveUpdateWorkspaceDir("0.8.2")
|
||||
if got != targetDir {
|
||||
t.Fatalf("expected workspace dir %q, got %q", targetDir, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpectedAssetNameForExecutableUsesLinuxWebKit41Suffix(t *testing.T) {
|
||||
assetName, err := expectedAssetNameForExecutable(
|
||||
"linux",
|
||||
|
||||
Reference in New Issue
Block a user