Files
SubMiner/src/core/services/anki-jimaku-ipc.ts

175 lines
4.8 KiB
TypeScript

import { ipcMain, IpcMainEvent } from "electron";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { createLogger } from "../../logger";
import {
JimakuApiResponse,
JimakuDownloadQuery,
JimakuDownloadResult,
JimakuEntry,
JimakuFileEntry,
JimakuFilesQuery,
JimakuMediaInfo,
JimakuSearchQuery,
KikuFieldGroupingChoice,
KikuMergePreviewRequest,
KikuMergePreviewResponse,
} from "../../types";
const logger = createLogger("main:anki-jimaku-ipc");
export interface AnkiJimakuIpcDeps {
setAnkiConnectEnabled: (enabled: boolean) => void;
clearAnkiHistory: () => void;
refreshKnownWords: () => Promise<void> | void;
respondFieldGrouping: (choice: KikuFieldGroupingChoice) => void;
buildKikuMergePreview: (
request: KikuMergePreviewRequest,
) => Promise<KikuMergePreviewResponse>;
getJimakuMediaInfo: () => JimakuMediaInfo;
searchJimakuEntries: (
query: JimakuSearchQuery,
) => Promise<JimakuApiResponse<JimakuEntry[]>>;
listJimakuFiles: (
query: JimakuFilesQuery,
) => Promise<JimakuApiResponse<JimakuFileEntry[]>>;
resolveJimakuApiKey: () => Promise<string | null>;
getCurrentMediaPath: () => string | null;
isRemoteMediaPath: (mediaPath: string) => boolean;
downloadToFile: (
url: string,
destPath: string,
headers: Record<string, string>,
) => Promise<JimakuDownloadResult>;
onDownloadedSubtitle: (pathToSubtitle: string) => void;
}
export function registerAnkiJimakuIpcHandlers(deps: AnkiJimakuIpcDeps): void {
ipcMain.on(
"set-anki-connect-enabled",
(_event: IpcMainEvent, enabled: boolean) => {
deps.setAnkiConnectEnabled(enabled);
},
);
ipcMain.on("clear-anki-connect-history", () => {
deps.clearAnkiHistory();
});
ipcMain.on("anki:refresh-known-words", async () => {
await deps.refreshKnownWords();
});
ipcMain.on(
"kiku:field-grouping-respond",
(_event: IpcMainEvent, choice: KikuFieldGroupingChoice) => {
deps.respondFieldGrouping(choice);
},
);
ipcMain.handle(
"kiku:build-merge-preview",
async (
_event,
request: KikuMergePreviewRequest,
): Promise<KikuMergePreviewResponse> => {
return deps.buildKikuMergePreview(request);
},
);
ipcMain.handle("jimaku:get-media-info", (): JimakuMediaInfo => {
return deps.getJimakuMediaInfo();
});
ipcMain.handle(
"jimaku:search-entries",
async (
_event,
query: JimakuSearchQuery,
): Promise<JimakuApiResponse<JimakuEntry[]>> => {
return deps.searchJimakuEntries(query);
},
);
ipcMain.handle(
"jimaku:list-files",
async (
_event,
query: JimakuFilesQuery,
): Promise<JimakuApiResponse<JimakuFileEntry[]>> => {
return deps.listJimakuFiles(query);
},
);
ipcMain.handle(
"jimaku:download-file",
async (
_event,
query: JimakuDownloadQuery,
): Promise<JimakuDownloadResult> => {
const apiKey = await deps.resolveJimakuApiKey();
if (!apiKey) {
return {
ok: false,
error: {
error:
"Jimaku API key not set. Configure jimaku.apiKey or jimaku.apiKeyCommand.",
code: 401,
},
};
}
const currentMediaPath = deps.getCurrentMediaPath();
if (!currentMediaPath) {
return { ok: false, error: { error: "No media file loaded in MPV." } };
}
const mediaDir = deps.isRemoteMediaPath(currentMediaPath)
? fs.mkdtempSync(path.join(os.tmpdir(), "subminer-jimaku-"))
: path.dirname(path.resolve(currentMediaPath));
const safeName = path.basename(query.name);
if (!safeName) {
return { ok: false, error: { error: "Invalid subtitle filename." } };
}
const ext = path.extname(safeName);
const baseName = ext ? safeName.slice(0, -ext.length) : safeName;
let targetPath = path.join(mediaDir, safeName);
if (fs.existsSync(targetPath)) {
targetPath = path.join(
mediaDir,
`${baseName} (jimaku-${query.entryId})${ext}`,
);
let counter = 2;
while (fs.existsSync(targetPath)) {
targetPath = path.join(
mediaDir,
`${baseName} (jimaku-${query.entryId}-${counter})${ext}`,
);
counter += 1;
}
}
logger.info(
`[jimaku] download-file name="${query.name}" entryId=${query.entryId}`,
);
const result = await deps.downloadToFile(query.url, targetPath, {
Authorization: apiKey,
"User-Agent": "SubMiner",
});
if (result.ok) {
logger.info(`[jimaku] download-file saved to ${result.path}`);
deps.onDownloadedSubtitle(result.path);
} else {
logger.error(
`[jimaku] download-file failed: ${result.error?.error ?? "unknown error"}`,
);
}
return result;
},
);
}