forked from VOICEVOX/voicevox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
94995bf
commit bf25b03
Showing
2 changed files
with
359 additions
and
337 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}, | ||
}; | ||
} |
Oops, something went wrong.