Skip to content

Commit

Permalink
サードパーティがエンジンへのアクセス情報を得るための設定書き出し機能 (#1765)
Browse files Browse the repository at this point in the history
* 未対応エンジン追加時にリストが消える件(#1168)
・追加されたエンジンが未対応である場合には追加を阻止
・追加されてしまっている場合には、エラーで処理中断しないように

* lintチェックエラー部分の修正

* コードレビューの反映 (ref #1179)
・MinimumEngineManifestの更新

* コードレビュー分の反映② ref #1179
・engineManifests[selectedId]自体が undefined であるケースに対応

* サードパーティがエンジンへのアクセス情報を得るための設定書き出し機能(ref #1738)

* ファイルは runtime-info.json に書き出し
* エンジン全起動もしくは個別起動/終了のタイミングで更新

* * 関数名の変更 : writeEngineInfoFor3rdParty
* 排他ロックの追加
* 処理の非同期化

* * コンストラクタ引数でファイルパスを渡すように
* 関数をシンプルに
* ログメッセージ修正
* コメント位置修正

* * エクスポートファイパスを渡す所を引数にした
* 変数、関数名修正
* いくつかの構造をクラス化

* 議論 #1738 に基づき、最小項目の書き出しに変更

* * ファイル書き出しクラスに機能を集約
* 変数名、コメントの修正

* RuntimeInfoManager.tsをブラッシュアップ

* EngineManagerとRuntimeInfoManagerを疎結合に

* データ構造調整、テスト追加

* Apply suggestions from code review

---------

Co-authored-by: Hiroshiba <hihokaruta@gmail.com>
  • Loading branch information
nmori and Hiroshiba committed Feb 13, 2024
1 parent b95c2a0 commit e159b3c
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ npm run electron:build
```bash
npm run test:unit
npm run test-watch:unit # 監視モード
npm run test:unit -- --update # スナップショットの更新
```

### ブラウザ End to End テスト
Expand Down
6 changes: 4 additions & 2 deletions docs/res/エンジン再起動シーケンス図.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ flowchart LR
408243["各エンジン"] --> 927120["Vuex.GET_ONLY_ENGINE_INFOS"]
927120 --> 512074["Vuex.POST_ENGINE_START"]
subgraph 408243["各エンジン"]
262932["SET_ENGINE_STATE(state=STARTING)"] --- 595264["back.RESTART_ENGINE"]
595264 --- 920995["engine.restartEngine"]
262932["SET_ENGINE_STATE(state=STARTING)"] --> 595264["back.RESTART_ENGINE"]
595264 --> 920995["engine.restartEngine"]
920995 --> 939785["runtimeInfo.setEngineInfos"]
939785 --> 494722["runtimeInfo.exportFile"]
end
subgraph 512074["Vuex.POST_ENGINE_START"]
623200["Vuex.GET_ALT_PORT_INFOS"] --> 225947["各エンジン"]
Expand Down
6 changes: 4 additions & 2 deletions docs/res/起動シーケンス図.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ flowchart
subgraph 332024["EditorHome.vue"]
end
subgraph 389651["back.start"]
967432["engine.runEngineAll"] --> 733212
321984["runtimeInfo.exportFile"] --> 733212
subgraph 733212["back.createWindow"]
613440["win.loadURL"]
end
subgraph 548965["launchEngines"]
250263["store.get engineSettings"] --> 222321["store.set engineSettings"]
870482["store.get registeredEngineDirs"] --> 250263
222321 --> 967432
222321 --> 967432["engine.runEngineAll"]
656570["engine.fetchEngineInfos"] --> 870482
110954["engine.initializeEngineInfosAndAltPortInfo"] --> 656570
967432 --> 302398["runtimeInfo.setEngineInfos"]
302398 --> 321984
subgraph 656570["engine.fetchEngineInfos"]
267019["engine.fetchAdditionalEngineInfos"]
end
Expand Down
11 changes: 11 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import EngineManager from "./background/engineManager";
import VvppManager, { isVvppFile } from "./background/vvppManager";
import configMigration014 from "./background/configMigration014";
import { failure, success } from "./type/result";
import { RuntimeInfoManager } from "./background/RuntimeInfoManager";
import { ipcMainHandle, ipcMainSend } from "@/electron/ipc";
import { getConfigManager } from "@/background/electronConfig";

Expand Down Expand Up @@ -160,6 +161,11 @@ const onEngineProcessError = (engineInfo: EngineInfo, error: Error) => {
dialog.showErrorBox("音声合成エンジンエラー", error.message);
};

const runtimeInfoManager = new RuntimeInfoManager(
path.join(app.getPath("userData"), "runtime-info.json"),
app.getVersion()
);

const configManager = getConfigManager();

const engineManager = new EngineManager({
Expand Down Expand Up @@ -494,6 +500,8 @@ async function launchEngines() {
configManager.set("engineSettings", engineSettings);

await engineManager.runEngineAll();
runtimeInfoManager.setEngineInfos(engineInfos);
await runtimeInfoManager.exportFile();
}

/**
Expand Down Expand Up @@ -766,6 +774,9 @@ ipcMainHandle("ENGINE_INFOS", () => {
*/
ipcMainHandle("RESTART_ENGINE", async (_, { engineId }) => {
await engineManager.restartEngine(engineId);
// TODO: setEngineInfosからexportFileはロックしたほうがより良い
runtimeInfoManager.setEngineInfos(engineManager.fetchEngineInfos());
await runtimeInfoManager.exportFile();
});

ipcMainHandle("OPEN_ENGINE_DIRECTORY", async (_, { engineId }) => {
Expand Down
104 changes: 104 additions & 0 deletions src/background/RuntimeInfoManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* サードパーティ向けのランタイム情報を書き出す。
* ランタイム情報には起動しているエンジンのURLなどが含まれる。
*/

import fs from "fs";
import AsyncLock from "async-lock";
import log from "electron-log/main";
import { EngineId, EngineInfo } from "@/type/preload";

/**
* ランタイム情報書き出しに必要なEngineInfo
*/
export type EngineInfoForRuntimeInfo = Pick<
EngineInfo,
"uuid" | "host" | "name"
>;

/**
* 保存されるランタイム情報
*/
type RuntimeInfo = {
formatVersion: number;
appVersion: string;
engineInfos: {
uuid: EngineId;
url: string;
name: string;
}[];
};

/**
* サードパーティ向けのランタイム情報を書き出す
*/
export class RuntimeInfoManager {
private runtimeInfoPath: string;
private appVersion: string;

constructor(runtimeInfoPath: string, appVersion: string) {
this.runtimeInfoPath = runtimeInfoPath;
this.appVersion = appVersion;
}

/**
* ファイルロック用のインスタンス
*/
private lock = new AsyncLock({
timeout: 1000,
});
private lockKey = "write";

/**
* ファイルフォーマットバージョン
*/
private fileFormatVersion = 1;

/**
* エンジン情報(書き出し用に記憶)
*/
private engineInfos: EngineInfoForRuntimeInfo[] = [];

/**
* エンジン情報を登録する
*/
public setEngineInfos(engineInfos: EngineInfoForRuntimeInfo[]) {
this.engineInfos = engineInfos;
}

/**
* ランタイム情報ファイルを書き出す
*/
public async exportFile() {
await this.lock.acquire(this.lockKey, async () => {
log.info(
`Runtime information file has been updated. : ${this.runtimeInfoPath}`
);

// データ化
const runtimeInfoFormatFor3rdParty: RuntimeInfo = {
formatVersion: this.fileFormatVersion,
appVersion: this.appVersion,
engineInfos: this.engineInfos.map((engineInfo) => {
return {
uuid: engineInfo.uuid,
url: engineInfo.host, // NOTE: 元のEngineInfo.hostにURLが入っている
name: engineInfo.name,
};
}),
};

// ファイル書き出し
try {
await fs.promises.writeFile(
this.runtimeInfoPath,
JSON.stringify(runtimeInfoFormatFor3rdParty) // FIXME: zod化する
);
} catch (e) {
// ディスクの空き容量がない、他ツールからのファイルロック時をトラップ。
// サードパーティ向けなのでVOICEVOX側には通知せず、エラー記録して継続
log.error(`Failed to write file : ${e}`);
}
});
}
}
1 change: 0 additions & 1 deletion src/background/engineManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export class EngineManager {
this.defaultEngineDir = defaultEngineDir;
this.vvppEngineDir = vvppEngineDir;
this.onEngineProcessError = onEngineProcessError;

this.engineProcessContainers = {};
}

Expand Down
54 changes: 54 additions & 0 deletions tests/unit/background/RuntimeInfo.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { tmpdir } from "os";
import { join } from "path";
import fs from "fs";
import { expect, test } from "vitest";
import { EngineId } from "@/type/preload";

import { RuntimeInfoManager } from "@/background/RuntimeInfoManager";

test("想定通りのラインタイム情報が保存されている", async () => {
const randomName = Math.random().toString(36).substring(7);
const tempFilePath = join(tmpdir(), `runtime-info-${randomName}.json`);

const appVersion = "999.999.999";
const runtimeInfoManager = new RuntimeInfoManager(tempFilePath, appVersion);

// エンジン情報
runtimeInfoManager.setEngineInfos([
{
uuid: EngineId("00000000-0000-0000-0000-000000000001"),
host: "https://example.com/engine1",
name: "engine1",
},
{
uuid: EngineId("00000000-0000-0000-0000-000000000002"),
host: "https://example.com/engine2",
name: "engine2",
},
]);

// ファイル書き出し
await runtimeInfoManager.exportFile();

// ファイル読み込みしてスナップショットの比較
// NOTE: スナップショットが変わった場合、破壊的変更ならformatVersionを上げる
const savedRuntimeInfo = JSON.parse(fs.readFileSync(tempFilePath, "utf-8"));
expect(savedRuntimeInfo).toMatchInlineSnapshot(`
{
"appVersion": "999.999.999",
"engineInfos": [
{
"name": "engine1",
"url": "https://example.com/engine1",
"uuid": "00000000-0000-0000-0000-000000000001",
},
{
"name": "engine2",
"url": "https://example.com/engine2",
"uuid": "00000000-0000-0000-0000-000000000002",
},
],
"formatVersion": 1,
}
`);
});

0 comments on commit e159b3c

Please sign in to comment.