feat(audit): Batch 23 — settings key renames with migration (CC1) + config.ts (CC11)
CC1: customCommandEnabled→enableCustomCommands, downloadArchive→ useDownloadArchive, clipboardWatch→watchClipboard, sidebarCollapsed→ isSidebarCollapsed, hardwareAcceleration→useHardwareAcceleration, videoDir/audioDir→videoFolder/audioFolder (+ chooseDir/clearDir store actions → chooseFolder/clearFolder). Rename map in settingsMigration.ts (pure, unit-tested incl. pre-rename backup fixture), applied as an idempotent on-disk shim at store open and on backup import so old settings.json and old backups both keep working. CC11: update host/owner/repo + release API moved to src/main/config.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+29
-11
@@ -1,6 +1,7 @@
|
||||
import { app, safeStorage } from 'electron'
|
||||
import Store from 'electron-store'
|
||||
import { isSafeFilenameTemplate, isSafeOutputDir } from './validation'
|
||||
import { RENAMED_SETTINGS_KEYS } from './settingsMigration'
|
||||
import { logger } from './logger'
|
||||
import {
|
||||
AUDIO_FORMATS,
|
||||
@@ -91,11 +92,28 @@ function sanitizeOptions(input: unknown): DownloadOptions {
|
||||
}
|
||||
}
|
||||
|
||||
// Constructed lazily — electron-store needs app paths, which exist only after
|
||||
// the app is ready (all callers run post-ready).
|
||||
// Constructed lazily — electron-store needs app paths. (`userData` resolves
|
||||
// pre-ready too, which the W15 hardware-acceleration gate in index.ts relies on.)
|
||||
let store: Store<Settings> | null = null
|
||||
function getStore(): Store<Settings> {
|
||||
if (!store) store = new Store<Settings>({ name: 'settings', defaults: DEFAULTS })
|
||||
if (!store) {
|
||||
store = new Store<Settings>({ name: 'settings', defaults: DEFAULTS })
|
||||
// CC1 key-rename migration: move any legacy key still on disk to its new
|
||||
// name, once. `has()` reports defaults as present, so probe the raw store
|
||||
// object instead; idempotent because the old key is deleted afterwards.
|
||||
const raw = store.store as unknown as Record<string, unknown>
|
||||
const legacy = RENAMED_SETTINGS_KEYS.filter(([oldKey]) => oldKey in raw)
|
||||
if (legacy.length > 0) {
|
||||
const untyped = store as unknown as {
|
||||
set(k: string, v: unknown): void
|
||||
delete(k: string): void
|
||||
}
|
||||
for (const [oldKey, newKey] of legacy) {
|
||||
untyped.set(newKey, raw[oldKey])
|
||||
untyped.delete(oldKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
@@ -185,7 +203,7 @@ export function getSettings(): Settings {
|
||||
// `set`, so only write when something actually changed — otherwise this churns
|
||||
// the settings file on every read. (audit P1)
|
||||
const cur = s.store
|
||||
// videoDir/audioDir are intentionally left blank by default — an empty value
|
||||
// videoFolder/audioFolder are intentionally left blank by default — an empty value
|
||||
// routes that kind into Documents\Video / Documents\Audio (see buildCommand).
|
||||
// Only an explicit user choice (Settings → folders) overrides that.
|
||||
// Migrate settings files that predate downloadOptions (or hold a partial one),
|
||||
@@ -278,18 +296,18 @@ function applySettings(s: Store<Settings>, partial: Partial<Settings>): void {
|
||||
// well-formed 24h HH:MM is ever stored (L51).
|
||||
if (isValidSyncTime(value)) s.set('syncTime', value)
|
||||
break
|
||||
case 'clipboardWatch':
|
||||
case 'watchClipboard':
|
||||
case 'useAria2c':
|
||||
case 'restrictFilenames':
|
||||
case 'downloadArchive':
|
||||
case 'useDownloadArchive':
|
||||
case 'autoUpdateYtdlp':
|
||||
case 'customCommandEnabled':
|
||||
case 'enableCustomCommands':
|
||||
case 'notifyOnComplete':
|
||||
case 'autoDownloadNew':
|
||||
case 'hasCompletedOnboarding':
|
||||
case 'minimizeToTray':
|
||||
case 'sidebarCollapsed':
|
||||
case 'hardwareAcceleration':
|
||||
case 'isSidebarCollapsed':
|
||||
case 'useHardwareAcceleration':
|
||||
if (typeof value === 'boolean') s.set(key, value)
|
||||
break
|
||||
case 'launchAtStartup':
|
||||
@@ -325,8 +343,8 @@ function applySettings(s: Store<Settings>, partial: Partial<Settings>): void {
|
||||
// group (it merges field changes locally before calling setSettings).
|
||||
s.set('downloadOptions', sanitizeOptions(value))
|
||||
break
|
||||
case 'videoDir':
|
||||
case 'audioDir':
|
||||
case 'videoFolder':
|
||||
case 'audioFolder':
|
||||
// An empty string is allowed — it clears the override and restores the
|
||||
// Documents\Video / Documents\Audio default for that kind.
|
||||
if (typeof value === 'string' && (value.trim() === '' || isSafeOutputDir(value.trim()))) {
|
||||
|
||||
Reference in New Issue
Block a user