Skip to content

Commit 4ccfc20

Browse files
committed
feat(desktop): 支持自定义 output 目录并迁移现有数据
- 支持在桌面端查看、选择和恢复默认 output 目录 - 安装器记录待应用目录,并在应用启动时自动迁移数据 - 后端支持 output 目录覆盖,补充桌面端与后端相关测试
1 parent 25f96ec commit 4ccfc20

8 files changed

Lines changed: 1146 additions & 51 deletions

File tree

desktop/scripts/installer-custom.nsh

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
; This file is included for both installer and uninstaller builds.
22
; Guard installer-only pages/functions to avoid "function not referenced" warnings
33
; when electron-builder compiles the standalone uninstaller.
4+
!define /ifndef WDA_DEFAULT_SETTINGS_PATH "$APPDATA\${APP_FILENAME}\desktop-settings.json"
5+
!define /ifndef WDA_DEFAULT_OUTPUT_DIR "$APPDATA\${APP_FILENAME}\output"
6+
!ifdef APP_PRODUCT_FILENAME
7+
!define /ifndef WDA_PRODUCT_SETTINGS_PATH "$APPDATA\${APP_PRODUCT_FILENAME}\desktop-settings.json"
8+
!define /ifndef WDA_PRODUCT_OUTPUT_DIR "$APPDATA\${APP_PRODUCT_FILENAME}\output"
9+
!else
10+
!define /ifndef WDA_PRODUCT_SETTINGS_PATH ""
11+
!define /ifndef WDA_PRODUCT_OUTPUT_DIR ""
12+
!endif
13+
!ifdef APP_PACKAGE_NAME
14+
!define /ifndef WDA_PACKAGE_SETTINGS_PATH "$APPDATA\${APP_PACKAGE_NAME}\desktop-settings.json"
15+
!define /ifndef WDA_PACKAGE_OUTPUT_DIR "$APPDATA\${APP_PACKAGE_NAME}\output"
16+
!else
17+
!define /ifndef WDA_PACKAGE_SETTINGS_PATH ""
18+
!define /ifndef WDA_PACKAGE_OUTPUT_DIR ""
19+
!endif
420
!ifndef BUILD_UNINSTALLER
521
!include nsDialogs.nsh
622
!include LogicLib.nsh
@@ -13,6 +29,10 @@
1329
!define /ifndef MUI_DIRECTORYPAGE_TEXT_DESTINATION "安装位置:"
1430

1531
Var WDA_InstallDirPage
32+
Var WDA_OutputDirPage
33+
Var WDA_OutputDirInput
34+
Var WDA_OutputDirBrowseButton
35+
Var WDA_SelectedOutputDir
1636

1737
!macro customInit
1838
; Safety: older versions created an `output` junction inside the install directory that points to the
@@ -22,17 +42,10 @@ Var WDA_InstallDirPage
2242
!macroend
2343

2444
!macro customInstall
25-
; Provide a safe, non-junction way for users to locate the real per-user output directory.
26-
; The actual data is NOT stored inside $INSTDIR (it is wiped on update/reinstall).
27-
; `open-output.cmd` uses %APPDATA% so it works for the current user.
28-
FileOpen $0 "$INSTDIR\output-location.txt" w
29-
FileWrite $0 "WeChatDataAnalysis output folder (per user):$\r$\n%APPDATA%\\${APP_PACKAGE_NAME}\\output$\r$\n"
30-
FileClose $0
31-
32-
FileOpen $1 "$INSTDIR\open-output.cmd" w
33-
; NSIS escaping: use $\" to output a literal quote character into the .cmd file.
34-
FileWrite $1 "@echo off$\r$\nexplorer $\"%APPDATA%\\${APP_PACKAGE_NAME}\\output$\"$\r$\n"
35-
FileClose $1
45+
${If} $WDA_SelectedOutputDir == ""
46+
Call WDA_InitOutputDirSelection
47+
${EndIf}
48+
Call WDA_WritePendingOutputDirSetting
3649
!macroend
3750

