Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ function App() {
setJustUpdatedInfo,
setShowInstallConfirmModal,
showInstallConfirmModal,
quitAfterRun,
updateInfo,
downloadStatus,
setShowUpdateDialog,
Expand Down Expand Up @@ -238,6 +239,7 @@ function App() {
setJustUpdatedInfo: state.setJustUpdatedInfo,
setShowInstallConfirmModal: state.setShowInstallConfirmModal,
showInstallConfirmModal: state.showInstallConfirmModal,
quitAfterRun: state.quitAfterRun,
updateInfo: state.updateInfo,
downloadStatus: state.downloadStatus,
setShowUpdateDialog: state.setShowUpdateDialog,
Expand Down Expand Up @@ -414,6 +416,9 @@ function App() {
// 尝试自动安装更新(无任务运行中时触发)
const tryAutoInstallUpdate = useCallback(() => {
const state = useAppStore.getState();
// 本次启动实际启用 -q 自动任务时优先退出,更新留给下次普通启动安装。
// 启动策略尚未确定时也不安装,避免初始化期间抢先触发。
if (state.quitAfterRun !== false) return;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
if (state.downloadStatus !== 'completed') return;
if (state.installStatus !== 'idle') return;
if (state.autoInstallPending) return;
Expand Down Expand Up @@ -476,9 +481,7 @@ function App() {
if (result.success) {
// 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名)
setDownloadSavePath(result.actualSavePath);
setDownloadStatus('completed');
log.info('更新下载完成');

// 保存待安装更新信息,以便下次启动时自动安装
savePendingUpdateInfo({
versionName: updateResult.versionName,
Expand All @@ -491,6 +494,9 @@ function App() {
timestamp: Date.now(),
});

// 先持久化待安装信息,再通知下载完成,避免自动安装先于保存执行。
setDownloadStatus('completed');
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

// 尝试自动安装更新
tryAutoInstallUpdate();
} else {
Expand Down Expand Up @@ -592,6 +598,14 @@ function App() {
setErrorMessage('');

try {
let shouldQuit = false;
if (isTauri()) {
try {
shouldQuit = await invoke<boolean>('has_quit_after_run_flag');
} catch (err) {
log.warn('读取 --quit-after-run 参数失败,按普通启动继续初始化:', err);
}
}
log.info('加载 interface.json...');
const result = await autoLoadInterface();
setProjectInterface(result.interface);
Expand Down Expand Up @@ -876,6 +890,7 @@ function App() {
// 新版本下载在后台进行,不影响本次任务启动。
let autoStartTasksPending = false;
let isAutoRunOnLaunchMode = false;
let unsubscribeQuitAfterRun: (() => void) | undefined;
if (isTauri()) {
try {
const isAutoStart = await invoke<boolean>('is_autostart');
Expand Down Expand Up @@ -917,16 +932,16 @@ function App() {
}
useAppStore.getState().setActiveInstance(targetInstanceId);

// 检查 -q/--quit-after-run 参数:任务完成后关闭自身
const shouldQuit = await invoke<boolean>('has_quit_after_run_flag');
// -q/--quit-after-run:任务完成后关闭自身
if (shouldQuit) {
log.info('命令行 --quit-after-run 参数:任务完成后将关闭自身');
const unsub = useAppStore.subscribe(
unsubscribeQuitAfterRun = useAppStore.subscribe(
(state) => state.instances.find((i) => i.id === targetInstanceId)?.isRunning,
(isRunning, prevIsRunning) => {
if (prevIsRunning && !isRunning) {
log.info('自动执行任务完成,关闭自身');
unsub();
unsubscribeQuitAfterRun?.();
unsubscribeQuitAfterRun = undefined;
import('@tauri-apps/plugin-process').then(({ exit }) => exit(0));
}
},
Expand All @@ -943,6 +958,10 @@ function App() {
}
}

// -q 仅在本次启动将自动执行有效实例时生效,否则沿用普通更新流程。
shouldQuit = shouldQuit && autoStartTasksPending;
useAppStore.getState().setQuitAfterRun(shouldQuit);

const dispatchPendingAutoStartTasks = () => {
if (!autoStartTasksPending) return;
autoStartTasksPending = false;
Expand All @@ -952,8 +971,14 @@ function App() {
new CustomEvent('mxu-start-tasks', {
detail: {
source: 'autostart',
onSettled: () => {
onSettled: (started: boolean) => {
autoTaskLaunchPendingRef.current = false;
if (!started) {
// 空任务、连接失败等情况不会产生任务结束事件,恢复普通更新流程。
unsubscribeQuitAfterRun?.();
unsubscribeQuitAfterRun = undefined;
useAppStore.getState().setQuitAfterRun(false);
}
// 启动失败时任务不会产生 running -> idle 状态变化,需要在这里补一次安装检查;
// 启动成功时仍处于运行态,本次检查会直接返回,并在任务结束后再次触发。
tryAutoInstallUpdate();
Expand Down Expand Up @@ -993,9 +1018,9 @@ function App() {
normalizeVersion(currentVersionNow) === normalizeVersion(updateCompleteInfo.newVersion)
) {
log.info('版本已更新到目标版本');
if (isAutoStartModeNow) {
if (isAutoStartModeNow || shouldQuit) {
// 无人值守:跳过弹窗,继续检查是否有更新版本
log.info('自启动模式,跳过更新完成弹窗');
log.info('自动执行模式,跳过更新完成弹窗');
} else {
showUpdateCompletedUI();
if (isAutoRunOnLaunchMode) {
Expand All @@ -1010,8 +1035,8 @@ function App() {
// 直接显示更新完成弹窗(zip 等自动安装场景)
log.info('检测到刚更新完成:', updateCompleteInfo.newVersion);
clearPendingUpdateInfo();
if (isAutoStartModeNow) {
log.info('自启动模式,跳过更新完成弹窗');
if (isAutoStartModeNow || shouldQuit) {
log.info('自动执行模式,跳过更新完成弹窗');
} else {
showUpdateCompletedUI();
if (isAutoRunOnLaunchMode) {
Expand Down Expand Up @@ -1039,6 +1064,13 @@ function App() {
downloadSource: pendingUpdate.downloadSource,
});
setDownloadSavePath(pendingUpdate.downloadSavePath);
if (shouldQuit) {
// 托管运行不安装、不重启;保留待安装包供调用方另开更新会话。
setDownloadStatus('completed');
log.info('--quit-after-run 模式,保留待安装更新并继续执行任务');
dispatchPendingAutoStartTasks();
return;
}
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
// 先设置 installStatus,再设置 downloadStatus,
// 避免 subscribe 在 installStatus 还是 idle 时触发 tryAutoInstallUpdate 造成双发
useAppStore.getState().setInstallStatus('installing');
Expand Down Expand Up @@ -1378,7 +1410,7 @@ function App() {
useEffect(() => {
if (currentPage === 'main') {
// 下载完成:弹出安装模态框
if (downloadStatus === 'completed') {
if (downloadStatus === 'completed' && quitAfterRun === false) {
setShowInstallConfirmModal(true);
}
// 有更新、有错误或正在下载:弹出更新气泡
Expand All @@ -1391,16 +1423,17 @@ function App() {
updateInfo?.hasUpdate,
updateInfo?.errorCode,
downloadStatus,
quitAfterRun,
setShowUpdateDialog,
setShowInstallConfirmModal,
]);

// 下载完成时,强制弹出安装模态框
useEffect(() => {
if (downloadStatus === 'completed') {
if (downloadStatus === 'completed' && quitAfterRun === false) {
setShowInstallConfirmModal(true);
}
}, [downloadStatus, setShowInstallConfirmModal]);
}, [downloadStatus, quitAfterRun, setShowInstallConfirmModal]);

// 下载失败或取消时,如果有挂起的自动任务,立即分发
useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/UpdatePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) {
if (result.success) {
// 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名)
setDownloadSavePath(result.actualSavePath);
setDownloadStatus('completed');
// 保存待安装更新信息,以便下次启动时自动安装
savePendingUpdateInfo({
versionName: updateInfo.versionName,
Expand All @@ -92,6 +91,7 @@ export function UpdatePanel({ onClose, anchorRef }: UpdatePanelProps) {
downloadSource: updateInfo.downloadSource,
timestamp: Date.now(),
});
setDownloadStatus('completed');
} else {
setDownloadStatus('failed');
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/settings/UpdateSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
cancelDownload,
MIRRORCHYAN_ERROR_CODES,
isDebugVersion,
savePendingUpdateInfo,
} from '@/services/updateService';
import { createProxySettings, proxySettingsForUpdateDownload } from '@/services/proxyService';
import { resolveI18nText } from '@/services/contentResolver';
Expand Down Expand Up @@ -152,6 +153,16 @@ export function UpdateSection() {
if (result.success) {
// 使用实际保存路径(可能与请求路径不同,如果从 302 重定向检测到正确文件名)
setDownloadSavePath(result.actualSavePath);
savePendingUpdateInfo({
versionName: info.versionName,
releaseNote: info.releaseNote,
channel: info.channel,
downloadSavePath: result.actualSavePath,
fileSize: info.fileSize,
updateType: info.updateType,
downloadSource: info.downloadSource,
timestamp: Date.now(),
});
setDownloadStatus('completed');
} else {
setDownloadStatus('failed');
Expand Down
3 changes: 2 additions & 1 deletion src/services/updateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1223,14 +1223,15 @@ export function consumeUpdateCompleteInfo(): UpdateCompleteInfo | null {
}

/**
* 保存待安装更新信息到本地存储(下载完成后调用)
* 保存待安装更新信息到本地存储(下载完成后调用)。失败时抛出,由下载入口处理。
*/
export function savePendingUpdateInfo(info: PendingUpdateInfo): void {
try {
localStorage.setItem(PENDING_UPDATE_STORAGE_KEY, JSON.stringify(info));
log.info('已保存待安装更新信息:', info.versionName);
} catch (error) {
log.warn('保存待安装更新信息失败:', error);
throw error;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/stores/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,8 @@ export const useAppStore = create<AppState>()(
}),

// 安装状态
quitAfterRun: null,
setQuitAfterRun: (quit) => set({ quitAfterRun: quit }),
showInstallConfirmModal: false,
installStatus: 'idle',
installError: null,
Expand Down
3 changes: 3 additions & 0 deletions src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ export interface AppState {
resetDownloadState: () => void;

// 安装状态
/** 本次启动是否实际启用 -q 自动任务;null 表示尚未确定,此时不自动安装。仅运行时使用。 */
quitAfterRun: boolean | null;
setQuitAfterRun: (quit: boolean) => void;
showInstallConfirmModal: boolean;
installStatus: InstallStatus;
installError: string | null;
Expand Down
Loading