fix(runtime): stage runtime installs strictly, time out only on stalls, and make reinstall actually reinstall

This commit is contained in:
Yun Chan 2026-09-28 00:53:44 +09:00
parent ba0dbbd813
commit 524d390bc2
10 changed files with 1160 additions and 238 deletions

View file

@ -3,10 +3,11 @@
import { ipcMain } from 'electron'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { ipcSuccess, ipcError, ErrorCode, D3ROError } from '@d3ro/core/errors'
import { ipcSuccess, ipcError, ErrorCode, D3ROError, type IPCResult } from '@d3ro/core/errors'
import { getLocalSTTService } from '../services/LocalSTTService'
import { getRuntimeProvisioner } from '../services/RuntimeProvisioner'
import type { RuntimeComponent } from '../services/RuntimeProvisioner'
import { isRuntimeComponent } from '../services/runtime/runtime-index'
import { getSTTManager } from '../services/stt/STTManager'
import { configGet, configSet } from '../services/ConfigService'
import { getMainWindow } from '../windows/WindowManager'
@ -27,8 +28,18 @@ function safeSendToRenderer(channel: string, data: unknown): void {
}
}
/**
* 던져진 오류를 IPC 오류로 바꾼다. D3ROError 면 그 코드를 보존하고, 아니면 fallback 코드를 쓴다.
* (예전 런타임 핸들러는 `ipcError(error, code)` 로 인자 순서가 뒤바뀌어 code 자리에 Error 객체가,
* message 자리에 숫자 코드가 들어갔다.)
*/
function toIpcError<T>(error: unknown, fallback: ErrorCode): IPCResult<T> {
if (error instanceof D3ROError) return ipcError(error.code, error.message)
return ipcError(fallback, error instanceof Error ? error.message : String(error))
}
export function registerSTTHandlers(): void {
// 로컬 AI 런타임(진/ffmpeg) 진행률 — 필요할 때 자동으로 내려받는다
// 로컬 AI 런타임(엔진/ffmpeg) 진행률 — 필요할 때 자동으로 내려받는다
getRuntimeProvisioner().on('progress', (payload) => {
safeSendToRenderer(IPC_CHANNELS.RUNTIME.PROGRESS, payload)
})
@ -37,22 +48,30 @@ export function registerSTTHandlers(): void {
try {
return ipcSuccess(getRuntimeProvisioner().getStatus())
} catch (error) {
return ipcError(error, ErrorCode.ConfigReadFailed)
return toIpcError(error, ErrorCode.ConfigReadFailed)
}
})
ipcMain.handle(IPC_CHANNELS.RUNTIME.ENSURE, async (_event, params: { component: RuntimeComponent }) => {
try {
const component = params?.component
if (component !== 'sidecar' && component !== 'ffmpeg') {
throw new D3ROError(ErrorCode.ConfigInvalidValue, `알 수 없는 런타임 구성 요소: ${String(component)}`)
// 렌더러의 이 호출은 사용자가 누른 "내려받기 / 다시 설치" 버튼 하나뿐이다. 이미 설치돼
// 있는데 누른 경우는 손상(백신 격리 등)을 고치려는 것이므로 기본값으로 새로 받아
// 교체한다 — 존재·버전 확인만으로는 손상을 알 수 없어, 예전에는 아무 일도 하지 않았다.
// 설치돼 있으면 건너뛰어야 하는 호출처는 `force: false` 를 넘긴다.
ipcMain.handle(
IPC_CHANNELS.RUNTIME.ENSURE,
async (_event, params: { component: RuntimeComponent; force?: boolean }) => {
try {
const component = params?.component
if (!isRuntimeComponent(component)) {
throw new D3ROError(ErrorCode.ConfigInvalidValue, `알 수 없는 런타임 구성 요소: ${String(component)}`)
}
const force = params?.force !== false
const binaryPath = await getRuntimeProvisioner().ensure(component, { force })
return ipcSuccess({ component, binaryPath })
} catch (error) {
return toIpcError(error, ErrorCode.STTSidecarSpawnFailed)
}
const binaryPath = await getRuntimeProvisioner().ensure(component)
return ipcSuccess({ component, binaryPath })
} catch (error) {
return ipcError(error, ErrorCode.STTSidecarSpawnFailed)
}
})
},
)
getLocalSTTService().on('download-progress', (payload) => {
safeSendToRenderer(IPC_CHANNELS.STT.DOWNLOAD_PROGRESS, payload)
})