installer: clean up installer-extracted map when switching to participant
When the user installs as single (skipMap=false) and then navigates back to choose participant (skipMap=true), the previously-extracted map files in .mc_custom/saves/ would remain because skipMap=true only skipped the download. The final participant install state was therefore inconsistent with the chosen role. Track the top-level entries that downloadMapZip extracts via a marker file (.musicquiz-installer-map.json) inside saves/. On participant install (skipMap=true) or before a re-download, only the entries listed in the marker are removed, so user-created worlds are preserved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -395,14 +395,63 @@ async function downloadServerZip(pack: PackDefinition, targetDir: string): Promi
|
||||
await downloadAndExtractZip(url, t('log.labelServerFile'), targetDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* 설치러가 saves/ 에 풀어놓은 최상위 폴더(또는 파일) 목록을 기록하는 마커 파일.
|
||||
* 재설치 시 잔여물을 안전하게 정리하고, 싱글→참가자 전환 시에도
|
||||
* 사용자가 직접 만든 월드는 보존한 채 설치러가 만든 맵만 제거하기 위함이다.
|
||||
*/
|
||||
const INSTALLER_MAP_MARKER = '.musicquiz-installer-map.json'
|
||||
|
||||
async function readInstallerMapMarker(customRoot: string): Promise<string[]> {
|
||||
const markerPath = path.join(customRoot, 'saves', INSTALLER_MAP_MARKER)
|
||||
try {
|
||||
const raw = await fsp.readFile(markerPath, 'utf8')
|
||||
const data = JSON.parse(raw) as { entries?: unknown }
|
||||
if (Array.isArray(data.entries)) {
|
||||
return data.entries.filter((s): s is string => typeof s === 'string')
|
||||
}
|
||||
} catch {
|
||||
// 마커가 없거나 파싱 실패 — 빈 목록 반환
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
async function writeInstallerMapMarker(customRoot: string, entries: string[]): Promise<void> {
|
||||
const savesDir = path.join(customRoot, 'saves')
|
||||
await fsp.mkdir(savesDir, { recursive: true })
|
||||
const markerPath = path.join(savesDir, INSTALLER_MAP_MARKER)
|
||||
await fsp.writeFile(markerPath, JSON.stringify({ entries }, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
async function cleanupInstallerMap(customRoot: string): Promise<void> {
|
||||
const savesDir = path.join(customRoot, 'saves')
|
||||
const entries = await readInstallerMapMarker(customRoot)
|
||||
if (entries.length === 0) return
|
||||
sendLog(t('log.cleanupInstallerMap', { count: entries.length }))
|
||||
for (const name of entries) {
|
||||
// 안전장치: 경로 구분자/상대경로 토큰이 섞인 항목은 무시
|
||||
if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') continue
|
||||
const target = path.join(savesDir, name)
|
||||
await fsp.rm(target, { recursive: true, force: true })
|
||||
}
|
||||
await fsp.rm(path.join(savesDir, INSTALLER_MAP_MARKER), { force: true })
|
||||
}
|
||||
|
||||
async function downloadMapZip(pack: PackDefinition, customRoot: string): Promise<void> {
|
||||
if (!pack.mapPath) {
|
||||
sendLog(t('log.skipMapZip'))
|
||||
return
|
||||
}
|
||||
// 이전 설치러가 풀어놓은 맵이 남아 있으면 먼저 제거 (다른 팩/재설치 시 잔여물 방지).
|
||||
await cleanupInstallerMap(customRoot)
|
||||
const url = resolveManifestRelative(pack.mapPath, 'maps')
|
||||
const savesDir = path.join(customRoot, 'saves')
|
||||
await fsp.mkdir(savesDir, { recursive: true })
|
||||
const before = new Set<string>(await fsp.readdir(savesDir).catch(() => [] as string[]))
|
||||
await downloadAndExtractZip(url, t('log.labelMap'), savesDir)
|
||||
const after = await fsp.readdir(savesDir).catch(() => [] as string[])
|
||||
const added = after.filter((name) => !before.has(name) && name !== INSTALLER_MAP_MARKER)
|
||||
await writeInstallerMapMarker(customRoot, added)
|
||||
}
|
||||
|
||||
async function downloadModsFolder(pack: PackDefinition, customRoot: string): Promise<void> {
|
||||
@@ -1048,6 +1097,9 @@ ipcMain.handle('client:install', async (_event, payload: ClientInstallPayload) =
|
||||
await downloadResourcepackZip(pack.pack, customRoot)
|
||||
|
||||
if (payload.skipMap) {
|
||||
// 참가자 모드: 이전 설치 흐름에서 설치러가 풀어둔 맵이 있다면 제거한다.
|
||||
// 사용자가 직접 만든 월드는 마커에 포함되지 않으므로 그대로 보존된다.
|
||||
await cleanupInstallerMap(customRoot)
|
||||
sendLog(t('log.skipMapZip'))
|
||||
} else {
|
||||
await downloadMapZip(pack.pack, customRoot)
|
||||
|
||||
Reference in New Issue
Block a user