From adcae2ee01cf1203538be1730549b053014cfcd7 Mon Sep 17 00:00:00 2001 From: sudacode Date: Tue, 10 Feb 2026 01:05:05 -0800 Subject: [PATCH] refactor: extract anki jimaku ipc deps runtime service --- package.json | 2 +- ...ki-jimaku-ipc-deps-runtime-service.test.ts | 48 +++++++++++++++++++ .../anki-jimaku-ipc-deps-runtime-service.ts | 32 +++++++++++++ .../services/anki-jimaku-runtime-service.ts | 8 +++- src/main.ts | 41 ++++++++++++---- 5 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 src/core/services/anki-jimaku-ipc-deps-runtime-service.test.ts create mode 100644 src/core/services/anki-jimaku-ipc-deps-runtime-service.ts diff --git a/package.json b/package.json index a70e9bc..69c9f3b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort", "test:config": "pnpm run build && node --test dist/config/config.test.js", - "test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-service.test.js dist/core/services/cli-command-deps-runtime-service.test.js dist/core/services/ipc-deps-runtime-service.test.js dist/core/services/numeric-shortcut-session-service.test.js dist/core/services/secondary-subtitle-service.test.js dist/core/services/mpv-render-metrics-service.test.js dist/core/services/mpv-runtime-service.test.js dist/core/services/runtime-options-runtime-service.test.js dist/core/services/overlay-modal-restore-service.test.js dist/core/services/runtime-config-service.test.js dist/core/services/overlay-bridge-runtime-service.test.js dist/core/services/overlay-visibility-facade-service.test.js dist/core/services/overlay-broadcast-runtime-service.test.js dist/core/services/app-ready-runtime-service.test.js dist/core/services/app-shutdown-runtime-service.test.js dist/core/services/mpv-client-deps-runtime-service.test.js dist/core/services/app-lifecycle-deps-runtime-service.test.js dist/core/services/runtime-options-manager-runtime-service.test.js dist/core/services/config-warning-runtime-service.test.js dist/core/services/app-logging-runtime-service.test.js dist/core/services/startup-resource-runtime-service.test.js dist/core/services/config-generation-runtime-service.test.js", + "test:core": "pnpm run build && node --test dist/cli/args.test.js dist/cli/help.test.js dist/core/services/cli-command-service.test.js dist/core/services/cli-command-deps-runtime-service.test.js dist/core/services/ipc-deps-runtime-service.test.js dist/core/services/anki-jimaku-ipc-deps-runtime-service.test.js dist/core/services/numeric-shortcut-session-service.test.js dist/core/services/secondary-subtitle-service.test.js dist/core/services/mpv-render-metrics-service.test.js dist/core/services/mpv-runtime-service.test.js dist/core/services/runtime-options-runtime-service.test.js dist/core/services/overlay-modal-restore-service.test.js dist/core/services/runtime-config-service.test.js dist/core/services/overlay-bridge-runtime-service.test.js dist/core/services/overlay-visibility-facade-service.test.js dist/core/services/overlay-broadcast-runtime-service.test.js dist/core/services/app-ready-runtime-service.test.js dist/core/services/app-shutdown-runtime-service.test.js dist/core/services/mpv-client-deps-runtime-service.test.js dist/core/services/app-lifecycle-deps-runtime-service.test.js dist/core/services/runtime-options-manager-runtime-service.test.js dist/core/services/config-warning-runtime-service.test.js dist/core/services/app-logging-runtime-service.test.js dist/core/services/startup-resource-runtime-service.test.js dist/core/services/config-generation-runtime-service.test.js", "test:subtitle": "pnpm run build && node --test dist/subtitle/stages.test.js dist/subtitle/pipeline.test.js", "generate:config-example": "pnpm run build && node dist/generate-config-example.js", "start": "pnpm run build && electron . --start", diff --git a/src/core/services/anki-jimaku-ipc-deps-runtime-service.test.ts b/src/core/services/anki-jimaku-ipc-deps-runtime-service.test.ts new file mode 100644 index 0000000..c11dd9d --- /dev/null +++ b/src/core/services/anki-jimaku-ipc-deps-runtime-service.test.ts @@ -0,0 +1,48 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { + AnkiJimakuIpcDepsRuntimeOptions, + createAnkiJimakuIpcDepsRuntimeService, +} from "./anki-jimaku-ipc-deps-runtime-service"; + +test("createAnkiJimakuIpcDepsRuntimeService returns passthrough runtime options", async () => { + const calls: string[] = []; + const options = { + patchAnkiConnectEnabled: () => calls.push("patch"), + getResolvedConfig: () => ({ ankiConnect: undefined }), + getRuntimeOptionsManager: () => null, + getSubtitleTimingTracker: () => null, + getMpvClient: () => null, + getAnkiIntegration: () => null, + setAnkiIntegration: () => calls.push("set-integration"), + showDesktopNotification: () => calls.push("notify"), + createFieldGroupingCallback: () => async () => ({ + keepNoteId: 0, + deleteNoteId: 0, + deleteDuplicate: false, + cancelled: true, + }), + broadcastRuntimeOptionsChanged: () => calls.push("broadcast"), + getFieldGroupingResolver: () => null, + setFieldGroupingResolver: () => calls.push("set-resolver"), + parseMediaInfo: () => ({ mediaPath: null, baseName: null, episode: null }), + getCurrentMediaPath: () => "/tmp/a.mp4", + jimakuFetchJson: async () => ({ ok: true, data: [] }), + getJimakuMaxEntryResults: () => 100, + getJimakuLanguagePreference: () => "prefer-japanese", + resolveJimakuApiKey: async () => "abc", + isRemoteMediaPath: () => false, + downloadToFile: async () => ({ ok: true, path: "/tmp/a.srt" }), + } as unknown as AnkiJimakuIpcDepsRuntimeOptions; + + const runtime = createAnkiJimakuIpcDepsRuntimeService(options); + + runtime.patchAnkiConnectEnabled(true); + runtime.broadcastRuntimeOptionsChanged(); + runtime.setFieldGroupingResolver(null); + + assert.deepEqual(calls, ["patch", "broadcast", "set-resolver"]); + assert.equal(runtime.getCurrentMediaPath(), "/tmp/a.mp4"); + assert.equal(runtime.getJimakuMaxEntryResults(), 100); + assert.equal(await runtime.resolveJimakuApiKey(), "abc"); +}); diff --git a/src/core/services/anki-jimaku-ipc-deps-runtime-service.ts b/src/core/services/anki-jimaku-ipc-deps-runtime-service.ts new file mode 100644 index 0000000..4176c71 --- /dev/null +++ b/src/core/services/anki-jimaku-ipc-deps-runtime-service.ts @@ -0,0 +1,32 @@ +import { + AnkiJimakuIpcRuntimeOptions, +} from "./anki-jimaku-runtime-service"; + +export type AnkiJimakuIpcDepsRuntimeOptions = AnkiJimakuIpcRuntimeOptions; + +export function createAnkiJimakuIpcDepsRuntimeService( + options: AnkiJimakuIpcDepsRuntimeOptions, +): AnkiJimakuIpcRuntimeOptions { + return { + patchAnkiConnectEnabled: options.patchAnkiConnectEnabled, + getResolvedConfig: options.getResolvedConfig, + getRuntimeOptionsManager: options.getRuntimeOptionsManager, + getSubtitleTimingTracker: options.getSubtitleTimingTracker, + getMpvClient: options.getMpvClient, + getAnkiIntegration: options.getAnkiIntegration, + setAnkiIntegration: options.setAnkiIntegration, + showDesktopNotification: options.showDesktopNotification, + createFieldGroupingCallback: options.createFieldGroupingCallback, + broadcastRuntimeOptionsChanged: options.broadcastRuntimeOptionsChanged, + getFieldGroupingResolver: options.getFieldGroupingResolver, + setFieldGroupingResolver: options.setFieldGroupingResolver, + parseMediaInfo: options.parseMediaInfo, + getCurrentMediaPath: options.getCurrentMediaPath, + jimakuFetchJson: options.jimakuFetchJson, + getJimakuMaxEntryResults: options.getJimakuMaxEntryResults, + getJimakuLanguagePreference: options.getJimakuLanguagePreference, + resolveJimakuApiKey: options.resolveJimakuApiKey, + isRemoteMediaPath: options.isRemoteMediaPath, + downloadToFile: options.downloadToFile, + }; +} diff --git a/src/core/services/anki-jimaku-runtime-service.ts b/src/core/services/anki-jimaku-runtime-service.ts index 386df8c..819a7c2 100644 --- a/src/core/services/anki-jimaku-runtime-service.ts +++ b/src/core/services/anki-jimaku-runtime-service.ts @@ -25,7 +25,7 @@ interface SubtitleTimingTrackerLike { cleanup: () => void; } -export function registerAnkiJimakuIpcRuntimeService(options: { +export interface AnkiJimakuIpcRuntimeOptions { patchAnkiConnectEnabled: (enabled: boolean) => void; getResolvedConfig: () => { ankiConnect?: AnkiConnectConfig }; getRuntimeOptionsManager: () => RuntimeOptionsManagerLike | null; @@ -55,7 +55,11 @@ export function registerAnkiJimakuIpcRuntimeService(options: { destPath: string, headers: Record, ) => Promise<{ ok: true; path: string } | { ok: false; error: { error: string; code?: number; retryAfter?: number } }>; -}): void { +} + +export function registerAnkiJimakuIpcRuntimeService( + options: AnkiJimakuIpcRuntimeOptions, +): void { registerAnkiJimakuIpcHandlers({ setAnkiConnectEnabled: (enabled) => { options.patchAnkiConnectEnabled(enabled); diff --git a/src/main.ts b/src/main.ts index 5679599..c8d68a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -207,6 +207,7 @@ import { createMpvIpcClientDepsRuntimeService } from "./core/services/mpv-client import { createAppLifecycleDepsRuntimeService } from "./core/services/app-lifecycle-deps-runtime-service"; import { createCliCommandDepsRuntimeService } from "./core/services/cli-command-deps-runtime-service"; import { createIpcDepsRuntimeService } from "./core/services/ipc-deps-runtime-service"; +import { createAnkiJimakuIpcDepsRuntimeService } from "./core/services/anki-jimaku-ipc-deps-runtime-service"; import { createRuntimeOptionsManagerRuntimeService } from "./core/services/runtime-options-manager-runtime-service"; import { createAppLoggingRuntimeService } from "./core/services/app-logging-runtime-service"; import { @@ -1323,12 +1324,34 @@ function sendToVisibleOverlay(channel: string, payload?: unknown, options?: { re }); } -registerAnkiJimakuIpcRuntimeService({ - patchAnkiConnectEnabled: (enabled) => { configService.patchRawConfig({ ankiConnect: { enabled } }); }, - getResolvedConfig: () => getResolvedConfig(), getRuntimeOptionsManager: () => runtimeOptionsManager, getSubtitleTimingTracker: () => subtitleTimingTracker, getMpvClient: () => mpvClient, getAnkiIntegration: () => ankiIntegration, setAnkiIntegration: (integration) => { ankiIntegration = integration; }, - showDesktopNotification, createFieldGroupingCallback: () => createFieldGroupingCallback(), broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(), - getFieldGroupingResolver: () => fieldGroupingResolver, setFieldGroupingResolver: (resolver) => { fieldGroupingResolver = resolver; }, - parseMediaInfo: (mediaPath) => parseMediaInfo(mediaPath), getCurrentMediaPath: () => currentMediaPath, jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query), - getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(), getJimakuLanguagePreference: () => getJimakuLanguagePreference(), resolveJimakuApiKey: () => resolveJimakuApiKey(), - isRemoteMediaPath: (mediaPath) => isRemoteMediaPath(mediaPath), downloadToFile: (url, destPath, headers) => downloadToFile(url, destPath, headers), -}); +registerAnkiJimakuIpcRuntimeService( + createAnkiJimakuIpcDepsRuntimeService({ + patchAnkiConnectEnabled: (enabled) => { + configService.patchRawConfig({ ankiConnect: { enabled } }); + }, + getResolvedConfig: () => getResolvedConfig(), + getRuntimeOptionsManager: () => runtimeOptionsManager, + getSubtitleTimingTracker: () => subtitleTimingTracker, + getMpvClient: () => mpvClient, + getAnkiIntegration: () => ankiIntegration, + setAnkiIntegration: (integration) => { + ankiIntegration = integration; + }, + showDesktopNotification, + createFieldGroupingCallback: () => createFieldGroupingCallback(), + broadcastRuntimeOptionsChanged: () => broadcastRuntimeOptionsChanged(), + getFieldGroupingResolver: () => fieldGroupingResolver, + setFieldGroupingResolver: (resolver) => { + fieldGroupingResolver = resolver; + }, + parseMediaInfo: (mediaPath) => parseMediaInfo(mediaPath), + getCurrentMediaPath: () => currentMediaPath, + jimakuFetchJson: (endpoint, query) => jimakuFetchJson(endpoint, query), + getJimakuMaxEntryResults: () => getJimakuMaxEntryResults(), + getJimakuLanguagePreference: () => getJimakuLanguagePreference(), + resolveJimakuApiKey: () => resolveJimakuApiKey(), + isRemoteMediaPath: (mediaPath) => isRemoteMediaPath(mediaPath), + downloadToFile: (url, destPath, headers) => + downloadToFile(url, destPath, headers), + }), +);