🎨 Optimize update services
Some checks failed
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled

This commit is contained in:
2025-11-08 00:00:08 +08:00
parent cc98e556c6
commit 286b0159d7
4 changed files with 173 additions and 323 deletions

View File

@@ -80,7 +80,7 @@ function animationLoop() {
// 等待一段时间后重置动画 // 等待一段时间后重置动画
resetTimeoutId = window.setTimeout(() => { resetTimeoutId = window.setTimeout(() => {
reset(); reset();
}, 750); }, 500);
} }
} }
@@ -136,7 +136,8 @@ onBeforeUnmount(() => {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: var(--voidraft-bg-gradient, radial-gradient(#222922, #000500)); //background: var(--voidraft-bg-gradient, rgba(0, 5, 0, 0.15));
//backdrop-filter: blur(2px);
z-index: 1000; z-index: 1000;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { computed, readonly, ref, shallowRef, onScopeDispose } from 'vue'; import { computed, readonly, ref, onScopeDispose } from 'vue';
import { CheckForUpdates, ApplyUpdate, RestartApplication } from '@/../bindings/voidraft/internal/services/selfupdateservice'; import { CheckForUpdates, ApplyUpdate, RestartApplication } from '@/../bindings/voidraft/internal/services/selfupdateservice';
import { SelfUpdateResult } from '@/../bindings/voidraft/internal/services/models'; import { SelfUpdateResult } from '@/../bindings/voidraft/internal/services/models';
import { useConfigStore } from './configStore'; import { useConfigStore } from './configStore';

View File

@@ -55,9 +55,11 @@ onBeforeUnmount(() => {
<template> <template>
<div class="editor-container"> <div class="editor-container">
<LoadingScreen v-if="editorStore.isLoading && enableLoadingAnimation" text="VOIDRAFT"/>
<div ref="editorElement" class="editor"></div> <div ref="editorElement" class="editor"></div>
<Toolbar/> <Toolbar/>
<transition name="loading-fade">
<LoadingScreen v-if="editorStore.isLoading && enableLoadingAnimation" text="VOIDRAFT"/>
</transition>
</div> </div>
</template> </template>
@@ -85,4 +87,15 @@ onBeforeUnmount(() => {
:deep(.cm-scroller) { :deep(.cm-scroller) {
overflow: auto; overflow: auto;
} }
// 加载动画过渡效果
.loading-fade-enter-active,
.loading-fade-leave-active {
transition: opacity 0.3s ease;
}
.loading-fade-enter-from,
.loading-fade-leave-to {
opacity: 0;
}
</style> </style>

View File

@@ -4,13 +4,15 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"runtime"
"sync"
"time"
"github.com/creativeprojects/go-selfupdate" "github.com/creativeprojects/go-selfupdate"
"github.com/wailsapp/wails/v3/pkg/services/dock" "github.com/wailsapp/wails/v3/pkg/services/dock"
"github.com/wailsapp/wails/v3/pkg/services/log" "github.com/wailsapp/wails/v3/pkg/services/log"
"github.com/wailsapp/wails/v3/pkg/services/notifications" "github.com/wailsapp/wails/v3/pkg/services/notifications"
"os"
"runtime"
"time"
"voidraft/internal/models" "voidraft/internal/models"
) )
@@ -30,55 +32,57 @@ type SelfUpdateResult struct {
type SelfUpdateService struct { type SelfUpdateService struct {
logger *log.LogService logger *log.LogService
configService *ConfigService configService *ConfigService
badgeService *dock.DockService // 直接使用Wails原生badge服务 badgeService *dock.DockService
notificationService *notifications.NotificationService // 通知服务 notificationService *notifications.NotificationService
config *models.AppConfig
// 状态管理 mu sync.Mutex // 保护更新状态
isUpdating bool isUpdating bool
} }
// NewSelfUpdateService 创建自我更新服务实例 // NewSelfUpdateService 创建自我更新服务实例
func NewSelfUpdateService(configService *ConfigService, badgeService *dock.DockService, notificationService *notifications.NotificationService, logger *log.LogService) *SelfUpdateService { func NewSelfUpdateService(configService *ConfigService, badgeService *dock.DockService, notificationService *notifications.NotificationService, logger *log.LogService) *SelfUpdateService {
// 获取配置 return &SelfUpdateService{
appConfig, err := configService.GetConfig()
if err != nil {
panic(err)
}
service := &SelfUpdateService{
logger: logger, logger: logger,
configService: configService, configService: configService,
badgeService: badgeService, badgeService: badgeService,
notificationService: notificationService, notificationService: notificationService,
config: appConfig,
isUpdating: false, isUpdating: false,
} }
}
return service // getConfig 获取最新配置
func (s *SelfUpdateService) getConfig() (*models.AppConfig, error) {
config, err := s.configService.GetConfig()
if err != nil {
return nil, fmt.Errorf("get config failed: %w", err)
}
return config, nil
} }
// CheckForUpdates 检查更新 // CheckForUpdates 检查更新
func (s *SelfUpdateService) CheckForUpdates(ctx context.Context) (*SelfUpdateResult, error) { func (s *SelfUpdateService) CheckForUpdates(ctx context.Context) (*SelfUpdateResult, error) {
config, err := s.getConfig()
if err != nil {
return nil, err
}
result := &SelfUpdateResult{ result := &SelfUpdateResult{
CurrentVersion: s.config.Updates.Version, CurrentVersion: config.Updates.Version,
HasUpdate: false, HasUpdate: false,
UpdateApplied: false, UpdateApplied: false,
} }
// 首先尝试主要更新源 // 尝试主要更新源
primaryResult, err := s.checkSourceForUpdates(ctx, s.config.Updates.PrimarySource) primaryResult, err := s.checkSourceForUpdates(ctx, config.Updates.PrimarySource, config)
if err == nil && primaryResult != nil { if err == nil && primaryResult != nil {
s.handleUpdateBadge(primaryResult) s.handleUpdateBadge(primaryResult)
return primaryResult, nil return primaryResult, nil
} }
// 如果主要更新源失败,尝试备用更新源 // 尝试备用更新源
backupResult, backupErr := s.checkSourceForUpdates(ctx, s.config.Updates.BackupSource) backupResult, backupErr := s.checkSourceForUpdates(ctx, config.Updates.BackupSource, config)
if backupErr != nil { if backupErr != nil {
// 如果备用源也失败,返回主要源的错误信息 result.Error = fmt.Sprintf("both sources failed: %v; %v", err, backupErr)
result.Error = fmt.Sprintf("Primary source error: %v; Backup source error: %v", err, backupErr)
// 确保在检查失败时也调用handleUpdateBadge来清除可能存在的badge
s.handleUpdateBadge(result) s.handleUpdateBadge(result)
return result, errors.New(result.Error) return result, errors.New(result.Error)
} }
@@ -88,17 +92,16 @@ func (s *SelfUpdateService) CheckForUpdates(ctx context.Context) (*SelfUpdateRes
} }
// checkSourceForUpdates 根据更新源类型检查更新 // checkSourceForUpdates 根据更新源类型检查更新
func (s *SelfUpdateService) checkSourceForUpdates(ctx context.Context, sourceType models.UpdateSourceType) (*SelfUpdateResult, error) { func (s *SelfUpdateService) checkSourceForUpdates(ctx context.Context, sourceType models.UpdateSourceType, config *models.AppConfig) (*SelfUpdateResult, error) {
// 创建带超时的上下文 timeout := config.Updates.UpdateTimeout
timeout := s.config.Updates.UpdateTimeout
if timeout <= 0 { if timeout <= 0 {
timeout = 30 // 默认30秒 timeout = 30
} }
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second) timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
defer cancel() defer cancel()
result := &SelfUpdateResult{ result := &SelfUpdateResult{
CurrentVersion: s.config.Updates.Version, CurrentVersion: config.Updates.Version,
HasUpdate: false, HasUpdate: false,
UpdateApplied: false, UpdateApplied: false,
Source: string(sourceType), Source: string(sourceType),
@@ -110,320 +113,164 @@ func (s *SelfUpdateService) checkSourceForUpdates(ctx context.Context, sourceTyp
switch sourceType { switch sourceType {
case models.UpdateSourceGithub: case models.UpdateSourceGithub:
release, found, err = s.checkGithubUpdates(timeoutCtx) release, found, err = s.checkGithubUpdates(timeoutCtx, config)
case models.UpdateSourceGitea: case models.UpdateSourceGitea:
release, found, err = s.checkGiteaUpdates(timeoutCtx) release, found, err = s.checkGiteaUpdates(timeoutCtx, config)
default: default:
return nil, fmt.Errorf("unsupported update source type: %s", sourceType) return nil, fmt.Errorf("unsupported source: %s", sourceType)
} }
if err != nil { if err != nil {
result.Error = fmt.Sprintf("Failed to check for updates: %v", err) return result, fmt.Errorf("check failed: %w", err)
return result, err
} }
if !found { if !found {
result.Error = fmt.Sprintf("No release found for %s/%s on %s", return result, fmt.Errorf("no release for %s/%s", runtime.GOOS, runtime.GOARCH)
runtime.GOOS, runtime.GOARCH, s.getRepoName(sourceType))
return result, errors.New(result.Error)
} }
result.LatestVersion = release.Version() result.LatestVersion = release.Version()
result.AssetURL = release.AssetURL result.AssetURL = release.AssetURL
result.ReleaseNotes = release.ReleaseNotes result.ReleaseNotes = release.ReleaseNotes
result.HasUpdate = release.GreaterThan(config.Updates.Version)
// 比较版本
if release.GreaterThan(s.config.Updates.Version) {
result.HasUpdate = true
} else {
s.logger.Info("Current version is up to date")
}
return result, nil return result, nil
} }
// createGithubUpdater 创建GitHub更新器 // createGithubUpdater 创建GitHub更新器
func (s *SelfUpdateService) createGithubUpdater() (*selfupdate.Updater, error) { func (s *SelfUpdateService) createGithubUpdater() (*selfupdate.Updater, error) {
// 使用默认的GitHub源 return selfupdate.NewUpdater(selfupdate.Config{})
updaterConfig := selfupdate.Config{}
return selfupdate.NewUpdater(updaterConfig)
} }
// createGiteaUpdater 创建Gitea更新器 // createGiteaUpdater 创建Gitea更新器
func (s *SelfUpdateService) createGiteaUpdater() (*selfupdate.Updater, error) { func (s *SelfUpdateService) createGiteaUpdater(config *models.AppConfig) (*selfupdate.Updater, error) {
giteaConfig := s.config.Updates.Gitea
// 创建Gitea源
source, err := selfupdate.NewGiteaSource(selfupdate.GiteaConfig{ source, err := selfupdate.NewGiteaSource(selfupdate.GiteaConfig{
BaseURL: giteaConfig.BaseURL, BaseURL: config.Updates.Gitea.BaseURL,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create Gitea source: %w", err) return nil, fmt.Errorf("create gitea source failed: %w", err)
} }
// 创建使用Gitea源的更新器 return selfupdate.NewUpdater(selfupdate.Config{Source: source})
updaterConfig := selfupdate.Config{
Source: source,
}
return selfupdate.NewUpdater(updaterConfig)
} }
// checkGithubUpdates 检查GitHub更新 // checkGithubUpdates 检查GitHub更新
func (s *SelfUpdateService) checkGithubUpdates(ctx context.Context) (*selfupdate.Release, bool, error) { func (s *SelfUpdateService) checkGithubUpdates(ctx context.Context, config *models.AppConfig) (*selfupdate.Release, bool, error) {
// 创建GitHub更新器
updater, err := s.createGithubUpdater() updater, err := s.createGithubUpdater()
if err != nil { if err != nil {
return nil, false, fmt.Errorf("failed to create GitHub updater: %w", err) return nil, false, err
} }
githubConfig := s.config.Updates.Github repo := selfupdate.NewRepositorySlug(config.Updates.Github.Owner, config.Updates.Github.Repo)
repository := selfupdate.NewRepositorySlug(githubConfig.Owner, githubConfig.Repo) return updater.DetectLatest(ctx, repo)
// 检测最新版本
return updater.DetectLatest(ctx, repository)
} }
// checkGiteaUpdates 检查Gitea更新 // checkGiteaUpdates 检查Gitea更新
func (s *SelfUpdateService) checkGiteaUpdates(ctx context.Context) (*selfupdate.Release, bool, error) { func (s *SelfUpdateService) checkGiteaUpdates(ctx context.Context, config *models.AppConfig) (*selfupdate.Release, bool, error) {
// 创建Gitea更新器 updater, err := s.createGiteaUpdater(config)
updater, err := s.createGiteaUpdater()
if err != nil { if err != nil {
return nil, false, fmt.Errorf("failed to create Gitea updater: %w", err) return nil, false, err
} }
giteaConfig := s.config.Updates.Gitea repo := selfupdate.NewRepositorySlug(config.Updates.Gitea.Owner, config.Updates.Gitea.Repo)
repository := selfupdate.NewRepositorySlug(giteaConfig.Owner, giteaConfig.Repo) return updater.DetectLatest(ctx, repo)
// 检测最新版本
return updater.DetectLatest(ctx, repository)
}
// getRepoName 获取当前更新源的仓库名称
func (s *SelfUpdateService) getRepoName(sourceType models.UpdateSourceType) string {
switch sourceType {
case models.UpdateSourceGithub:
return s.config.Updates.Github.Repo
case models.UpdateSourceGitea:
return s.config.Updates.Gitea.Repo
default:
return "unknown"
}
} }
// ApplyUpdate 应用更新 // ApplyUpdate 应用更新
func (s *SelfUpdateService) ApplyUpdate(ctx context.Context) (*SelfUpdateResult, error) { func (s *SelfUpdateService) ApplyUpdate(ctx context.Context) (*SelfUpdateResult, error) {
s.mu.Lock()
if s.isUpdating { if s.isUpdating {
return nil, errors.New("update is already in progress") s.mu.Unlock()
return nil, errors.New("update in progress")
} }
s.isUpdating = true s.isUpdating = true
s.mu.Unlock()
defer func() { defer func() {
s.mu.Lock()
s.isUpdating = false s.isUpdating = false
s.mu.Unlock()
}() }()
// 获取可执行文件路径 config, err := s.getConfig()
if err != nil {
return nil, err
}
exe, err := selfupdate.ExecutablePath() exe, err := selfupdate.ExecutablePath()
if err != nil { if err != nil {
return &SelfUpdateResult{ return nil, fmt.Errorf("locate executable failed: %w", err)
CurrentVersion: s.config.Updates.Version,
Error: fmt.Sprintf("Could not locate executable path: %v", err),
}, err
} }
// 创建带超时的上下文,仅用于检测最新版本 // 尝试主要源
timeout := s.config.Updates.UpdateTimeout result, err := s.performUpdate(ctx, config.Updates.PrimarySource, exe, config)
if timeout <= 0 { if err == nil {
timeout = 30 // 默认30秒
}
checkTimeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
defer cancel()
result := &SelfUpdateResult{
CurrentVersion: s.config.Updates.Version,
}
// 首先尝试从主要更新源获取更新信息
primarySourceType := s.config.Updates.PrimarySource
backupSourceType := s.config.Updates.BackupSource
result.Source = string(primarySourceType)
// 从主更新源获取更新信息
primaryUpdater, primaryRelease, primaryFound, err := s.getUpdateFromSource(checkTimeoutCtx, primarySourceType)
if err != nil || !primaryFound {
// 主更新源失败,直接尝试备用源
return s.updateFromSource(ctx, backupSourceType, exe)
}
// 检查是否有可用更新
if !primaryRelease.GreaterThan(s.config.Updates.Version) {
result.LatestVersion = primaryRelease.Version()
return result, nil return result, nil
} }
// 更新结果信息 // 尝试备用源
result.LatestVersion = primaryRelease.Version() result, err = s.performUpdate(ctx, config.Updates.BackupSource, exe, config)
result.AssetURL = primaryRelease.AssetURL
result.ReleaseNotes = primaryRelease.ReleaseNotes
result.HasUpdate = true
// 备份当前可执行文件(如果启用)
var backupPath string
if s.config.Updates.BackupBeforeUpdate {
var err error
backupPath, err = s.createBackup(exe)
if err != nil { if err != nil {
result.Error = fmt.Sprintf("Failed to create backup: %v", err) return nil, fmt.Errorf("update failed from both sources: %w", err)
return result, err
}
}
// 从主要源尝试下载并应用更新,不设置超时
err = primaryUpdater.UpdateTo(ctx, primaryRelease, exe)
// 如果主要源下载失败,尝试备用源
if err != nil {
// 尝试从备用源更新
backupResult, backupErr := s.updateFromSource(ctx, backupSourceType, exe)
// 如果备用源也失败,清理并返回错误
if backupErr != nil {
if backupPath != "" {
s.cleanupBackup(backupPath)
}
result.Error = fmt.Sprintf("Update failed from both sources: primary error: %v; backup error: %v", err, backupErr)
return result, errors.New(result.Error)
}
// 备用源成功
return backupResult, nil
}
// 主要源更新成功
result.UpdateApplied = true
// 更新成功后清理备份文件
if backupPath != "" {
if err := s.cleanupBackup(backupPath); err != nil {
s.logger.Error("Failed to cleanup backup", "error", err)
}
}
// 更新配置中的版本号
if err := s.updateConfigVersion(result.LatestVersion); err != nil {
s.logger.Error("Failed to update config version", "error", err)
}
// 执行配置迁移
if err := s.configService.MigrateConfig(); err != nil {
s.logger.Error("Failed to migrate config after update", "error", err)
}
// 更新成功移除badge
if s.badgeService != nil {
if err := s.badgeService.RemoveBadge(); err != nil {
s.logger.Error("failed to remove update badge after successful update", "error", err)
}
} }
return result, nil return result, nil
} }
// updateFromSource 从指定源尝试下载应用更新 // performUpdate 执行更新操作(包括检测、备份、下载应用
func (s *SelfUpdateService) updateFromSource(ctx context.Context, sourceType models.UpdateSourceType, exe string) (*SelfUpdateResult, error) { func (s *SelfUpdateService) performUpdate(ctx context.Context, sourceType models.UpdateSourceType, exe string, config *models.AppConfig) (*SelfUpdateResult, error) {
// 创建带超时的上下文,仅用于检测最新版本 timeout := config.Updates.UpdateTimeout
checkTimeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(s.config.Updates.UpdateTimeout)*time.Second) if timeout <= 0 {
timeout = 30
}
checkCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
defer cancel() defer cancel()
// 获取更新器和版本信息
updater, release, found, err := s.getUpdateFromSource(checkCtx, sourceType, config)
if err != nil || !found {
return nil, fmt.Errorf("detect release failed: %w", err)
}
result := &SelfUpdateResult{ result := &SelfUpdateResult{
CurrentVersion: s.config.Updates.Version, CurrentVersion: config.Updates.Version,
LatestVersion: release.Version(),
AssetURL: release.AssetURL,
ReleaseNotes: release.ReleaseNotes,
Source: string(sourceType), Source: string(sourceType),
HasUpdate: release.GreaterThan(config.Updates.Version),
} }
s.logger.Info("Attempting to update from source", "source", sourceType) // 无更新
if !result.HasUpdate {
// 获取更新信息
updater, release, found, err := s.getUpdateFromSource(checkTimeoutCtx, sourceType)
if err != nil {
result.Error = fmt.Sprintf("Failed to detect latest release from %s: %v", sourceType, err)
return result, err
}
if !found {
result.Error = fmt.Sprintf("Latest release not found from %s", sourceType)
return result, errors.New(result.Error)
}
// 更新结果信息
result.LatestVersion = release.Version()
result.AssetURL = release.AssetURL
result.ReleaseNotes = release.ReleaseNotes
// 检查是否有更新
if !release.GreaterThan(s.config.Updates.Version) {
s.logger.Info("Current version is up to date, no need to apply update")
return result, nil return result, nil
} }
// 标记有更新可用 // 创建备份
result.HasUpdate = true
// 备份当前可执行文件(如果启用且尚未备份)
var backupPath string var backupPath string
if s.config.Updates.BackupBeforeUpdate { if config.Updates.BackupBeforeUpdate {
s.logger.Info("Creating backup before update...")
var err error
backupPath, err = s.createBackup(exe) backupPath, err = s.createBackup(exe)
if err != nil { if err != nil {
result.Error = fmt.Sprintf("Failed to create backup: %v", err) return nil, fmt.Errorf("backup failed: %w", err)
return result, err
} }
} defer func() {
// 尝试下载并应用更新,不设置超时
err = updater.UpdateTo(ctx, release, exe)
if err != nil {
result.Error = fmt.Sprintf("Failed to apply update from %s: %v", sourceType, err)
// 移除下载失败时恢复备份的逻辑,让用户手动处理
if backupPath != "" { if backupPath != "" {
s.logger.Info("Update failed, backup is available at: " + backupPath) s.cleanupBackup(backupPath)
} }
return result, err }()
}
// 下载并应用更新
if err := updater.UpdateTo(ctx, release, exe); err != nil {
return nil, fmt.Errorf("apply update failed: %w", err)
} }
result.UpdateApplied = true result.UpdateApplied = true
s.handleUpdateSuccess(result)
// 更新成功后清理备份文件
if backupPath != "" {
if err := s.cleanupBackup(backupPath); err != nil {
s.logger.Error("Failed to cleanup backup", "error", err)
}
}
// 更新配置中的版本号
if err := s.updateConfigVersion(result.LatestVersion); err != nil {
s.logger.Error("Failed to update config version", "error", err)
}
// 更新成功移除badge
if s.badgeService != nil {
if err := s.badgeService.RemoveBadge(); err != nil {
s.logger.Error("failed to remove update badge after successful update", "error", err)
}
}
return result, nil return result, nil
} }
// getUpdateFromSource 从指定源获取更新信息 // getUpdateFromSource 从指定源获取更新信息
func (s *SelfUpdateService) getUpdateFromSource(ctx context.Context, sourceType models.UpdateSourceType) (*selfupdate.Updater, *selfupdate.Release, bool, error) { func (s *SelfUpdateService) getUpdateFromSource(ctx context.Context, sourceType models.UpdateSourceType, config *models.AppConfig) (*selfupdate.Updater, *selfupdate.Release, bool, error) {
var updater *selfupdate.Updater var updater *selfupdate.Updater
var release *selfupdate.Release var release *selfupdate.Release
var found bool var found bool
@@ -433,50 +280,50 @@ func (s *SelfUpdateService) getUpdateFromSource(ctx context.Context, sourceType
case models.UpdateSourceGithub: case models.UpdateSourceGithub:
updater, err = s.createGithubUpdater() updater, err = s.createGithubUpdater()
if err != nil { if err != nil {
return nil, nil, false, fmt.Errorf("failed to create GitHub updater: %w", err) return nil, nil, false, err
} }
release, found, err = s.checkGithubUpdates(ctx) release, found, err = s.checkGithubUpdates(ctx, config)
case models.UpdateSourceGitea: case models.UpdateSourceGitea:
updater, err = s.createGiteaUpdater() updater, err = s.createGiteaUpdater(config)
if err != nil { if err != nil {
return nil, nil, false, fmt.Errorf("failed to create Gitea updater: %w", err) return nil, nil, false, err
} }
release, found, err = s.checkGiteaUpdates(ctx) release, found, err = s.checkGiteaUpdates(ctx, config)
default: default:
return nil, nil, false, fmt.Errorf("unsupported update source type: %s", sourceType) return nil, nil, false, fmt.Errorf("unsupported source: %s", sourceType)
} }
return updater, release, found, err return updater, release, found, err
} }
// RestartApplication 重启应用程序 // handleUpdateSuccess 处理更新成功后的操作
func (s *SelfUpdateService) RestartApplication() error { func (s *SelfUpdateService) handleUpdateSuccess(result *SelfUpdateResult) {
return s.restartApplication() // 更新配置版本
} if err := s.configService.Set("updates.version", result.LatestVersion); err != nil {
s.logger.Error("update config version failed", "error", err)
// updateConfigVersion 更新配置中的版本号 }
func (s *SelfUpdateService) updateConfigVersion(version string) error {
// 使用configService更新配置中的版本号 // 执行配置迁移
if err := s.configService.Set("updates.version", version); err != nil { if err := s.configService.MigrateConfig(); err != nil {
return fmt.Errorf("failed to update config version: %w", err) s.logger.Error("migrate config failed", "error", err)
}
// 移除badge
if s.badgeService != nil {
s.badgeService.RemoveBadge()
} }
return nil
} }
// createBackup 创建当前可执行文件备份 // createBackup 创建可执行文件备份
func (s *SelfUpdateService) createBackup(executablePath string) (string, error) { func (s *SelfUpdateService) createBackup(executablePath string) (string, error) {
backupPath := executablePath + ".backup" backupPath := executablePath + ".backup"
// 读取原文件
data, err := os.ReadFile(executablePath) data, err := os.ReadFile(executablePath)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read executable: %w", err) return "", fmt.Errorf("read executable failed: %w", err)
} }
// 写入备份文件 if err := os.WriteFile(backupPath, data, 0755); err != nil {
err = os.WriteFile(backupPath, data, 0755) return "", fmt.Errorf("write backup failed: %w", err)
if err != nil {
return "", fmt.Errorf("failed to create backup: %w", err)
} }
return backupPath, nil return backupPath, nil
@@ -485,31 +332,34 @@ func (s *SelfUpdateService) createBackup(executablePath string) (string, error)
// cleanupBackup 清理备份文件 // cleanupBackup 清理备份文件
func (s *SelfUpdateService) cleanupBackup(backupPath string) error { func (s *SelfUpdateService) cleanupBackup(backupPath string) error {
if err := os.Remove(backupPath); err != nil && !os.IsNotExist(err) { if err := os.Remove(backupPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove backup file: %w", err) s.logger.Error("cleanup backup failed", "error", err)
} }
return nil return nil
} }
// handleUpdateBadge 处理更新通知badge和通知 // RestartApplication 重启应用程序
func (s *SelfUpdateService) RestartApplication() error {
return s.restartApplication()
}
// handleUpdateBadge 处理更新徽章和通知
func (s *SelfUpdateService) handleUpdateBadge(result *SelfUpdateResult) { func (s *SelfUpdateService) handleUpdateBadge(result *SelfUpdateResult) {
if result != nil && result.HasUpdate { if result == nil || !result.HasUpdate {
// 有更新时显示更新badge if s.badgeService != nil {
s.badgeService.RemoveBadge()
}
return
}
// 显示徽章
if s.badgeService != nil { if s.badgeService != nil {
if err := s.badgeService.SetBadge("●"); err != nil { if err := s.badgeService.SetBadge("●"); err != nil {
s.logger.Error("failed to set update badge", "error", err) s.logger.Error("set badge failed", "error", err)
} }
} }
// 发送简单通知 // 发送通知
s.sendUpdateNotification(result) s.sendUpdateNotification(result)
} else {
// 没有更新或出错时移除badge
if s.badgeService != nil {
if err := s.badgeService.RemoveBadge(); err != nil {
s.logger.Error("failed to remove update badge", "error", err)
}
}
}
} }
// sendUpdateNotification 发送更新通知 // sendUpdateNotification 发送更新通知
@@ -518,34 +368,20 @@ func (s *SelfUpdateService) sendUpdateNotification(result *SelfUpdateResult) {
return return
} }
// 检查通知授权macOS需要 // 检查授权
authorized, err := s.notificationService.CheckNotificationAuthorization() authorized, err := s.notificationService.CheckNotificationAuthorization()
if err != nil { if err != nil || !authorized {
s.logger.Error("Failed to check notification authorization", "error", err)
return
}
if !authorized {
authorized, err = s.notificationService.RequestNotificationAuthorization() authorized, err = s.notificationService.RequestNotificationAuthorization()
if err != nil || !authorized { if err != nil || !authorized {
s.logger.Error("Failed to get notification authorization", "error", err)
return return
} }
} }
// 构建简单通知内容 // 发送通知
title := "Voidraft Update Available" s.notificationService.SendNotification(notifications.NotificationOptions{
body := fmt.Sprintf("New version %s available (current: %s)", result.LatestVersion, result.CurrentVersion)
// 发送简单通知
err = s.notificationService.SendNotification(notifications.NotificationOptions{
ID: "update_available", ID: "update_available",
Title: title, Title: "Voidraft Update Available",
Subtitle: "New version available", Subtitle: "New version available",
Body: body, Body: fmt.Sprintf("Version %s available (current: %s)", result.LatestVersion, result.CurrentVersion),
}) })
if err != nil {
s.logger.Error("Failed to send notification", "error", err)
return
}
} }