installer-rp: delete partial artifacts on failure; bump to 0.3.6

Resume previously skipped any track/cover whose file merely existed, so a
partially written NN.ogg or cover_NN.png from a failed download/convert
could be mistaken for a finished file on the next attempt. Now the
failure path removes the expected output before bailing, so only fully
completed artifacts are skipped on resume.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 16:30:45 +09:00
parent fe0d2f75e3
commit 812026df93
2 changed files with 11 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "minecraft-music-quiz-installer", "name": "minecraft-music-quiz-installer",
"version": "0.3.5", "version": "0.3.6",
"description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트", "description": "마인크래프트 음악퀴즈 간편설치기 + 관리 사이트",
"main": "dist/installer/main.js", "main": "dist/installer/main.js",
"scripts": { "scripts": {

View File

@@ -365,6 +365,9 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
async function tryDownloadTrack(i: number, emitErrorProgress: boolean): Promise<boolean> { async function tryDownloadTrack(i: number, emitErrorProgress: boolean): Promise<boolean> {
const entry = musicList[i] const entry = musicList[i]
const idx = i + 1 const idx = i + 1
// 최종 산출물 경로. 실패 시 부분 생성된 파일을 지워, 다음 재시도(이어받기)에서
// 완성본으로 오인해 건너뛰는 일을 막는다.
const expectedOut = path.join(musicDir, String(idx).padStart(2, '0') + '.ogg')
sendLog(t('log.musicTrackStart', { idx })) sendLog(t('log.musicTrackStart', { idx }))
sendProgress({ phase: 'item', kind: 'music', index: idx, total: musicTotal, percent: 0, status: 'running' }) sendProgress({ phase: 'item', kind: 'music', index: idx, total: musicTotal, percent: 0, status: 'running' })
let child: ChildProcess | null = null let child: ChildProcess | null = null
@@ -394,6 +397,8 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
return true return true
} catch (err) { } catch (err) {
if (child) state.activeChildren.delete(child) if (child) state.activeChildren.delete(child)
// 부분 생성된 .ogg 를 제거(이어받기 시 완성본 오인 방지).
await fsp.rm(expectedOut, { force: true }).catch(() => {})
if (state.cancelRequested) { if (state.cancelRequested) {
sendProgress({ phase: 'item', kind: 'music', index: idx, total: musicTotal, percent: 0, status: 'error', message: t('progress.cancelled') }) sendProgress({ phase: 'item', kind: 'music', index: idx, total: musicTotal, percent: 0, status: 'error', message: t('progress.cancelled') })
return false return false
@@ -483,15 +488,19 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
try { try {
buf = await downloadImage(entry.url) buf = await downloadImage(entry.url)
} catch (err) { } catch (err) {
// 부분 생성됐을 수 있는 커버 파일 제거(이어받기 시 완성본 오인 방지).
await fsp.rm(coverPath, { force: true }).catch(() => {})
sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 0, status: 'error', message: (err as Error).message }) sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 0, status: 'error', message: (err as Error).message })
throw new Error(t('errors.imageDownloadFailed', { idx, message: (err as Error).message })) throw new Error(t('errors.imageDownloadFailed', { idx, message: (err as Error).message }))
} }
throwIfCancelled() throwIfCancelled()
sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 60, status: 'running' }) sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 60, status: 'running' })
const outPath = path.join(paintingDir, coverFileName(idx)) const outPath = coverPath
try { try {
await normalizeToCover(buf, outPath) await normalizeToCover(buf, outPath)
} catch (err) { } catch (err) {
// 변환 중 부분 생성된 PNG 제거(이어받기 시 완성본 오인 방지).
await fsp.rm(coverPath, { force: true }).catch(() => {})
sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 0, status: 'error', message: (err as Error).message }) sendProgress({ phase: 'item', kind: 'image', index: idx, total: imageTotal, percent: 0, status: 'error', message: (err as Error).message })
throw new Error(t('errors.imageNormalizeFailed', { idx, message: (err as Error).message })) throw new Error(t('errors.imageNormalizeFailed', { idx, message: (err as Error).message }))
} }