Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: getIpcMainHandle を追加 #2563

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 351 additions & 0 deletions src/backend/electron/ipcMainHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
import fs from "fs";
import path from "path";
import { app, nativeTheme, shell } from "electron";
import { hasSupportedGpu } from "./device";
import { getConfigManager } from "./electronConfig";
import { getEngineAndVvppController } from "./engineAndVvppController";
import { writeFileSafely } from "./fileHelper";
import { IpcMainHandle } from "./ipc";
import { getEngineInfoManager } from "./manager/engineInfoManager";
import { getEngineProcessManager } from "./manager/engineProcessManager";
import { getWindowManager } from "./manager/windowManager";
import { AssetTextFileNames } from "@/type/staticResources";
import { failure, success } from "@/type/result";
import {
defaultToolbarButtonSetting,
EngineId,
SystemError,
TextAsset,
} from "@/type/preload";

// エンジンのフォルダを開く
function openEngineDirectory(engineId: EngineId) {
const engineDirectory = getEngineInfoManager().fetchEngineDirectory(engineId);

// Windows環境だとスラッシュ区切りのパスが動かない。
// path.resolveはWindowsだけバックスラッシュ区切りにしてくれるため、path.resolveを挟む。
void shell.openPath(path.resolve(engineDirectory));
}

/**
* 保存に適した場所を選択するかキャンセルするまでダイアログを繰り返し表示する。
* アンインストール等で消えうる場所などを避ける。
* @param showDialogFunction ダイアログを表示する関数
*/
async function retryShowSaveDialogWhileSafeDir<
T extends Electron.OpenDialogReturnValue | Electron.SaveDialogReturnValue,
>(showDialogFunction: () => Promise<T>, appDirPath: string): Promise<T> {
/**
* 指定されたパスが安全でないかどうかを判断する
*/
const isUnsafePath = (filePath: string) => {
const unsafeSaveDirs = [appDirPath, app.getPath("userData")]; // アンインストールで消えうるフォルダ
return unsafeSaveDirs.some((unsafeDir) => {
const relativePath = path.relative(unsafeDir, filePath);
return !(
path.isAbsolute(relativePath) ||
relativePath.startsWith(`..${path.sep}`) ||
relativePath === ".."
);
});
};

/**
* 警告ダイアログを表示し、ユーザーが再試行を選択したかどうかを返す
*/
const showWarningDialog = async () => {
const windowManager = getWindowManager();
const productName = app.getName().toUpperCase();
const warningResult = await windowManager.showMessageBox({
message: `指定された保存先は${productName}により自動的に削除される可能性があります。\n他の場所に保存することをおすすめします。`,
type: "warning",
buttons: ["保存場所を変更", "無視して保存"],
defaultId: 0,
title: "警告",
cancelId: 0,
});
return warningResult.response === 0 ? "retry" : "forceSave";
};

while (true) {
const result = await showDialogFunction();
// キャンセルされた場合、結果を直ちに返す
if (result.canceled) return result;

// 選択されたファイルパスを取得
const filePath =
"filePaths" in result ? result.filePaths[0] : result.filePath;

// 選択されたパスが安全かどうかを確認
if (isUnsafePath(filePath)) {
const result = await showWarningDialog();
if (result === "retry") continue; // ユーザーが保存場所を変更を選択した場合
}
return result; // 安全なパスが選択された場合
}
}