3851
Function WDA_RemoveLegacyOutputLink
@@ -47,9 +60,27 @@ FunctionEnd
4760
; the final install location (includes the app sub-folder).
4861
!ifdef allowToChangeInstallationDirectory
4962
Page custom WDA_InstallDirPageCreate WDA_InstallDirPageLeave
63+
Page custom WDA_OutputDirPageCreate WDA_OutputDirPageLeave
5064
!endif
5165
!macroend
5266

67+
Function WDA_InitOutputDirSelection
68+
StrCpy $WDA_SelectedOutputDir "${WDA_DEFAULT_OUTPUT_DIR}"
69+
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command "& { param([string] $$defaultSettingsPath, [string] $$defaultOutputPath, [string] $$legacySettingsPath1, [string] $$legacySettingsPath2) $$candidates = @($$defaultSettingsPath, $$legacySettingsPath1, $$legacySettingsPath2) | Where-Object { -not [string]::IsNullOrWhiteSpace($$_) } | Select-Object -Unique; $$settingsPath = $$defaultSettingsPath; foreach ($$candidate in $$candidates) { if (Test-Path -LiteralPath $$candidate) { $$settingsPath = $$candidate; break } }; $$result = $$defaultOutputPath; if (Test-Path -LiteralPath $$settingsPath) { try { $$json = Get-Content -LiteralPath $$settingsPath -Raw | ConvertFrom-Json; $$value = [string] $$json.pendingOutputDir; if ([string]::IsNullOrWhiteSpace($$value)) { $$value = [string] $$json.outputDir }; if ($$value -eq '''') { $$result = $$defaultOutputPath } elseif (-not [string]::IsNullOrWhiteSpace($$value)) { $$result = $$value } } catch {} }; [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; [Console]::Write($$result) }" "${WDA_DEFAULT_SETTINGS_PATH}" "${WDA_DEFAULT_OUTPUT_DIR}" "${WDA_PRODUCT_SETTINGS_PATH}" "${WDA_PACKAGE_SETTINGS_PATH}"'
70+
Pop $0
71+
Pop $1
72+
${If} $0 == "0"
73+
${AndIf} $1 != ""
74+
StrCpy $WDA_SelectedOutputDir "$1"
75+
${EndIf}
76+
FunctionEnd
77+
78+
Function WDA_WritePendingOutputDirSetting
79+
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command "& { param([string] $$defaultSettingsPath, [string] $$defaultOutputPath, [string] $$selectedOutputPath, [string] $$legacySettingsPath1, [string] $$legacySettingsPath2) $$candidates = @($$defaultSettingsPath, $$legacySettingsPath1, $$legacySettingsPath2) | Where-Object { -not [string]::IsNullOrWhiteSpace($$_) } | Select-Object -Unique; $$sourceSettingsPath = $$defaultSettingsPath; foreach ($$candidate in $$candidates) { if (Test-Path -LiteralPath $$candidate) { $$sourceSettingsPath = $$candidate; break } }; if ([string]::IsNullOrWhiteSpace($$selectedOutputPath)) { $$selectedOutputPath = $$defaultOutputPath }; $$pending = if ([string]::Equals($$selectedOutputPath, $$defaultOutputPath, [System.StringComparison]::OrdinalIgnoreCase)) { '''' } else { $$selectedOutputPath }; $$obj = @{}; if (Test-Path -LiteralPath $$sourceSettingsPath) { try { $$existing = Get-Content -LiteralPath $$sourceSettingsPath -Raw | ConvertFrom-Json; if ($$null -ne $$existing) { $$existing.PSObject.Properties | ForEach-Object { $$obj[$$_.Name] = $$_.Value } } } catch {} }; $$obj[''pendingOutputDir''] = $$pending; $$dir = Split-Path -Parent $$defaultSettingsPath; New-Item -ItemType Directory -Force -Path $$dir | Out-Null; $$json = [PSCustomObject] $$obj | ConvertTo-Json -Depth 10; Set-Content -LiteralPath $$defaultSettingsPath -Value $$json -Encoding UTF8 }" "${WDA_DEFAULT_SETTINGS_PATH}" "${WDA_DEFAULT_OUTPUT_DIR}" "$WDA_SelectedOutputDir" "${WDA_PRODUCT_SETTINGS_PATH}" "${WDA_PACKAGE_SETTINGS_PATH}"'
80+
Pop $0
81+
Pop $1
82+
FunctionEnd
83+
5384
Function WDA_EnsureAppSubDir
5485
; Normalize $INSTDIR to always end with "\${APP_FILENAME}" (avoid cluttering a parent folder).
5586
StrCpy $0 "$INSTDIR"
@@ -105,6 +136,48 @@ FunctionEnd
105136
Function WDA_InstallDirPageLeave
106137
FunctionEnd
107138

