mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
refactor: extract ipc mpv and tokenizer runtime deps
This commit is contained in:
@@ -6,26 +6,31 @@ import {
|
||||
SubsyncResult,
|
||||
} from "../../types";
|
||||
|
||||
export interface HandleMpvCommandFromIpcOptions {
|
||||
specialCommands: {
|
||||
SUBSYNC_TRIGGER: string;
|
||||
RUNTIME_OPTIONS_OPEN: string;
|
||||
RUNTIME_OPTION_CYCLE_PREFIX: string;
|
||||
REPLAY_SUBTITLE: string;
|
||||
PLAY_NEXT_SUBTITLE: string;
|
||||
};
|
||||
triggerSubsyncFromConfig: () => void;
|
||||
openRuntimeOptionsPalette: () => void;
|
||||
runtimeOptionsCycle: (
|
||||
id: RuntimeOptionId,
|
||||
direction: 1 | -1,
|
||||
) => RuntimeOptionApplyResult;
|
||||
showMpvOsd: (text: string) => void;
|
||||
mpvReplaySubtitle: () => void;
|
||||
mpvPlayNextSubtitle: () => void;
|
||||
mpvSendCommand: (command: (string | number)[]) => void;
|
||||
isMpvConnected: () => boolean;
|
||||
hasRuntimeOptionsManager: () => boolean;
|
||||
}
|
||||
|
||||
export function handleMpvCommandFromIpcService(
|
||||
command: (string | number)[],
|
||||
options: {
|
||||
specialCommands: {
|
||||
SUBSYNC_TRIGGER: string;
|
||||
RUNTIME_OPTIONS_OPEN: string;
|
||||
RUNTIME_OPTION_CYCLE_PREFIX: string;
|
||||
REPLAY_SUBTITLE: string;
|
||||
PLAY_NEXT_SUBTITLE: string;
|
||||
};
|
||||
triggerSubsyncFromConfig: () => void;
|
||||
openRuntimeOptionsPalette: () => void;
|
||||
runtimeOptionsCycle: (id: RuntimeOptionId, direction: 1 | -1) => RuntimeOptionApplyResult;
|
||||
showMpvOsd: (text: string) => void;
|
||||
mpvReplaySubtitle: () => void;
|
||||
mpvPlayNextSubtitle: () => void;
|
||||
mpvSendCommand: (command: (string | number)[]) => void;
|
||||
isMpvConnected: () => boolean;
|
||||
hasRuntimeOptionsManager: () => boolean;
|
||||
},
|
||||
options: HandleMpvCommandFromIpcOptions,
|
||||
): void {
|
||||
const first = typeof command[0] === "string" ? command[0] : "";
|
||||
if (first === options.specialCommands.SUBSYNC_TRIGGER) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { SPECIAL_COMMANDS } from "../../config";
|
||||
import { createMpvCommandIpcDepsRuntimeService } from "./mpv-command-ipc-deps-runtime-service";
|
||||
|
||||
test("createMpvCommandIpcDepsRuntimeService wires runtime-options cycle and manager availability", () => {
|
||||
const osd: string[] = [];
|
||||
const deps = createMpvCommandIpcDepsRuntimeService({
|
||||
specialCommands: SPECIAL_COMMANDS,
|
||||
triggerSubsyncFromConfig: () => {},
|
||||
openRuntimeOptionsPalette: () => {},
|
||||
getRuntimeOptionsManager: () => ({
|
||||
cycleOption: () => ({ ok: true, osdMessage: "cycled" }),
|
||||
}),
|
||||
showMpvOsd: (text) => {
|
||||
osd.push(text);
|
||||
},
|
||||
mpvReplaySubtitle: () => {},
|
||||
mpvPlayNextSubtitle: () => {},
|
||||
mpvSendCommand: () => {},
|
||||
isMpvConnected: () => true,
|
||||
});
|
||||
|
||||
const result = deps.runtimeOptionsCycle("subtitles.secondaryMode" as never, 1);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(deps.hasRuntimeOptionsManager(), true);
|
||||
assert.ok(osd.includes("cycled"));
|
||||
});
|
||||
53
src/core/services/mpv-command-ipc-deps-runtime-service.ts
Normal file
53
src/core/services/mpv-command-ipc-deps-runtime-service.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import {
|
||||
RuntimeOptionApplyResult,
|
||||
RuntimeOptionId,
|
||||
} from "../../types";
|
||||
import {
|
||||
HandleMpvCommandFromIpcOptions,
|
||||
} from "./ipc-command-service";
|
||||
import { applyRuntimeOptionResultRuntimeService } from "./runtime-options-runtime-service";
|
||||
|
||||
interface RuntimeOptionsManagerLike {
|
||||
cycleOption: (
|
||||
id: RuntimeOptionId,
|
||||
direction: 1 | -1,
|
||||
) => RuntimeOptionApplyResult;
|
||||
}
|
||||
|
||||
export interface MpvCommandIpcDepsRuntimeOptions {
|
||||
specialCommands: HandleMpvCommandFromIpcOptions["specialCommands"];
|
||||
triggerSubsyncFromConfig: () => void;
|
||||
openRuntimeOptionsPalette: () => void;
|
||||
getRuntimeOptionsManager: () => RuntimeOptionsManagerLike | null;
|
||||
showMpvOsd: (text: string) => void;
|
||||
mpvReplaySubtitle: () => void;
|
||||
mpvPlayNextSubtitle: () => void;
|
||||
mpvSendCommand: (command: (string | number)[]) => void;
|
||||
isMpvConnected: () => boolean;
|
||||
}
|
||||
|
||||
export function createMpvCommandIpcDepsRuntimeService(
|
||||
options: MpvCommandIpcDepsRuntimeOptions,
|
||||
): HandleMpvCommandFromIpcOptions {
|
||||
return {
|
||||
specialCommands: options.specialCommands,
|
||||
triggerSubsyncFromConfig: options.triggerSubsyncFromConfig,
|
||||
openRuntimeOptionsPalette: options.openRuntimeOptionsPalette,
|
||||
runtimeOptionsCycle: (id, direction) => {
|
||||
const manager = options.getRuntimeOptionsManager();
|
||||
if (!manager) {
|
||||
return { ok: false, error: "Runtime options manager unavailable" };
|
||||
}
|
||||
return applyRuntimeOptionResultRuntimeService(
|
||||
manager.cycleOption(id, direction),
|
||||
options.showMpvOsd,
|
||||
);
|
||||
},
|
||||
showMpvOsd: options.showMpvOsd,
|
||||
mpvReplaySubtitle: options.mpvReplaySubtitle,
|
||||
mpvPlayNextSubtitle: options.mpvPlayNextSubtitle,
|
||||
mpvSendCommand: options.mpvSendCommand,
|
||||
isMpvConnected: options.isMpvConnected,
|
||||
hasRuntimeOptionsManager: () => options.getRuntimeOptionsManager() !== null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createRuntimeOptionsIpcDepsRuntimeService } from "./runtime-options-ipc-deps-runtime-service";
|
||||
|
||||
test("createRuntimeOptionsIpcDepsRuntimeService delegates set/cycle with osd", () => {
|
||||
const osd: string[] = [];
|
||||
const deps = createRuntimeOptionsIpcDepsRuntimeService({
|
||||
getRuntimeOptionsManager: () => ({
|
||||
setOptionValue: () => ({ ok: true, osdMessage: "set ok" }),
|
||||
cycleOption: () => ({ ok: true, osdMessage: "cycle ok" }),
|
||||
}),
|
||||
showMpvOsd: (text) => {
|
||||
osd.push(text);
|
||||
},
|
||||
});
|
||||
|
||||
const setResult = deps.setRuntimeOption("subtitles.secondaryMode", "hidden") as {
|
||||
ok: boolean;
|
||||
};
|
||||
const cycleResult = deps.cycleRuntimeOption("subtitles.secondaryMode", 1) as {
|
||||
ok: boolean;
|
||||
};
|
||||
|
||||
assert.equal(setResult.ok, true);
|
||||
assert.equal(cycleResult.ok, true);
|
||||
assert.ok(osd.includes("set ok"));
|
||||
assert.ok(osd.includes("cycle ok"));
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
RuntimeOptionId,
|
||||
RuntimeOptionValue,
|
||||
} from "../../types";
|
||||
import {
|
||||
cycleRuntimeOptionFromIpcRuntimeService,
|
||||
RuntimeOptionsManagerLike,
|
||||
setRuntimeOptionFromIpcRuntimeService,
|
||||
} from "./runtime-options-runtime-service";
|
||||
|
||||
export interface RuntimeOptionsIpcDepsRuntimeOptions {
|
||||
getRuntimeOptionsManager: () => RuntimeOptionsManagerLike | null;
|
||||
showMpvOsd: (text: string) => void;
|
||||
}
|
||||
|
||||
export function createRuntimeOptionsIpcDepsRuntimeService(
|
||||
options: RuntimeOptionsIpcDepsRuntimeOptions,
|
||||
): {
|
||||
setRuntimeOption: (id: string, value: unknown) => unknown;
|
||||
cycleRuntimeOption: (id: string, direction: 1 | -1) => unknown;
|
||||
} {
|
||||
return {
|
||||
setRuntimeOption: (id, value) =>
|
||||
setRuntimeOptionFromIpcRuntimeService(
|
||||
options.getRuntimeOptionsManager(),
|
||||
id as RuntimeOptionId,
|
||||
value as RuntimeOptionValue,
|
||||
options.showMpvOsd,
|
||||
),
|
||||
cycleRuntimeOption: (id, direction) =>
|
||||
cycleRuntimeOptionFromIpcRuntimeService(
|
||||
options.getRuntimeOptionsManager(),
|
||||
id as RuntimeOptionId,
|
||||
direction,
|
||||
options.showMpvOsd,
|
||||
),
|
||||
};
|
||||
}
|
||||
48
src/core/services/tokenizer-deps-runtime-service.test.ts
Normal file
48
src/core/services/tokenizer-deps-runtime-service.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { PartOfSpeech } from "../../types";
|
||||
import { createTokenizerDepsRuntimeService } from "./tokenizer-deps-runtime-service";
|
||||
|
||||
test("createTokenizerDepsRuntimeService tokenizes with mecab and merge", async () => {
|
||||
let parserWindow: any = null;
|
||||
let readyPromise: Promise<void> | null = null;
|
||||
let initPromise: Promise<boolean> | null = null;
|
||||
|
||||
const deps = createTokenizerDepsRuntimeService({
|
||||
getYomitanExt: () => null,
|
||||
getYomitanParserWindow: () => parserWindow,
|
||||
setYomitanParserWindow: (window) => {
|
||||
parserWindow = window;
|
||||
},
|
||||
getYomitanParserReadyPromise: () => readyPromise,
|
||||
setYomitanParserReadyPromise: (promise) => {
|
||||
readyPromise = promise;
|
||||
},
|
||||
getYomitanParserInitPromise: () => initPromise,
|
||||
setYomitanParserInitPromise: (promise) => {
|
||||
initPromise = promise;
|
||||
},
|
||||
getMecabTokenizer: () => ({
|
||||
tokenize: async () => [
|
||||
{
|
||||
word: "猫",
|
||||
partOfSpeech: PartOfSpeech.noun,
|
||||
pos1: "名詞",
|
||||
pos2: "一般",
|
||||
pos3: "",
|
||||
pos4: "",
|
||||
inflectionType: "",
|
||||
inflectionForm: "",
|
||||
headword: "猫",
|
||||
katakanaReading: "ネコ",
|
||||
pronunciation: "ネコ",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const merged = await deps.tokenizeWithMecab("猫");
|
||||
assert.ok(Array.isArray(merged));
|
||||
assert.equal(merged?.length, 1);
|
||||
assert.equal(merged?.[0]?.surface, "猫");
|
||||
});
|
||||
45
src/core/services/tokenizer-deps-runtime-service.ts
Normal file
45
src/core/services/tokenizer-deps-runtime-service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { BrowserWindow, Extension } from "electron";
|
||||
import { mergeTokens } from "../../token-merger";
|
||||
import { TokenizerServiceDeps } from "./tokenizer-service";
|
||||
|
||||
interface RawTokenLike {}
|
||||
|
||||
interface MecabTokenizerLike {
|
||||
tokenize: (text: string) => Promise<RawTokenLike[] | null>;
|
||||
}
|
||||
|
||||
export interface TokenizerDepsRuntimeOptions {
|
||||
getYomitanExt: () => Extension | null;
|
||||
getYomitanParserWindow: () => BrowserWindow | null;
|
||||
setYomitanParserWindow: (window: BrowserWindow | null) => void;
|
||||
getYomitanParserReadyPromise: () => Promise<void> | null;
|
||||
setYomitanParserReadyPromise: (promise: Promise<void> | null) => void;
|
||||
getYomitanParserInitPromise: () => Promise<boolean> | null;
|
||||
setYomitanParserInitPromise: (promise: Promise<boolean> | null) => void;
|
||||
getMecabTokenizer: () => MecabTokenizerLike | null;
|
||||
}
|
||||
|
||||
export function createTokenizerDepsRuntimeService(
|
||||
options: TokenizerDepsRuntimeOptions,
|
||||
): TokenizerServiceDeps {
|
||||
return {
|
||||
getYomitanExt: options.getYomitanExt,
|
||||
getYomitanParserWindow: options.getYomitanParserWindow,
|
||||
setYomitanParserWindow: options.setYomitanParserWindow,
|
||||
getYomitanParserReadyPromise: options.getYomitanParserReadyPromise,
|
||||
setYomitanParserReadyPromise: options.setYomitanParserReadyPromise,
|
||||
getYomitanParserInitPromise: options.getYomitanParserInitPromise,
|
||||
setYomitanParserInitPromise: options.setYomitanParserInitPromise,
|
||||
tokenizeWithMecab: async (text) => {
|
||||
const mecabTokenizer = options.getMecabTokenizer();
|
||||
if (!mecabTokenizer) {
|
||||
return null;
|
||||
}
|
||||
const rawTokens = await mecabTokenizer.tokenize(text);
|
||||
if (!rawTokens || rawTokens.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return mergeTokens(rawTokens as never);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user