refactor(db): lift db handle to long-lived singleton + close in shutdown

appDb module-scope let, getAppDb() exported getter, opened once in
app.whenReady with migrator run inline, closed in shutdownCleanup before
debugLog flush so WAL checkpoint completes cleanly. Unlocks IPC handlers
to read/write SQLite without per-call open/close.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 23:43:01 +02:00
parent 5465847c87
commit c08b6fef7d

View File

@ -9,6 +9,7 @@ import { compareUpdateVersions, isNewerUpdateVersion, normalizeUpdateVersion } f
import { writeFileAtomicSync } from './main/infra/fs-atomic'; import { writeFileAtomicSync } from './main/infra/fs-atomic';
import { parseDuration, formatDuration, formatDurationDashed } from './main/infra/duration'; import { parseDuration, formatDuration, formatDurationDashed } from './main/infra/duration';
import { tBackend as tBackendCore, type BackendMessageKey } from './main/domain/i18n-backend'; import { tBackend as tBackendCore, type BackendMessageKey } from './main/domain/i18n-backend';
import type { DbHandle } from './main/infra/db';
import { import {
normalizeLogin, normalizeLogin,
normalizeAutoRecordPollSeconds, normalizeAutoRecordPollSeconds,
@ -7206,30 +7207,32 @@ ipcMain.handle('save-video-dialog', async (_, defaultName: string) => {
// ========================================== // ==========================================
// APP LIFECYCLE // APP LIFECYCLE
// ========================================== // ==========================================
// Long-lived SQLite-Handle (Plan 04b+ Voraussetzung). Wird in app.whenReady
// geoeffnet, in shutdownCleanup geschlossen. getAppDb() returnt null wenn
// Open fehlgeschlagen ist (Native-Build-Probleme) — Caller mussen das pruefen.
let appDb: DbHandle | null = null;
export function getAppDb(): DbHandle | null { return appDb; }
app.whenReady().then(() => { app.whenReady().then(() => {
app.setAppUserModelId('com.twitch.vodmanager'); app.setAppUserModelId('com.twitch.vodmanager');
refreshBundledToolPaths(true); refreshBundledToolPaths(true);
startMetadataCacheCleanup(); startMetadataCacheCleanup();
startDebugLogFlushTimer(); startDebugLogFlushTimer();
// SQLite-Shadow-Migration (Plan 02 / v5.0.0-alpha.1). Idempotent + fail-soft — // SQLite-Open + Shadow-Migration. Long-lived handle in appDb (siehe oben).
// bei Fehler bleibt JSON der Master. Lazy require, damit Native-Build-Fehler // Lazy require, damit Native-Build-Fehler den App-Start nicht verhindern.
// den App-Start nicht verhindern.
try { try {
const { openDatabase } = require('./main/infra/db'); const { openDatabase } = require('./main/infra/db');
const { migrateJsonToSqlite } = require('./main/domain/migrator'); const { migrateJsonToSqlite } = require('./main/domain/migrator');
const dbPath = path.join(APPDATA_DIR, 'app.db'); const dbPath = path.join(APPDATA_DIR, 'app.db');
const db = openDatabase(dbPath); appDb = openDatabase(dbPath);
try { const result = migrateJsonToSqlite({ db: appDb, appDataDir: APPDATA_DIR });
const result = migrateJsonToSqlite({ db, appDataDir: APPDATA_DIR }); appendDebugLog('sqlite-migrator', result);
appendDebugLog('sqlite-migrator', result);
} finally {
db.close();
}
} catch (e) { } catch (e) {
appendDebugLog('sqlite-migrator-failed', { appendDebugLog('sqlite-open-failed', {
error: e instanceof Error ? e.message : String(e), error: e instanceof Error ? e.message : String(e),
}); });
appDb = null;
} }
restartAutoRecordPoller(); restartAutoRecordPoller();
@ -7291,6 +7294,13 @@ function shutdownCleanup(reason: 'window-all-closed' | 'before-quit'): void {
saveConfig(config); saveConfig(config);
flushQueueSave(); flushQueueSave();
// SQLite-Handle schliessen, falls geoeffnet — WAL-Checkpoint passiert beim
// close, sodass beim naechsten Start keine .wal/.shm orphans bleiben.
if (appDb) {
try { appDb.close(); } catch { /* already closed */ }
appDb = null;
}
// Flush debug log AFTER persisting state so any errors saving config / // Flush debug log AFTER persisting state so any errors saving config /
// queue land in the log before the timer is gone. // queue land in the log before the timer is gone.
stopDebugLogFlushTimer(true); stopDebugLogFlushTimer(true);