Skip to content
Merged
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
2 changes: 2 additions & 0 deletions OpenBitFun-Installer/AGENTS-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
Language Select → Options → Progress → Model Setup → Theme Setup
```

如果所选路径已有 OpenBitFun 安装,流程会在 `Progress` 后结束,保留已有的模型和主题配置。

## 命令

这些是命令参考,不是默认预检清单。PR 范围请以下方 Verification 为准。
Expand Down
3 changes: 3 additions & 0 deletions OpenBitFun-Installer/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Install flow:
Language Select → Options → Progress → Model Setup → Theme Setup
```

For an existing installation at the selected path, the flow ends after
`Progress`; model and theme setup are retained from the existing installation.

## Commands

These are command references, not the default precheck list. Use Verification
Expand Down
5 changes: 5 additions & 0 deletions OpenBitFun-Installer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ Language Select → Options → Progress → Model Setup → Theme Setup
language options install model config launch/close
```

When the selected installation path already contains the current OpenBitFun
installation, the update flow ends after Progress. The existing model and theme
configuration are preserved, and the installer launches the updated application
when launch-after-install is enabled.

## Development

### Prerequisites
Expand Down
33 changes: 32 additions & 1 deletion OpenBitFun-Installer/src/hooks/useInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ export interface UseInstallerReturn {

const STEPS: InstallStep[] = ['lang', 'options', 'progress', 'model', 'theme'];

function normalizeInstallPath(path: string): string {
return path.trim().replace(/[\\/]+$/, '');
}

function isSameInstallPath(left: string, right: string): boolean {
const normalizedLeft = normalizeInstallPath(left);
const normalizedRight = normalizeInstallPath(right);
if (normalizedLeft === normalizedRight) return true;

// Windows registry paths are case-insensitive. Keep POSIX paths case-sensitive.
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(normalizedLeft)
|| /^[A-Za-z]:[\\/]/.test(normalizedRight)
|| normalizedLeft.includes('\\')
|| normalizedRight.includes('\\');
return isWindowsPath && normalizedLeft.toLowerCase() === normalizedRight.toLowerCase();
}

export function useInstaller(): UseInstallerReturn {
const [previewOnly, setPreviewOnly] = useState(false);
const [step, setStep] = useState<InstallStep>('lang');
Expand Down Expand Up @@ -243,9 +260,16 @@ export function useInstaller(): UseInstallerReturn {
setInstallationCompleted(false);
setCanConfirmProgress(false);
try {
const detectedInstall = await refreshExistingInstall();
const validated = await invoke<InstallPathValidation>('validate_install_path', {
path: options.installPath,
});
const isUpdate = Boolean(
detectedInstall.detected
&& detectedInstall.mainBinaryPresent
&& detectedInstall.installLocation
&& isSameInstallPath(detectedInstall.installLocation, validated.installPath),
);
const effectiveOptions = {
...options,
installPath: validated.installPath,
Expand All @@ -257,6 +281,13 @@ export function useInstaller(): UseInstallerReturn {
setProgress({ step: 'prepare', percent: 0, message: '' });
await invoke('start_installation', { options: effectiveOptions });
setInstallationCompleted(true);
if (isUpdate) {
if (options.launchAfterInstall) {
await invoke('launch_application', { installPath: effectiveOptions.installPath });
}
await invoke('close_installer');
return;
}
setStep('model');
try {
const info = await readExistingInstall();
Expand All @@ -270,7 +301,7 @@ export function useInstaller(): UseInstallerReturn {
} finally {
setIsInstalling(false);
}
}, [options, readExistingInstall]);
}, [options, readExistingInstall, refreshExistingInstall]);

const confirmProgress = useCallback(() => {
if (!canConfirmProgress) return;
Expand Down
Loading