139+
Function WDA_OutputDirBrowse
140+
nsDialogs::SelectFolderDialog "选择 output 目录" "$WDA_SelectedOutputDir"
141+
Pop $0
142+
${If} $0 != error
143+
StrCpy $WDA_SelectedOutputDir "$0"
144+
${NSD_SetText} $WDA_OutputDirInput "$0"
145+
${EndIf}
146+
FunctionEnd
147+
148+
Function WDA_OutputDirPageCreate
149+
Call WDA_InitOutputDirSelection
150+
151+
nsDialogs::Create 1018
152+
Pop $WDA_OutputDirPage
153+
154+
${If} $WDA_OutputDirPage == error
155+
Abort
156+
${EndIf}
157+
158+
${NSD_CreateLabel} 0u 0u 100% 24u "请选择 output 目录(保存解密数据库、导出内容、缓存、日志等)。"
159+
Pop $0
160+
161+
${NSD_CreateText} 0u 28u 78% 12u "$WDA_SelectedOutputDir"
162+
Pop $WDA_OutputDirInput
163+
164+
${NSD_CreateButton} 82% 27u 18% 14u "浏览..."
165+
Pop $WDA_OutputDirBrowseButton
166+
${NSD_OnClick} $WDA_OutputDirBrowseButton WDA_OutputDirBrowse
167+
168+
${NSD_CreateLabel} 0u 52u 100% 28u "安装器只记录你的选择;真正的数据迁移会在首次启动应用时执行。若目标目录已有内容,应用会阻止切换并提示处理。"
169+
Pop $0
170+
171+
nsDialogs::Show
172+
FunctionEnd
173+
174+
Function WDA_OutputDirPageLeave
175+
${NSD_GetText} $WDA_OutputDirInput $WDA_SelectedOutputDir
176+
${If} $WDA_SelectedOutputDir == ""
177+
StrCpy $WDA_SelectedOutputDir "${WDA_DEFAULT_OUTPUT_DIR}"
178+
${EndIf}
179+
FunctionEnd
180+
108181
!endif
109182

110183
!ifdef BUILD_UNINSTALLER
@@ -177,6 +250,12 @@ FunctionEnd
177250
RMDir /r "$APPDATA\${APP_PACKAGE_NAME}"
178251
!endif
179252

253+
IfFileExists "$INSTDIR\output-location.path" 0 WDA_SkipCustomOutputDelete
254+
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -Command "& { param([string] $$pathFile, [string] $$defaultPath1, [string] $$defaultPath2, [string] $$defaultPath3) if (Test-Path -LiteralPath $$pathFile) { $$target = (Get-Content -LiteralPath $$pathFile -Raw).Trim(); $$defaults = @($$defaultPath1, $$defaultPath2, $$defaultPath3) | Where-Object { -not [string]::IsNullOrWhiteSpace($$_) }; $$isDefault = $$false; foreach ($$defaultPath in $$defaults) { if ([string]::Equals($$target, $$defaultPath, [System.StringComparison]::OrdinalIgnoreCase)) { $$isDefault = $$true; break } }; if (-not $$isDefault -and -not [string]::IsNullOrWhiteSpace($$target) -and (Test-Path -LiteralPath $$target)) { Remove-Item -LiteralPath $$target -Recurse -Force -ErrorAction SilentlyContinue } } }" "$INSTDIR\output-location.path" "${WDA_DEFAULT_OUTPUT_DIR}" "${WDA_PRODUCT_OUTPUT_DIR}" "${WDA_PACKAGE_OUTPUT_DIR}"'
255+
Pop $0
256+
Pop $1
257+
WDA_SkipCustomOutputDelete:
258+
180259
${if} $installMode == "all"
181260
SetShellVarContext all
182261
${endif}

0 commit comments

Comments
 (0)