mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
feat: make startup warmups configurable with low-power mode
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: TASK-74
|
||||
title: 'Startup warmups: configurable warmup vs defer with low-power mode'
|
||||
status: In Progress
|
||||
assignee: []
|
||||
created_date: '2026-02-27 21:05'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- src/types.ts
|
||||
- src/config/definitions/defaults-core.ts
|
||||
- src/config/definitions/options-core.ts
|
||||
- src/config/definitions/template-sections.ts
|
||||
- src/config/resolve/core-domains.ts
|
||||
- src/main/runtime/startup-warmups.ts
|
||||
- src/main/runtime/startup-warmups-main-deps.ts
|
||||
- src/main/runtime/composers/mpv-runtime-composer.ts
|
||||
- src/main.ts
|
||||
- src/config/config.test.ts
|
||||
- src/main/runtime/startup-warmups.test.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Add startup warmup controls to allow per-integration warmup or deferred first-use loading.
|
||||
|
||||
Scope:
|
||||
- New config section `startupWarmups` with toggles for `mecab`, `yomitanExtension`, `subtitleDictionaries`, and `jellyfinRemoteSession`.
|
||||
- New `startupWarmups.lowPowerMode` policy: defer everything except Yomitan extension.
|
||||
- Keep default behavior as full warmup.
|
||||
- Ensure deferred integrations lazy-load on first real usage path.
|
||||
- Add test coverage for config parsing/defaults and warmup scheduling behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Implemented:
|
||||
- Added `startupWarmups` to config types/defaults/options/template/resolve.
|
||||
- Warmup scheduler now uses per-integration gating functions.
|
||||
- Low-power mode now defers MeCab, subtitle dictionaries, and Jellyfin remote session warmups while still warming Yomitan extension.
|
||||
- Tokenization path guarantees lazy first-use init for deferred dependencies (Yomitan extension, MeCab when missing, subtitle dictionaries).
|
||||
- Added/updated tests across config and runtime warmup modules.
|
||||
|
||||
Validation:
|
||||
- `bun run test:config:src`
|
||||
- `bun run test:core:src`
|
||||
- `tsc --noEmit`
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -23,6 +23,11 @@ test('loads defaults when config is missing', () => {
|
||||
assert.equal(config.jellyfin.remoteControlAutoConnect, true);
|
||||
assert.equal(config.jellyfin.autoAnnounce, false);
|
||||
assert.equal(config.jellyfin.remoteControlDeviceName, 'SubMiner');
|
||||
assert.equal(config.startupWarmups.lowPowerMode, false);
|
||||
assert.equal(config.startupWarmups.mecab, true);
|
||||
assert.equal(config.startupWarmups.yomitanExtension, true);
|
||||
assert.equal(config.startupWarmups.subtitleDictionaries, true);
|
||||
assert.equal(config.startupWarmups.jellyfinRemoteSession, true);
|
||||
assert.equal(config.discordPresence.enabled, false);
|
||||
assert.equal(config.discordPresence.updateIntervalMs, 3_000);
|
||||
assert.equal(config.subtitleStyle.backgroundColor, 'rgb(30, 32, 48, 0.88)');
|
||||
@@ -294,6 +299,72 @@ test('parses jellyfin.enabled and remoteControlEnabled disabled combinations', (
|
||||
);
|
||||
});
|
||||
|
||||
test('parses startup warmup toggles and low-power mode', () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.jsonc'),
|
||||
`{
|
||||
"startupWarmups": {
|
||||
"lowPowerMode": true,
|
||||
"mecab": false,
|
||||
"yomitanExtension": true,
|
||||
"subtitleDictionaries": false,
|
||||
"jellyfinRemoteSession": false
|
||||
}
|
||||
}`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const service = new ConfigService(dir);
|
||||
const config = service.getConfig();
|
||||
assert.equal(config.startupWarmups.lowPowerMode, true);
|
||||
assert.equal(config.startupWarmups.mecab, false);
|
||||
assert.equal(config.startupWarmups.yomitanExtension, true);
|
||||
assert.equal(config.startupWarmups.subtitleDictionaries, false);
|
||||
assert.equal(config.startupWarmups.jellyfinRemoteSession, false);
|
||||
});
|
||||
|
||||
test('invalid startup warmup values warn and keep defaults', () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(dir, 'config.jsonc'),
|
||||
`{
|
||||
"startupWarmups": {
|
||||
"lowPowerMode": "yes",
|
||||
"mecab": 1,
|
||||
"yomitanExtension": null,
|
||||
"subtitleDictionaries": "no",
|
||||
"jellyfinRemoteSession": []
|
||||
}
|
||||
}`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const service = new ConfigService(dir);
|
||||
const config = service.getConfig();
|
||||
const warnings = service.getWarnings();
|
||||
|
||||
assert.equal(config.startupWarmups.lowPowerMode, DEFAULT_CONFIG.startupWarmups.lowPowerMode);
|
||||
assert.equal(config.startupWarmups.mecab, DEFAULT_CONFIG.startupWarmups.mecab);
|
||||
assert.equal(
|
||||
config.startupWarmups.yomitanExtension,
|
||||
DEFAULT_CONFIG.startupWarmups.yomitanExtension,
|
||||
);
|
||||
assert.equal(
|
||||
config.startupWarmups.subtitleDictionaries,
|
||||
DEFAULT_CONFIG.startupWarmups.subtitleDictionaries,
|
||||
);
|
||||
assert.equal(
|
||||
config.startupWarmups.jellyfinRemoteSession,
|
||||
DEFAULT_CONFIG.startupWarmups.jellyfinRemoteSession,
|
||||
);
|
||||
assert.ok(warnings.some((warning) => warning.path === 'startupWarmups.lowPowerMode'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'startupWarmups.mecab'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'startupWarmups.yomitanExtension'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'startupWarmups.subtitleDictionaries'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'startupWarmups.jellyfinRemoteSession'));
|
||||
});
|
||||
|
||||
test('parses discordPresence fields and warns for invalid types', () => {
|
||||
const dir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
@@ -1135,6 +1206,7 @@ test('template generator includes known keys', () => {
|
||||
assert.match(output, /"logging":/);
|
||||
assert.match(output, /"websocket":/);
|
||||
assert.match(output, /"discordPresence":/);
|
||||
assert.match(output, /"startupWarmups":/);
|
||||
assert.match(output, /"youtubeSubgen":/);
|
||||
assert.match(output, /"preserveLineBreaks": false/);
|
||||
assert.match(output, /"nPlusOne"\s*:\s*\{/);
|
||||
|
||||
@@ -27,6 +27,7 @@ const {
|
||||
shortcuts,
|
||||
secondarySub,
|
||||
subsync,
|
||||
startupWarmups,
|
||||
auto_start_overlay,
|
||||
} = CORE_DEFAULT_CONFIG;
|
||||
const { ankiConnect, jimaku, anilist, jellyfin, discordPresence, youtubeSubgen } =
|
||||
@@ -44,6 +45,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
shortcuts,
|
||||
secondarySub,
|
||||
subsync,
|
||||
startupWarmups,
|
||||
subtitleStyle,
|
||||
auto_start_overlay,
|
||||
jimaku,
|
||||
|
||||
@@ -10,6 +10,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
| 'shortcuts'
|
||||
| 'secondarySub'
|
||||
| 'subsync'
|
||||
| 'startupWarmups'
|
||||
| 'auto_start_overlay'
|
||||
> = {
|
||||
subtitlePosition: { yPercent: 10 },
|
||||
@@ -50,5 +51,12 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
ffsubsync_path: '',
|
||||
ffmpeg_path: '',
|
||||
},
|
||||
startupWarmups: {
|
||||
lowPowerMode: false,
|
||||
mecab: true,
|
||||
yomitanExtension: true,
|
||||
subtitleDictionaries: true,
|
||||
jellyfinRemoteSession: true,
|
||||
},
|
||||
auto_start_overlay: false,
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ test('config option registry includes critical paths and has unique entries', ()
|
||||
|
||||
for (const requiredPath of [
|
||||
'logging.level',
|
||||
'startupWarmups.lowPowerMode',
|
||||
'subtitleStyle.enableJlpt',
|
||||
'ankiConnect.enabled',
|
||||
'immersionTracking.enabled',
|
||||
@@ -31,6 +32,7 @@ test('config template sections include expected domains and unique keys', () =>
|
||||
const keys = CONFIG_TEMPLATE_SECTIONS.map((section) => section.key);
|
||||
const requiredKeys: (typeof keys)[number][] = [
|
||||
'websocket',
|
||||
'startupWarmups',
|
||||
'subtitleStyle',
|
||||
'ankiConnect',
|
||||
'immersionTracking',
|
||||
|
||||
@@ -32,6 +32,36 @@ export function buildCoreConfigOptionRegistry(
|
||||
defaultValue: defaultConfig.subsync.defaultMode,
|
||||
description: 'Subsync default mode.',
|
||||
},
|
||||
{
|
||||
path: 'startupWarmups.lowPowerMode',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.startupWarmups.lowPowerMode,
|
||||
description: 'Defer startup warmups except Yomitan extension.',
|
||||
},
|
||||
{
|
||||
path: 'startupWarmups.mecab',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.startupWarmups.mecab,
|
||||
description: 'Warm up MeCab tokenizer at startup.',
|
||||
},
|
||||
{
|
||||
path: 'startupWarmups.yomitanExtension',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.startupWarmups.yomitanExtension,
|
||||
description: 'Warm up Yomitan extension at startup.',
|
||||
},
|
||||
{
|
||||
path: 'startupWarmups.subtitleDictionaries',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.startupWarmups.subtitleDictionaries,
|
||||
description: 'Warm up subtitle dictionaries at startup.',
|
||||
},
|
||||
{
|
||||
path: 'startupWarmups.jellyfinRemoteSession',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.startupWarmups.jellyfinRemoteSession,
|
||||
description: 'Warm up Jellyfin remote session at startup.',
|
||||
},
|
||||
{
|
||||
path: 'shortcuts.multiCopyTimeoutMs',
|
||||
kind: 'number',
|
||||
|
||||
@@ -26,6 +26,15 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
||||
description: ['Controls logging verbosity.', 'Set to debug for full runtime diagnostics.'],
|
||||
key: 'logging',
|
||||
},
|
||||
{
|
||||
title: 'Startup Warmups',
|
||||
description: [
|
||||
'Background warmup controls for MeCab, Yomitan, dictionaries, and Jellyfin session.',
|
||||
'Disable individual warmups to defer load until first real usage.',
|
||||
'lowPowerMode defers all warmups except Yomitan extension.',
|
||||
],
|
||||
key: 'startupWarmups',
|
||||
},
|
||||
{
|
||||
title: 'Keyboard Shortcuts',
|
||||
description: ['Overlay keyboard shortcuts. Set a shortcut to null to disable.'],
|
||||
|
||||
@@ -74,6 +74,30 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
|
||||
);
|
||||
}
|
||||
|
||||
if (isObject(src.startupWarmups)) {
|
||||
const startupWarmupBooleanKeys = [
|
||||
'lowPowerMode',
|
||||
'mecab',
|
||||
'yomitanExtension',
|
||||
'subtitleDictionaries',
|
||||
'jellyfinRemoteSession',
|
||||
] as const;
|
||||
|
||||
for (const key of startupWarmupBooleanKeys) {
|
||||
const value = asBoolean(src.startupWarmups[key]);
|
||||
if (value !== undefined) {
|
||||
resolved.startupWarmups[key] = value as (typeof resolved.startupWarmups)[typeof key];
|
||||
} else if (src.startupWarmups[key] !== undefined) {
|
||||
warn(
|
||||
`startupWarmups.${key}`,
|
||||
src.startupWarmups[key],
|
||||
resolved.startupWarmups[key],
|
||||
'Expected boolean.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isObject(src.shortcuts)) {
|
||||
const shortcutKeys = [
|
||||
'toggleVisibleOverlayGlobal',
|
||||
|
||||
27
src/main.ts
27
src/main.ts
@@ -1936,10 +1936,6 @@ const { reloadConfig: reloadConfigHandler, appReadyRuntimeRunner } = composeAppR
|
||||
logInfo: (message) => appLogger.logInfo(message),
|
||||
logWarning: (message) => appLogger.logWarning(message),
|
||||
showDesktopNotification: (title, options) => showDesktopNotification(title, options),
|
||||
showConfigWarningsDialog:
|
||||
process.platform === 'darwin'
|
||||
? (title, details) => dialog.showErrorBox(title, details)
|
||||
: undefined,
|
||||
startConfigHotReload: () => configHotReloadRuntime.start(),
|
||||
refreshAnilistClientSecretState: (options) => refreshAnilistClientSecretState(options),
|
||||
failHandlers: {
|
||||
@@ -2250,6 +2246,7 @@ const {
|
||||
getKnownWordMatchMode: () =>
|
||||
appState.ankiIntegration?.getKnownWordMatchMode() ??
|
||||
getResolvedConfig().ankiConnect.nPlusOne.matchMode,
|
||||
getNPlusOneEnabled: () => getResolvedConfig().ankiConnect.nPlusOne.highlightEnabled,
|
||||
getMinSentenceWordsForNPlusOne: () =>
|
||||
getResolvedConfig().ankiConnect.nPlusOne.minSentenceWords,
|
||||
getJlptLevel: (text) => appState.jlptLevelLookup(text),
|
||||
@@ -2290,6 +2287,28 @@ const {
|
||||
},
|
||||
isTexthookerOnlyMode: () => appState.texthookerOnlyMode,
|
||||
ensureYomitanExtensionLoaded: () => ensureYomitanExtensionLoaded().then(() => {}),
|
||||
shouldWarmupMecab: () => {
|
||||
const startupWarmups = getResolvedConfig().startupWarmups;
|
||||
if (startupWarmups.lowPowerMode) {
|
||||
return false;
|
||||
}
|
||||
return startupWarmups.mecab;
|
||||
},
|
||||
shouldWarmupYomitanExtension: () => getResolvedConfig().startupWarmups.yomitanExtension,
|
||||
shouldWarmupSubtitleDictionaries: () => {
|
||||
const startupWarmups = getResolvedConfig().startupWarmups;
|
||||
if (startupWarmups.lowPowerMode) {
|
||||
return false;
|
||||
}
|
||||
return startupWarmups.subtitleDictionaries;
|
||||
},
|
||||
shouldWarmupJellyfinRemoteSession: () => {
|
||||
const startupWarmups = getResolvedConfig().startupWarmups;
|
||||
if (startupWarmups.lowPowerMode) {
|
||||
return false;
|
||||
}
|
||||
return startupWarmups.jellyfinRemoteSession;
|
||||
},
|
||||
shouldAutoConnectJellyfinRemote: () => {
|
||||
const jellyfin = getResolvedConfig().jellyfin;
|
||||
return (
|
||||
|
||||
@@ -26,6 +26,7 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
const calls: string[] = [];
|
||||
let started = false;
|
||||
let metrics = BASE_METRICS;
|
||||
let mecabTokenizer: { id: string } | null = null;
|
||||
|
||||
class FakeMpvClient {
|
||||
connected = false;
|
||||
@@ -140,9 +141,15 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
return { text };
|
||||
},
|
||||
createMecabTokenizerAndCheckMainDeps: {
|
||||
getMecabTokenizer: () => ({ id: 'mecab' }),
|
||||
setMecabTokenizer: () => {},
|
||||
createMecabTokenizer: () => ({ id: 'mecab' }),
|
||||
getMecabTokenizer: () => mecabTokenizer,
|
||||
setMecabTokenizer: (next) => {
|
||||
mecabTokenizer = next as { id: string };
|
||||
calls.push('set-mecab');
|
||||
},
|
||||
createMecabTokenizer: () => {
|
||||
calls.push('create-mecab');
|
||||
return { id: 'mecab' };
|
||||
},
|
||||
checkAvailability: async () => {
|
||||
calls.push('check-mecab');
|
||||
},
|
||||
@@ -176,6 +183,10 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
ensureYomitanExtensionLoaded: async () => {
|
||||
calls.push('warmup-yomitan');
|
||||
},
|
||||
shouldWarmupMecab: () => true,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => true,
|
||||
shouldWarmupJellyfinRemoteSession: () => true,
|
||||
shouldAutoConnectJellyfinRemote: () => false,
|
||||
startJellyfinRemoteSession: async () => {
|
||||
calls.push('warmup-jellyfin');
|
||||
@@ -212,9 +223,12 @@ test('composeMpvRuntimeHandlers returns callable handlers and forwards to inject
|
||||
assert.ok(calls.includes('broadcast-metrics'));
|
||||
assert.ok(calls.includes('create-tokenizer-runtime-deps'));
|
||||
assert.ok(calls.includes('tokenize:subtitle text'));
|
||||
assert.ok(calls.includes('create-mecab'));
|
||||
assert.ok(calls.includes('set-mecab'));
|
||||
assert.ok(calls.includes('check-mecab'));
|
||||
assert.ok(calls.includes('prewarm-jlpt'));
|
||||
assert.ok(calls.includes('prewarm-frequency'));
|
||||
assert.ok(calls.includes('set-started:true'));
|
||||
assert.ok(calls.includes('warmup-yomitan'));
|
||||
assert.ok(calls.indexOf('create-mecab') < calls.indexOf('set-started:true'));
|
||||
});
|
||||
|
||||
@@ -133,6 +133,10 @@ export function composeMpvRuntimeHandlers<
|
||||
options.tokenizer.prewarmSubtitleDictionariesMainDeps,
|
||||
);
|
||||
const tokenizeSubtitle = async (text: string): Promise<TTokenizedSubtitle> => {
|
||||
await options.warmups.startBackgroundWarmupsMainDeps.ensureYomitanExtensionLoaded();
|
||||
if (!options.tokenizer.createMecabTokenizerAndCheckMainDeps.getMecabTokenizer()) {
|
||||
await createMecabTokenizerAndCheck().catch(() => {});
|
||||
}
|
||||
await prewarmSubtitleDictionaries();
|
||||
return options.tokenizer.tokenizeSubtitle(
|
||||
text,
|
||||
|
||||
@@ -34,6 +34,10 @@ test('startup warmups main deps builders map callbacks', async () => {
|
||||
prewarmSubtitleDictionaries: async () => {
|
||||
calls.push('dict');
|
||||
},
|
||||
shouldWarmupMecab: () => false,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => false,
|
||||
shouldWarmupJellyfinRemoteSession: () => true,
|
||||
shouldAutoConnectJellyfinRemote: () => true,
|
||||
startJellyfinRemoteSession: async () => {
|
||||
calls.push('jellyfin');
|
||||
@@ -48,6 +52,10 @@ test('startup warmups main deps builders map callbacks', async () => {
|
||||
await start.createMecabTokenizerAndCheck();
|
||||
await start.ensureYomitanExtensionLoaded();
|
||||
await start.prewarmSubtitleDictionaries();
|
||||
assert.equal(start.shouldWarmupMecab(), false);
|
||||
assert.equal(start.shouldWarmupYomitanExtension(), true);
|
||||
assert.equal(start.shouldWarmupSubtitleDictionaries(), false);
|
||||
assert.equal(start.shouldWarmupJellyfinRemoteSession(), true);
|
||||
assert.equal(start.shouldAutoConnectJellyfinRemote(), true);
|
||||
await start.startJellyfinRemoteSession();
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ export function createBuildStartBackgroundWarmupsMainDepsHandler(deps: StartBack
|
||||
createMecabTokenizerAndCheck: () => deps.createMecabTokenizerAndCheck(),
|
||||
ensureYomitanExtensionLoaded: () => deps.ensureYomitanExtensionLoaded(),
|
||||
prewarmSubtitleDictionaries: () => deps.prewarmSubtitleDictionaries(),
|
||||
shouldWarmupMecab: () => deps.shouldWarmupMecab(),
|
||||
shouldWarmupYomitanExtension: () => deps.shouldWarmupYomitanExtension(),
|
||||
shouldWarmupSubtitleDictionaries: () => deps.shouldWarmupSubtitleDictionaries(),
|
||||
shouldWarmupJellyfinRemoteSession: () => deps.shouldWarmupJellyfinRemoteSession(),
|
||||
shouldAutoConnectJellyfinRemote: () => deps.shouldAutoConnectJellyfinRemote(),
|
||||
startJellyfinRemoteSession: () => deps.startJellyfinRemoteSession(),
|
||||
});
|
||||
|
||||
@@ -41,6 +41,10 @@ test('startBackgroundWarmups no-ops when already started', () => {
|
||||
createMecabTokenizerAndCheck: async () => {},
|
||||
ensureYomitanExtensionLoaded: async () => {},
|
||||
prewarmSubtitleDictionaries: async () => {},
|
||||
shouldWarmupMecab: () => true,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => true,
|
||||
shouldWarmupJellyfinRemoteSession: () => true,
|
||||
shouldAutoConnectJellyfinRemote: () => false,
|
||||
startJellyfinRemoteSession: async () => {},
|
||||
});
|
||||
@@ -49,7 +53,7 @@ test('startBackgroundWarmups no-ops when already started', () => {
|
||||
assert.equal(launches, 0);
|
||||
});
|
||||
|
||||
test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.enabled is false', () => {
|
||||
test('startBackgroundWarmups respects per-integration warmup toggles', () => {
|
||||
const labels: string[] = [];
|
||||
let started = false;
|
||||
const startWarmups = createStartBackgroundWarmupsHandler({
|
||||
@@ -64,9 +68,13 @@ test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.ena
|
||||
createMecabTokenizerAndCheck: async () => {},
|
||||
ensureYomitanExtensionLoaded: async () => {},
|
||||
prewarmSubtitleDictionaries: async () => {},
|
||||
shouldWarmupMecab: () => false,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => false,
|
||||
shouldWarmupJellyfinRemoteSession: () => false,
|
||||
shouldAutoConnectJellyfinRemote: () =>
|
||||
shouldAutoConnectJellyfinRemote({
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
remoteControlEnabled: true,
|
||||
remoteControlAutoConnect: true,
|
||||
}),
|
||||
@@ -75,7 +83,7 @@ test('startBackgroundWarmups does not schedule jellyfin warmup when jellyfin.ena
|
||||
|
||||
startWarmups();
|
||||
assert.equal(started, true);
|
||||
assert.deepEqual(labels, ['mecab', 'yomitan-extension', 'subtitle-dictionaries']);
|
||||
assert.deepEqual(labels, ['yomitan-extension']);
|
||||
});
|
||||
|
||||
test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags are enabled', () => {
|
||||
@@ -93,6 +101,10 @@ test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags a
|
||||
createMecabTokenizerAndCheck: async () => {},
|
||||
ensureYomitanExtensionLoaded: async () => {},
|
||||
prewarmSubtitleDictionaries: async () => {},
|
||||
shouldWarmupMecab: () => true,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => true,
|
||||
shouldWarmupJellyfinRemoteSession: () => true,
|
||||
shouldAutoConnectJellyfinRemote: () =>
|
||||
shouldAutoConnectJellyfinRemote({
|
||||
enabled: true,
|
||||
@@ -111,3 +123,36 @@ test('startBackgroundWarmups schedules jellyfin warmup when all jellyfin flags a
|
||||
'jellyfin-remote-session',
|
||||
]);
|
||||
});
|
||||
|
||||
test('startBackgroundWarmups skips jellyfin warmup when warmup is deferred', () => {
|
||||
const labels: string[] = [];
|
||||
let started = false;
|
||||
const startWarmups = createStartBackgroundWarmupsHandler({
|
||||
getStarted: () => started,
|
||||
setStarted: (value) => {
|
||||
started = value;
|
||||
},
|
||||
isTexthookerOnlyMode: () => false,
|
||||
launchTask: (label) => {
|
||||
labels.push(label);
|
||||
},
|
||||
createMecabTokenizerAndCheck: async () => {},
|
||||
ensureYomitanExtensionLoaded: async () => {},
|
||||
prewarmSubtitleDictionaries: async () => {},
|
||||
shouldWarmupMecab: () => false,
|
||||
shouldWarmupYomitanExtension: () => true,
|
||||
shouldWarmupSubtitleDictionaries: () => false,
|
||||
shouldWarmupJellyfinRemoteSession: () => false,
|
||||
shouldAutoConnectJellyfinRemote: () =>
|
||||
shouldAutoConnectJellyfinRemote({
|
||||
enabled: true,
|
||||
remoteControlEnabled: true,
|
||||
remoteControlAutoConnect: true,
|
||||
}),
|
||||
startJellyfinRemoteSession: async () => {},
|
||||
});
|
||||
|
||||
startWarmups();
|
||||
assert.equal(started, true);
|
||||
assert.deepEqual(labels, ['yomitan-extension']);
|
||||
});
|
||||
|
||||
@@ -24,6 +24,10 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
||||
createMecabTokenizerAndCheck: () => Promise<void>;
|
||||
ensureYomitanExtensionLoaded: () => Promise<void>;
|
||||
prewarmSubtitleDictionaries: () => Promise<void>;
|
||||
shouldWarmupMecab: () => boolean;
|
||||
shouldWarmupYomitanExtension: () => boolean;
|
||||
shouldWarmupSubtitleDictionaries: () => boolean;
|
||||
shouldWarmupJellyfinRemoteSession: () => boolean;
|
||||
shouldAutoConnectJellyfinRemote: () => boolean;
|
||||
startJellyfinRemoteSession: () => Promise<void>;
|
||||
}) {
|
||||
@@ -32,16 +36,22 @@ export function createStartBackgroundWarmupsHandler(deps: {
|
||||
if (deps.isTexthookerOnlyMode()) return;
|
||||
|
||||
deps.setStarted(true);
|
||||
deps.launchTask('mecab', async () => {
|
||||
await deps.createMecabTokenizerAndCheck();
|
||||
});
|
||||
deps.launchTask('yomitan-extension', async () => {
|
||||
await deps.ensureYomitanExtensionLoaded();
|
||||
});
|
||||
deps.launchTask('subtitle-dictionaries', async () => {
|
||||
await deps.prewarmSubtitleDictionaries();
|
||||
});
|
||||
if (deps.shouldAutoConnectJellyfinRemote()) {
|
||||
if (deps.shouldWarmupMecab()) {
|
||||
deps.launchTask('mecab', async () => {
|
||||
await deps.createMecabTokenizerAndCheck();
|
||||
});
|
||||
}
|
||||
if (deps.shouldWarmupYomitanExtension()) {
|
||||
deps.launchTask('yomitan-extension', async () => {
|
||||
await deps.ensureYomitanExtensionLoaded();
|
||||
});
|
||||
}
|
||||
if (deps.shouldWarmupSubtitleDictionaries()) {
|
||||
deps.launchTask('subtitle-dictionaries', async () => {
|
||||
await deps.prewarmSubtitleDictionaries();
|
||||
});
|
||||
}
|
||||
if (deps.shouldWarmupJellyfinRemoteSession() && deps.shouldAutoConnectJellyfinRemote()) {
|
||||
deps.launchTask('jellyfin-remote-session', async () => {
|
||||
await deps.startJellyfinRemoteSession();
|
||||
});
|
||||
|
||||
16
src/types.ts
16
src/types.ts
@@ -99,6 +99,14 @@ export interface SubsyncConfig {
|
||||
ffmpeg_path?: string;
|
||||
}
|
||||
|
||||
export interface StartupWarmupsConfig {
|
||||
lowPowerMode?: boolean;
|
||||
mecab?: boolean;
|
||||
yomitanExtension?: boolean;
|
||||
subtitleDictionaries?: boolean;
|
||||
jellyfinRemoteSession?: boolean;
|
||||
}
|
||||
|
||||
export interface WebSocketConfig {
|
||||
enabled?: boolean | 'auto';
|
||||
port?: number;
|
||||
@@ -417,6 +425,7 @@ export interface Config {
|
||||
shortcuts?: ShortcutsConfig;
|
||||
secondarySub?: SecondarySubConfig;
|
||||
subsync?: SubsyncConfig;
|
||||
startupWarmups?: StartupWarmupsConfig;
|
||||
subtitleStyle?: SubtitleStyleConfig;
|
||||
auto_start_overlay?: boolean;
|
||||
jimaku?: JimakuConfig;
|
||||
@@ -513,6 +522,13 @@ export interface ResolvedConfig {
|
||||
shortcuts: Required<ShortcutsConfig>;
|
||||
secondarySub: Required<SecondarySubConfig>;
|
||||
subsync: Required<SubsyncConfig>;
|
||||
startupWarmups: {
|
||||
lowPowerMode: boolean;
|
||||
mecab: boolean;
|
||||
yomitanExtension: boolean;
|
||||
subtitleDictionaries: boolean;
|
||||
jellyfinRemoteSession: boolean;
|
||||
};
|
||||
subtitleStyle: Required<Omit<SubtitleStyleConfig, 'secondary' | 'frequencyDictionary'>> & {
|
||||
secondary: Required<NonNullable<SubtitleStyleConfig['secondary']>>;
|
||||
frequencyDictionary: {
|
||||
|
||||
Reference in New Issue
Block a user