export function getIpcMainHandle(
appStateGetter: () => { willQuit: boolean },
staticDirPath: string,
appDirPath: string,
initialFilePath: string | undefined,
): IpcMainHandle {
const configManager = getConfigManager();
const engineAndVvppController = getEngineAndVvppController();
const engineInfoManager = getEngineInfoManager();
const engineProcessManager = getEngineProcessManager();
const windowManager = getWindowManager();
return {
GET_TEXT_ASSET: async (_, textType) => {
const fileName = path.join(staticDirPath, AssetTextFileNames[textType]);
const text = await fs.promises.readFile(fileName, "utf-8");
if (textType === "OssLicenses" || textType === "UpdateInfos") {
return JSON.parse(text) as TextAsset[typeof textType];
}
return text;
},

GET_ALT_PORT_INFOS: () => {
return engineInfoManager.altPortInfos;
},

GET_INITIAL_PROJECT_FILE_PATH: async () => {
if (initialFilePath && initialFilePath.endsWith(".vvproj")) {
return initialFilePath;
}
},

/**
* 保存先になるディレクトリを選ぶダイアログを表示する。
*/
SHOW_SAVE_DIRECTORY_DIALOG: async (_, { title }) => {
const result = await retryShowSaveDialogWhileSafeDir(
() =>
windowManager.showOpenDialog({
title,
properties: [
"openDirectory",
"createDirectory",
"treatPackageAsDirectory",
],
}),
appDirPath,
);
if (result.canceled) {
return undefined;
}
return result.filePaths[0];
},

/**
* ディレクトリ選択ダイアログを表示する。
* 保存先として選ぶ場合は SHOW_SAVE_DIRECTORY_DIALOG を使うべき。
*/
SHOW_OPEN_DIRECTORY_DIALOG: async (_, { title }) => {
const result = await windowManager.showOpenDialog({
title,
properties: [
"openDirectory",
"createDirectory",
"treatPackageAsDirectory",
],
});
if (result.canceled) {
return undefined;
}
return result.filePaths[0];
},

SHOW_WARNING_DIALOG: (_, { title, message }) => {
return windowManager.showMessageBox({
type: "warning",
title,
message,
});
},

SHOW_ERROR_DIALOG: (_, { title, message }) => {
return windowManager.showMessageBox({
type: "error",
title,
message,
});
},

SHOW_OPEN_FILE_DIALOG: (_, { title, name, extensions, defaultPath }) => {
return windowManager.showOpenDialogSync({
title,
defaultPath,
filters: [{ name, extensions }],
properties: ["openFile", "createDirectory", "treatPackageAsDirectory"],
})?.[0];
},

SHOW_SAVE_FILE_DIALOG: async (
_,
{ title, defaultPath, name, extensions },
) => {
const result = await retryShowSaveDialogWhileSafeDir(
() =>
windowManager.showSaveDialog({
title,
defaultPath,
filters: [{ name, extensions }],
properties: ["createDirectory"],
}),
appDirPath,
);
if (result.canceled) {
return undefined;
}
return result.filePath;
},

IS_AVAILABLE_GPU_MODE: () => {
return hasSupportedGpu(process.platform);
},

IS_MAXIMIZED_WINDOW: () => {
return windowManager.isMaximized();
},

CLOSE_WINDOW: () => {
const appState = appStateGetter();
appState.willQuit = true;
windowManager.destroyWindow();
},

MINIMIZE_WINDOW: () => {
windowManager.minimize();
},

TOGGLE_MAXIMIZE_WINDOW: () => {
windowManager.toggleMaximizeWindow();
},

TOGGLE_FULLSCREEN: () => {
windowManager.toggleFullScreen();
},

/** UIの拡大 */
ZOOM_IN: () => {
windowManager.zoomIn();
},

/** UIの縮小 */
ZOOM_OUT: () => {
windowManager.zoomOut();
},

/** UIの拡大率リセット */
ZOOM_RESET: () => {
windowManager.zoomReset();
},

OPEN_LOG_DIRECTORY: () => {
void shell.openPath(app.getPath("logs"));
},

ENGINE_INFOS: () => {
// エンジン情報を設定ファイルに保存しないためにelectron-storeは使わない
return engineInfoManager.fetchEngineInfos();
},

RESTART_ENGINE: async (_, { engineId }) => {
return engineProcessManager.restartEngine(engineId);
},

OPEN_ENGINE_DIRECTORY: async (_, { engineId }) => {
openEngineDirectory(engineId);
},

HOTKEY_SETTINGS: (_, { newData }) => {
if (newData != undefined) {
const hotkeySettings = configManager.get("hotkeySettings");
const hotkeySetting = hotkeySettings.find(
(hotkey) => hotkey.action == newData.action,
);
if (hotkeySetting != undefined) {
hotkeySetting.combination = newData.combination;
}
configManager.set("hotkeySettings", hotkeySettings);
}
return configManager.get("hotkeySettings");
},

ON_VUEX_READY: () => {
windowManager.show();
},

CHECK_FILE_EXISTS: (_, { file }) => {
return fs.existsSync(file);
},

CHANGE_PIN_WINDOW: () => {
windowManager.togglePinWindow();
},

GET_DEFAULT_TOOLBAR_SETTING: () => {
return defaultToolbarButtonSetting;
},

GET_SETTING: (_, key) => {
return configManager.get(key);
},

SET_SETTING: (_, key, newValue) => {
configManager.set(key, newValue);
return configManager.get(key);
},

SET_ENGINE_SETTING: async (_, engineId, engineSetting) => {
engineAndVvppController.updateEngineSetting(engineId, engineSetting);
},

SET_NATIVE_THEME: (_, source) => {
nativeTheme.themeSource = source;
},

INSTALL_VVPP_ENGINE: async (_, path: string) => {
return await engineAndVvppController.installVvppEngine(path);
},

UNINSTALL_VVPP_ENGINE: async (_, engineId: EngineId) => {
return await engineAndVvppController.uninstallVvppEngine(engineId);
},

VALIDATE_ENGINE_DIR: (_, { engineDir }) => {
return engineInfoManager.validateEngineDir(engineDir);
},

RELOAD_APP: async (_, { isMultiEngineOffMode }) => {
await windowManager.reload(isMultiEngineOffMode);
},

WRITE_FILE: (_, { filePath, buffer }) => {
try {
writeFileSafely(
filePath,
new DataView(buffer instanceof Uint8Array ? buffer.buffer : buffer),
);
return success(undefined);
} catch (e) {
// throwだと`.code`の情報が消えるのでreturn
const a = e as SystemError;
return failure(a.code, a);
}
},

READ_FILE: async (_, { filePath }) => {
try {
const result = await fs.promises.readFile(filePath);
return success(result);
} catch (e) {
// throwだと`.code`の情報が消えるのでreturn
const a = e as SystemError;
return failure(a.code, a);
}
},
};
}
Loading
Loading