mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-12 16:02:48 +08:00
🐛 fix(update): 修复下载进度标题显示内部键并消除通道切换竞态
- 下载进度状态拆分 key 与 version 字段,弹窗标题恢复显示纯版本号 - SetUpdateChannel 将检查、持久化与缓存清理并入同一临界区,消除下载窗口期竞态 - 通道配置文件改为临时文件加重命名的原子写入 - 移除 useAppUpdateManager 未使用的 isMacRuntime 参数
This commit is contained in:
@@ -1714,7 +1714,6 @@ function App() {
|
||||
updateChannel,
|
||||
updateDownloadProgress,
|
||||
} = useAppUpdateManager({
|
||||
isMacRuntime,
|
||||
runtimeBuildType,
|
||||
t,
|
||||
});
|
||||
|
||||
@@ -54,10 +54,9 @@ describe('useAppUpdateManager', () => {
|
||||
return key;
|
||||
};
|
||||
|
||||
const renderHook = (isMacRuntime: boolean) => {
|
||||
const renderHook = () => {
|
||||
const Harness = () => {
|
||||
hook = useAppUpdateManager({
|
||||
isMacRuntime,
|
||||
runtimeBuildType: 'release',
|
||||
t,
|
||||
});
|
||||
@@ -113,7 +112,7 @@ describe('useAppUpdateManager', () => {
|
||||
backendApp.InstallUpdateAndRestart.mockResolvedValue({ success: true });
|
||||
backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
|
||||
|
||||
renderHook(true);
|
||||
renderHook();
|
||||
|
||||
await act(async () => {
|
||||
await hook?.checkForUpdates(false);
|
||||
@@ -146,7 +145,7 @@ describe('useAppUpdateManager', () => {
|
||||
});
|
||||
backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
|
||||
|
||||
renderHook(true);
|
||||
renderHook();
|
||||
|
||||
await act(async () => {
|
||||
await hook?.checkForUpdates(false);
|
||||
@@ -173,7 +172,7 @@ describe('useAppUpdateManager', () => {
|
||||
},
|
||||
});
|
||||
|
||||
renderHook(false);
|
||||
renderHook();
|
||||
|
||||
await act(async () => {
|
||||
await hook?.changeUpdateChannel('dev');
|
||||
|
||||
@@ -40,7 +40,6 @@ type UpdateDownloadResultData = {
|
||||
};
|
||||
|
||||
type UseAppUpdateManagerOptions = {
|
||||
isMacRuntime: boolean;
|
||||
runtimeBuildType: string;
|
||||
t: Translator;
|
||||
};
|
||||
@@ -48,6 +47,7 @@ type UseAppUpdateManagerOptions = {
|
||||
const createEmptyDownloadProgress = () => ({
|
||||
open: false,
|
||||
version: '',
|
||||
key: '',
|
||||
status: 'idle' as 'idle' | 'start' | 'downloading' | 'done' | 'error',
|
||||
percent: 0,
|
||||
downloaded: 0,
|
||||
@@ -64,7 +64,6 @@ const buildUpdateKey = (info: Pick<UpdateInfo, 'channel' | 'latestVersion'> | nu
|
||||
: '';
|
||||
|
||||
export const useAppUpdateManager = ({
|
||||
isMacRuntime: _isMacRuntime,
|
||||
runtimeBuildType,
|
||||
t,
|
||||
}: UseAppUpdateManagerOptions) => {
|
||||
@@ -148,7 +147,8 @@ export const useAppUpdateManager = ({
|
||||
updateDownloadMetaRef.current = null;
|
||||
setUpdateDownloadProgress({
|
||||
open: true,
|
||||
version: targetKey,
|
||||
version: info.latestVersion,
|
||||
key: targetKey,
|
||||
status: 'start',
|
||||
percent: 0,
|
||||
downloaded: 0,
|
||||
@@ -219,7 +219,7 @@ export const useAppUpdateManager = ({
|
||||
);
|
||||
const isBackgroundProgressForLatestUpdate = Boolean(lastUpdateInfo?.hasUpdate)
|
||||
&& Boolean(lastUpdateKey)
|
||||
&& updateDownloadProgress.version === lastUpdateKey
|
||||
&& updateDownloadProgress.key === lastUpdateKey
|
||||
&& (updateDownloadProgress.status === 'start'
|
||||
|| updateDownloadProgress.status === 'downloading'
|
||||
|| updateDownloadProgress.status === 'done'
|
||||
@@ -292,8 +292,9 @@ export const useAppUpdateManager = ({
|
||||
const total = info.assetSize || prev.total || 0;
|
||||
return {
|
||||
...prev,
|
||||
open: prev.open && prev.version === infoKey,
|
||||
version: infoKey,
|
||||
open: prev.open && prev.key === infoKey,
|
||||
version: info.latestVersion,
|
||||
key: infoKey,
|
||||
status: 'done',
|
||||
percent: 100,
|
||||
downloaded: total,
|
||||
@@ -317,7 +318,8 @@ export const useAppUpdateManager = ({
|
||||
return {
|
||||
...prev,
|
||||
open: false,
|
||||
version: infoKey,
|
||||
version: info.latestVersion,
|
||||
key: infoKey,
|
||||
status: 'idle',
|
||||
percent: 0,
|
||||
downloaded: 0,
|
||||
@@ -485,6 +487,7 @@ export const useAppUpdateManager = ({
|
||||
setUpdateDownloadProgress((prev) => ({
|
||||
open: prev.open,
|
||||
version: prev.version,
|
||||
key: prev.key,
|
||||
status: nextStatus,
|
||||
percent,
|
||||
downloaded,
|
||||
|
||||
@@ -81,7 +81,18 @@ func (a *App) persistUpdateChannel(channel updateChannel) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(updateChannelMetadataPath(a.configDir), payload, 0o644)
|
||||
|
||||
// 先写临时文件再重命名,避免进程中断留下损坏的 JSON。
|
||||
target := updateChannelMetadataPath(a.configDir)
|
||||
tmp := target + ".tmp"
|
||||
if err := os.WriteFile(tmp, payload, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmp, target); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) currentUpdateChannel() updateChannel {
|
||||
@@ -115,15 +126,16 @@ func (a *App) SetUpdateChannel(channel string) connection.QueryResult {
|
||||
return connection.QueryResult{Success: false, Message: a.localizedUpdateError(err)}
|
||||
}
|
||||
|
||||
// 检查、持久化与缓存清理必须在同一临界区内完成,
|
||||
// 否则窗口期内启动的下载会把旧通道的安装包写回缓存。
|
||||
a.updateMu.Lock()
|
||||
defer a.updateMu.Unlock()
|
||||
if a.updateState.downloading {
|
||||
a.updateMu.Unlock()
|
||||
return connection.QueryResult{
|
||||
Success: false,
|
||||
Message: a.appText("app.update.backend.message.channel_change_blocked_downloading", nil),
|
||||
}
|
||||
}
|
||||
a.updateMu.Unlock()
|
||||
|
||||
if err := a.persistUpdateChannel(normalized); err != nil {
|
||||
logger.Error(err, "保存更新通道失败")
|
||||
@@ -133,10 +145,8 @@ func (a *App) SetUpdateChannel(channel string) connection.QueryResult {
|
||||
}
|
||||
}
|
||||
|
||||
a.updateMu.Lock()
|
||||
a.updateState.lastCheck = nil
|
||||
a.updateState.staged = nil
|
||||
a.updateMu.Unlock()
|
||||
|
||||
return connection.QueryResult{
|
||||
Success: true,
|
||||
|
||||
Reference in New Issue
Block a user