mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor: extract cli command orchestration service
This commit is contained in:
204
src/core/services/cli-command-service.ts
Normal file
204
src/core/services/cli-command-service.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import {
|
||||
CliArgs,
|
||||
CliCommandSource,
|
||||
commandNeedsOverlayRuntime,
|
||||
} from "../../cli/args";
|
||||
|
||||
export interface CliCommandServiceDeps {
|
||||
getMpvSocketPath: () => string;
|
||||
setMpvSocketPath: (socketPath: string) => void;
|
||||
setMpvClientSocketPath: (socketPath: string) => void;
|
||||
hasMpvClient: () => boolean;
|
||||
connectMpvClient: () => void;
|
||||
isTexthookerRunning: () => boolean;
|
||||
setTexthookerPort: (port: number) => void;
|
||||
getTexthookerPort: () => number;
|
||||
shouldOpenTexthookerBrowser: () => boolean;
|
||||
ensureTexthookerRunning: (port: number) => void;
|
||||
openTexthookerInBrowser: (url: string) => void;
|
||||
stopApp: () => void;
|
||||
isOverlayRuntimeInitialized: () => boolean;
|
||||
initializeOverlayRuntime: () => void;
|
||||
toggleVisibleOverlay: () => void;
|
||||
toggleInvisibleOverlay: () => void;
|
||||
openYomitanSettingsDelayed: (delayMs: number) => void;
|
||||
setVisibleOverlayVisible: (visible: boolean) => void;
|
||||
setInvisibleOverlayVisible: (visible: boolean) => void;
|
||||
copyCurrentSubtitle: () => void;
|
||||
startPendingMultiCopy: (timeoutMs: number) => void;
|
||||
mineSentenceCard: () => Promise<void>;
|
||||
startPendingMineSentenceMultiple: (timeoutMs: number) => void;
|
||||
updateLastCardFromClipboard: () => Promise<void>;
|
||||
cycleSecondarySubMode: () => void;
|
||||
triggerFieldGrouping: () => Promise<void>;
|
||||
triggerSubsyncFromConfig: () => Promise<void>;
|
||||
markLastCardAsAudioCard: () => Promise<void>;
|
||||
openRuntimeOptionsPalette: () => void;
|
||||
printHelp: () => void;
|
||||
hasMainWindow: () => boolean;
|
||||
getMultiCopyTimeoutMs: () => number;
|
||||
showMpvOsd: (text: string) => void;
|
||||
log: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string, err: unknown) => void;
|
||||
}
|
||||
|
||||
function runAsyncWithOsd(
|
||||
task: () => Promise<void>,
|
||||
deps: CliCommandServiceDeps,
|
||||
logLabel: string,
|
||||
osdLabel: string,
|
||||
): void {
|
||||
task().catch((err) => {
|
||||
deps.error(`${logLabel} failed:`, err);
|
||||
deps.showMpvOsd(`${osdLabel}: ${(err as Error).message}`);
|
||||
});
|
||||
}
|
||||
|
||||
export function handleCliCommandService(
|
||||
args: CliArgs,
|
||||
source: CliCommandSource = "initial",
|
||||
deps: CliCommandServiceDeps,
|
||||
): void {
|
||||
const hasNonStartAction =
|
||||
args.stop ||
|
||||
args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay ||
|
||||
args.settings ||
|
||||
args.show ||
|
||||
args.hide ||
|
||||
args.showVisibleOverlay ||
|
||||
args.hideVisibleOverlay ||
|
||||
args.showInvisibleOverlay ||
|
||||
args.hideInvisibleOverlay ||
|
||||
args.copySubtitle ||
|
||||
args.copySubtitleMultiple ||
|
||||
args.mineSentence ||
|
||||
args.mineSentenceMultiple ||
|
||||
args.updateLastCardFromClipboard ||
|
||||
args.toggleSecondarySub ||
|
||||
args.triggerFieldGrouping ||
|
||||
args.triggerSubsync ||
|
||||
args.markAudioCard ||
|
||||
args.openRuntimeOptions ||
|
||||
args.texthooker ||
|
||||
args.help;
|
||||
const ignoreStart = source === "second-instance" && args.start;
|
||||
if (ignoreStart && !hasNonStartAction) {
|
||||
deps.log("Ignoring --start because SubMiner is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldStart =
|
||||
!ignoreStart &&
|
||||
(args.start ||
|
||||
(source === "initial" &&
|
||||
(args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay)));
|
||||
const needsOverlayRuntime = commandNeedsOverlayRuntime(args);
|
||||
|
||||
if (args.socketPath !== undefined) {
|
||||
deps.setMpvSocketPath(args.socketPath);
|
||||
deps.setMpvClientSocketPath(args.socketPath);
|
||||
}
|
||||
|
||||
if (args.texthookerPort !== undefined) {
|
||||
if (deps.isTexthookerRunning()) {
|
||||
deps.warn(
|
||||
"Ignoring --port override because the texthooker server is already running.",
|
||||
);
|
||||
} else {
|
||||
deps.setTexthookerPort(args.texthookerPort);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.stop) {
|
||||
deps.log("Stopping SubMiner...");
|
||||
deps.stopApp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsOverlayRuntime && !deps.isOverlayRuntimeInitialized()) {
|
||||
deps.initializeOverlayRuntime();
|
||||
}
|
||||
|
||||
if (shouldStart && deps.hasMpvClient()) {
|
||||
const socketPath = deps.getMpvSocketPath();
|
||||
deps.setMpvClientSocketPath(socketPath);
|
||||
deps.connectMpvClient();
|
||||
deps.log(`Starting MPV IPC connection on socket: ${socketPath}`);
|
||||
}
|
||||
|
||||
if (args.toggle || args.toggleVisibleOverlay) {
|
||||
deps.toggleVisibleOverlay();
|
||||
} else if (args.toggleInvisibleOverlay) {
|
||||
deps.toggleInvisibleOverlay();
|
||||
} else if (args.settings) {
|
||||
deps.openYomitanSettingsDelayed(1000);
|
||||
} else if (args.show || args.showVisibleOverlay) {
|
||||
deps.setVisibleOverlayVisible(true);
|
||||
} else if (args.hide || args.hideVisibleOverlay) {
|
||||
deps.setVisibleOverlayVisible(false);
|
||||
} else if (args.showInvisibleOverlay) {
|
||||
deps.setInvisibleOverlayVisible(true);
|
||||
} else if (args.hideInvisibleOverlay) {
|
||||
deps.setInvisibleOverlayVisible(false);
|
||||
} else if (args.copySubtitle) {
|
||||
deps.copyCurrentSubtitle();
|
||||
} else if (args.copySubtitleMultiple) {
|
||||
deps.startPendingMultiCopy(deps.getMultiCopyTimeoutMs());
|
||||
} else if (args.mineSentence) {
|
||||
runAsyncWithOsd(
|
||||
() => deps.mineSentenceCard(),
|
||||
deps,
|
||||
"mineSentenceCard",
|
||||
"Mine sentence failed",
|
||||
);
|
||||
} else if (args.mineSentenceMultiple) {
|
||||
deps.startPendingMineSentenceMultiple(deps.getMultiCopyTimeoutMs());
|
||||
} else if (args.updateLastCardFromClipboard) {
|
||||
runAsyncWithOsd(
|
||||
() => deps.updateLastCardFromClipboard(),
|
||||
deps,
|
||||
"updateLastCardFromClipboard",
|
||||
"Update failed",
|
||||
);
|
||||
} else if (args.toggleSecondarySub) {
|
||||
deps.cycleSecondarySubMode();
|
||||
} else if (args.triggerFieldGrouping) {
|
||||
runAsyncWithOsd(
|
||||
() => deps.triggerFieldGrouping(),
|
||||
deps,
|
||||
"triggerFieldGrouping",
|
||||
"Field grouping failed",
|
||||
);
|
||||
} else if (args.triggerSubsync) {
|
||||
runAsyncWithOsd(
|
||||
() => deps.triggerSubsyncFromConfig(),
|
||||
deps,
|
||||
"triggerSubsyncFromConfig",
|
||||
"Subsync failed",
|
||||
);
|
||||
} else if (args.markAudioCard) {
|
||||
runAsyncWithOsd(
|
||||
() => deps.markLastCardAsAudioCard(),
|
||||
deps,
|
||||
"markLastCardAsAudioCard",
|
||||
"Audio card failed",
|
||||
);
|
||||
} else if (args.openRuntimeOptions) {
|
||||
deps.openRuntimeOptionsPalette();
|
||||
} else if (args.texthooker) {
|
||||
const texthookerPort = deps.getTexthookerPort();
|
||||
deps.ensureTexthookerRunning(texthookerPort);
|
||||
if (deps.shouldOpenTexthookerBrowser()) {
|
||||
deps.openTexthookerInBrowser(`http://127.0.0.1:${texthookerPort}`);
|
||||
}
|
||||
deps.log(`Texthooker available at http://127.0.0.1:${texthookerPort}`);
|
||||
} else if (args.help) {
|
||||
deps.printHelp();
|
||||
if (!deps.hasMainWindow()) deps.stopApp();
|
||||
}
|
||||
}
|
||||
204
src/main.ts
204
src/main.ts
@@ -85,7 +85,6 @@ import {
|
||||
import {
|
||||
CliArgs,
|
||||
CliCommandSource,
|
||||
commandNeedsOverlayRuntime,
|
||||
hasExplicitCommand,
|
||||
parseArgs,
|
||||
shouldStartApp,
|
||||
@@ -117,6 +116,7 @@ import {
|
||||
import { runOverlayShortcutLocalFallback } from "./core/services/overlay-shortcut-fallback-runner";
|
||||
import { createOverlayShortcutRuntimeHandlers } from "./core/services/overlay-shortcut-runtime-service";
|
||||
import { createNumericShortcutSessionService } from "./core/services/numeric-shortcut-session-service";
|
||||
import { handleCliCommandService } from "./core/services/cli-command-service";
|
||||
import { showDesktopNotification } from "./core/utils/notification";
|
||||
import { openYomitanSettingsWindow } from "./core/services/yomitan-settings-service";
|
||||
import { tokenizeSubtitleService } from "./core/services/tokenizer-service";
|
||||
@@ -575,141 +575,73 @@ function handleCliCommand(
|
||||
args: CliArgs,
|
||||
source: CliCommandSource = "initial",
|
||||
): void {
|
||||
const hasNonStartAction =
|
||||
args.stop ||
|
||||
args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay ||
|
||||
args.settings ||
|
||||
args.show ||
|
||||
args.hide ||
|
||||
args.showVisibleOverlay ||
|
||||
args.hideVisibleOverlay ||
|
||||
args.showInvisibleOverlay ||
|
||||
args.hideInvisibleOverlay ||
|
||||
args.copySubtitle ||
|
||||
args.copySubtitleMultiple ||
|
||||
args.mineSentence ||
|
||||
args.mineSentenceMultiple ||
|
||||
args.updateLastCardFromClipboard ||
|
||||
args.toggleSecondarySub ||
|
||||
args.triggerFieldGrouping ||
|
||||
args.triggerSubsync ||
|
||||
args.markAudioCard ||
|
||||
args.openRuntimeOptions ||
|
||||
args.texthooker ||
|
||||
args.help;
|
||||
const ignoreStart = source === "second-instance" && args.start;
|
||||
if (ignoreStart && !hasNonStartAction) {
|
||||
console.log("Ignoring --start because SubMiner is already running.");
|
||||
return;
|
||||
}
|
||||
const shouldStart =
|
||||
!ignoreStart &&
|
||||
(args.start ||
|
||||
(source === "initial" &&
|
||||
(args.toggle ||
|
||||
args.toggleVisibleOverlay ||
|
||||
args.toggleInvisibleOverlay)));
|
||||
const needsOverlayRuntime = commandNeedsOverlayRuntime(args);
|
||||
|
||||
if (args.socketPath !== undefined) {
|
||||
mpvSocketPath = args.socketPath;
|
||||
if (mpvClient) {
|
||||
mpvClient.setSocketPath(mpvSocketPath);
|
||||
}
|
||||
}
|
||||
if (args.texthookerPort !== undefined) {
|
||||
if (texthookerService.isRunning()) {
|
||||
console.warn(
|
||||
"Ignoring --port override because the texthooker server is already running.",
|
||||
);
|
||||
} else {
|
||||
texthookerPort = args.texthookerPort;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.stop) {
|
||||
console.log("Stopping SubMiner...");
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (needsOverlayRuntime && !overlayRuntimeInitialized) {
|
||||
initializeOverlayRuntime();
|
||||
}
|
||||
|
||||
if (shouldStart && mpvClient) {
|
||||
mpvClient.setSocketPath(mpvSocketPath);
|
||||
mpvClient.connect();
|
||||
console.log(`Starting MPV IPC connection on socket: ${mpvSocketPath}`);
|
||||
}
|
||||
|
||||
if (args.toggle || args.toggleVisibleOverlay) {
|
||||
toggleVisibleOverlay();
|
||||
} else if (args.toggleInvisibleOverlay) {
|
||||
toggleInvisibleOverlay();
|
||||
} else if (args.settings) {
|
||||
setTimeout(() => {
|
||||
openYomitanSettings();
|
||||
}, 1000);
|
||||
} else if (args.show || args.showVisibleOverlay) {
|
||||
setVisibleOverlayVisible(true);
|
||||
} else if (args.hide || args.hideVisibleOverlay) {
|
||||
setVisibleOverlayVisible(false);
|
||||
} else if (args.showInvisibleOverlay) {
|
||||
setInvisibleOverlayVisible(true);
|
||||
} else if (args.hideInvisibleOverlay) {
|
||||
setInvisibleOverlayVisible(false);
|
||||
} else if (args.copySubtitle) {
|
||||
copyCurrentSubtitle();
|
||||
} else if (args.copySubtitleMultiple) {
|
||||
startPendingMultiCopy(getConfiguredShortcuts().multiCopyTimeoutMs);
|
||||
} else if (args.mineSentence) {
|
||||
mineSentenceCard().catch((err) => {
|
||||
console.error("mineSentenceCard failed:", err);
|
||||
showMpvOsd(`Mine sentence failed: ${(err as Error).message}`);
|
||||
});
|
||||
} else if (args.mineSentenceMultiple) {
|
||||
startPendingMineSentenceMultiple(getConfiguredShortcuts().multiCopyTimeoutMs);
|
||||
} else if (args.updateLastCardFromClipboard) {
|
||||
updateLastCardFromClipboard().catch((err) => {
|
||||
console.error("updateLastCardFromClipboard failed:", err);
|
||||
showMpvOsd(`Update failed: ${(err as Error).message}`);
|
||||
});
|
||||
} else if (args.toggleSecondarySub) {
|
||||
cycleSecondarySubMode();
|
||||
} else if (args.triggerFieldGrouping) {
|
||||
triggerFieldGrouping().catch((err) => {
|
||||
console.error("triggerFieldGrouping failed:", err);
|
||||
showMpvOsd(`Field grouping failed: ${(err as Error).message}`);
|
||||
});
|
||||
} else if (args.triggerSubsync) {
|
||||
triggerSubsyncFromConfig().catch((err) => {
|
||||
console.error("triggerSubsyncFromConfig failed:", err);
|
||||
showMpvOsd(`Subsync failed: ${(err as Error).message}`);
|
||||
});
|
||||
} else if (args.markAudioCard) {
|
||||
markLastCardAsAudioCard().catch((err) => {
|
||||
console.error("markLastCardAsAudioCard failed:", err);
|
||||
showMpvOsd(`Audio card failed: ${(err as Error).message}`);
|
||||
});
|
||||
} else if (args.openRuntimeOptions) {
|
||||
openRuntimeOptionsPalette();
|
||||
} else if (args.texthooker) {
|
||||
if (!texthookerService.isRunning()) {
|
||||
texthookerService.start(texthookerPort);
|
||||
}
|
||||
const config = getResolvedConfig();
|
||||
const openBrowser = config.texthooker?.openBrowser !== false;
|
||||
if (openBrowser) {
|
||||
shell.openExternal(`http://127.0.0.1:${texthookerPort}`);
|
||||
}
|
||||
console.log(`Texthooker available at http://127.0.0.1:${texthookerPort}`);
|
||||
} else if (args.help) {
|
||||
printHelp(DEFAULT_TEXTHOOKER_PORT);
|
||||
if (!mainWindow) app.quit();
|
||||
}
|
||||
handleCliCommandService(args, source, {
|
||||
getMpvSocketPath: () => mpvSocketPath,
|
||||
setMpvSocketPath: (socketPath) => {
|
||||
mpvSocketPath = socketPath;
|
||||
},
|
||||
setMpvClientSocketPath: (socketPath) => {
|
||||
if (!mpvClient) return;
|
||||
mpvClient.setSocketPath(socketPath);
|
||||
},
|
||||
hasMpvClient: () => Boolean(mpvClient),
|
||||
connectMpvClient: () => {
|
||||
if (!mpvClient) return;
|
||||
mpvClient.connect();
|
||||
},
|
||||
isTexthookerRunning: () => texthookerService.isRunning(),
|
||||
setTexthookerPort: (port) => {
|
||||
texthookerPort = port;
|
||||
},
|
||||
getTexthookerPort: () => texthookerPort,
|
||||
shouldOpenTexthookerBrowser: () =>
|
||||
getResolvedConfig().texthooker?.openBrowser !== false,
|
||||
ensureTexthookerRunning: (port) => {
|
||||
if (!texthookerService.isRunning()) {
|
||||
texthookerService.start(port);
|
||||
}
|
||||
},
|
||||
openTexthookerInBrowser: (url) => {
|
||||
shell.openExternal(url);
|
||||
},
|
||||
stopApp: () => app.quit(),
|
||||
isOverlayRuntimeInitialized: () => overlayRuntimeInitialized,
|
||||
initializeOverlayRuntime: () => initializeOverlayRuntime(),
|
||||
toggleVisibleOverlay: () => toggleVisibleOverlay(),
|
||||
toggleInvisibleOverlay: () => toggleInvisibleOverlay(),
|
||||
openYomitanSettingsDelayed: (delayMs) => {
|
||||
setTimeout(() => {
|
||||
openYomitanSettings();
|
||||
}, delayMs);
|
||||
},
|
||||
setVisibleOverlayVisible: (visible) => setVisibleOverlayVisible(visible),
|
||||
setInvisibleOverlayVisible: (visible) =>
|
||||
setInvisibleOverlayVisible(visible),
|
||||
copyCurrentSubtitle: () => copyCurrentSubtitle(),
|
||||
startPendingMultiCopy: (timeoutMs) => startPendingMultiCopy(timeoutMs),
|
||||
mineSentenceCard: () => mineSentenceCard(),
|
||||
startPendingMineSentenceMultiple: (timeoutMs) =>
|
||||
startPendingMineSentenceMultiple(timeoutMs),
|
||||
updateLastCardFromClipboard: () => updateLastCardFromClipboard(),
|
||||
cycleSecondarySubMode: () => cycleSecondarySubMode(),
|
||||
triggerFieldGrouping: () => triggerFieldGrouping(),
|
||||
triggerSubsyncFromConfig: () => triggerSubsyncFromConfig(),
|
||||
markLastCardAsAudioCard: () => markLastCardAsAudioCard(),
|
||||
openRuntimeOptionsPalette: () => openRuntimeOptionsPalette(),
|
||||
printHelp: () => printHelp(DEFAULT_TEXTHOOKER_PORT),
|
||||
hasMainWindow: () => Boolean(mainWindow),
|
||||
getMultiCopyTimeoutMs: () => getConfiguredShortcuts().multiCopyTimeoutMs,
|
||||
showMpvOsd: (text) => showMpvOsd(text),
|
||||
log: (message) => {
|
||||
console.log(message);
|
||||
},
|
||||
warn: (message) => {
|
||||
console.warn(message);
|
||||
},
|
||||
error: (message, err) => {
|
||||
console.error(message, err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleInitialArgs(): void {
|
||||
|
||||
Reference in New Issue
Block a user