Skip to content

Commit e159b3c

Browse files
nmoriHiroshiba
andauthored
サードパーティがエンジンへのアクセス情報を得るための設定書き出し機能 (#1765)
* 未対応エンジン追加時にリストが消える件(#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 <[email protected]>
1 parent b95c2a0 commit e159b3c

File tree

7 files changed

+178
-5
lines changed

7 files changed

+178
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ npm run electron:build
8989
```bash
9090
npm run test:unit
9191
npm run test-watch:unit # 監視モード
92+
npm run test:unit -- --update # スナップショットの更新
9293
```
9394

9495
### ブラウザ End to End テスト

docs/res/エンジン再起動シーケンス図.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ flowchart LR
1010
408243["各エンジン"] --> 927120["Vuex.GET_ONLY_ENGINE_INFOS"]
1111
927120 --> 512074["Vuex.POST_ENGINE_START"]
1212
subgraph 408243["各エンジン"]
13-
262932["SET_ENGINE_STATE(state=STARTING)"] --- 595264["back.RESTART_ENGINE"]
14-
595264 --- 920995["engine.restartEngine"]
13+
262932["SET_ENGINE_STATE(state=STARTING)"] --> 595264["back.RESTART_ENGINE"]
14+
595264 --> 920995["engine.restartEngine"]
15+
920995 --> 939785["runtimeInfo.setEngineInfos"]
16+
939785 --> 494722["runtimeInfo.exportFile"]
1517
end
1618
subgraph 512074["Vuex.POST_ENGINE_START"]
1719
623200["Vuex.GET_ALT_PORT_INFOS"] --> 225947["各エンジン"]

docs/res/起動シーケンス図.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ flowchart
1616
subgraph 332024["EditorHome.vue"]
1717
end
1818
subgraph 389651["back.start"]
19-
967432["engine.runEngineAll"] --> 733212
19+
321984["runtimeInfo.exportFile"] --> 733212
2020
subgraph 733212["back.createWindow"]
2121
613440["win.loadURL"]
2222
end
2323
subgraph 548965["launchEngines"]
2424
250263["store.get engineSettings"] --> 222321["store.set engineSettings"]
2525
870482["store.get registeredEngineDirs"] --> 250263
26-
222321 --> 967432
26+
222321 --> 967432["engine.runEngineAll"]
2727
656570["engine.fetchEngineInfos"] --> 870482
2828
110954["engine.initializeEngineInfosAndAltPortInfo"] --> 656570
29+
967432 --> 302398["runtimeInfo.setEngineInfos"]
30+
302398 --> 321984
2931
subgraph 656570["engine.fetchEngineInfos"]
3032
267019["engine.fetchAdditionalEngineInfos"]
3133
end

src/background.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import EngineManager from "./background/engineManager";
4444
import VvppManager, { isVvppFile } from "./background/vvppManager";
4545
import configMigration014 from "./background/configMigration014";
4646
import { failure, success } from "./type/result";
47+
import { RuntimeInfoManager } from "./background/RuntimeInfoManager";
4748
import { ipcMainHandle, ipcMainSend } from "@/electron/ipc";
4849
import { getConfigManager } from "@/background/electronConfig";
4950

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

164+
const runtimeInfoManager = new RuntimeInfoManager(
165+
path.join(app.getPath("userData"), "runtime-info.json"),
166+
app.getVersion()
167+
);
168+
163169
const configManager = getConfigManager();
164170

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

496502
await engineManager.runEngineAll();
503+
runtimeInfoManager.setEngineInfos(engineInfos);
504+
await runtimeInfoManager.exportFile();
497505
}
498506

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

771782
ipcMainHandle("OPEN_ENGINE_DIRECTORY", async (_, { engineId }) => {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* サードパーティ向けのランタイム情報を書き出す。
3+
* ランタイム情報には起動しているエンジンのURLなどが含まれる。
4+
*/
5+
6+
import fs from "fs";
7+
import AsyncLock from "async-lock";
8+
import log from "electron-log/main";
9+
import { EngineId, EngineInfo } from "@/type/preload";
10+
11+
/**
12+
* ランタイム情報書き出しに必要なEngineInfo
13+
*/
14+
export type EngineInfoForRuntimeInfo = Pick<
15+
EngineInfo,
16+
"uuid" | "host" | "name"
17+
>;
18+
19+
/**
20+
* 保存されるランタイム情報
21+
*/
22+
type RuntimeInfo = {
23+
formatVersion: number;
24+
appVersion: string;
25+
engineInfos: {
26+
uuid: EngineId;
27+
url: string;
28+
name: string;
29+
}[];
30+
};
31+
32+
/**
33+
* サードパーティ向けのランタイム情報を書き出す
34+
*/
35+
export class RuntimeInfoManager {
36+
private runtimeInfoPath: string;
37+
private appVersion: string;
38+
39+
constructor(runtimeInfoPath: string, appVersion: string) {
40+
this.runtimeInfoPath = runtimeInfoPath;
41+
this.appVersion = appVersion;
42+
}
43+
44+
/**
45+
* ファイルロック用のインスタンス
46+
*/
47+
private lock = new AsyncLock({
48+
timeout: 1000,
49+
});
50+
private lockKey = "write";
51+
52+
/**
53+
* ファイルフォーマットバージョン
54+
*/
55+
private fileFormatVersion = 1;
56+
57+
/**
58+
* エンジン情報(書き出し用に記憶)
59+
*/
60+
private engineInfos: EngineInfoForRuntimeInfo[] = [];
61+
62+
/**
63+
* エンジン情報を登録する
64+
*/
65+
public setEngineInfos(engineInfos: EngineInfoForRuntimeInfo[]) {
66+
this.engineInfos = engineInfos;
67+
}
68+
69+
/**
70+
* ランタイム情報ファイルを書き出す
71+
*/
72+
public async exportFile() {
73+
await this.lock.acquire(this.lockKey, async () => {
74+
log.info(
75+
`Runtime information file has been updated. : ${this.runtimeInfoPath}`
76+
);
77+
78+
// データ化
79+
const runtimeInfoFormatFor3rdParty: RuntimeInfo = {
80+
formatVersion: this.fileFormatVersion,
81+
appVersion: this.appVersion,
82+
engineInfos: this.engineInfos.map((engineInfo) => {
83+
return {
84+
uuid: engineInfo.uuid,
85+
url: engineInfo.host, // NOTE: 元のEngineInfo.hostにURLが入っている
86+
name: engineInfo.name,
87+
};
88+
}),
89+
};
90+
91+
// ファイル書き出し
92+
try {
93+
await fs.promises.writeFile(
94+
this.runtimeInfoPath,
95+
JSON.stringify(runtimeInfoFormatFor3rdParty) // FIXME: zod化する
96+
);
97+
} catch (e) {
98+
// ディスクの空き容量がない、他ツールからのファイルロック時をトラップ。
99+
// サードパーティ向けなのでVOICEVOX側には通知せず、エラー記録して継続
100+
log.error(`Failed to write file : ${e}`);
101+
}
102+
});
103+
}
104+
}

src/background/engineManager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export class EngineManager {
8282
this.defaultEngineDir = defaultEngineDir;
8383
this.vvppEngineDir = vvppEngineDir;
8484
this.onEngineProcessError = onEngineProcessError;
85-
8685
this.engineProcessContainers = {};
8786
}
8887

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { tmpdir } from "os";
2+
import { join } from "path";
3+
import fs from "fs";
4+
import { expect, test } from "vitest";
5+
import { EngineId } from "@/type/preload";
6+
7+
import { RuntimeInfoManager } from "@/background/RuntimeInfoManager";
8+
9+
test("想定通りのラインタイム情報が保存されている", async () => {
10+
const randomName = Math.random().toString(36).substring(7);
11+
const tempFilePath = join(tmpdir(), `runtime-info-${randomName}.json`);
12+
13+
const appVersion = "999.999.999";
14+
const runtimeInfoManager = new RuntimeInfoManager(tempFilePath, appVersion);
15+
16+
// エンジン情報
17+
runtimeInfoManager.setEngineInfos([
18+
{
19+
uuid: EngineId("00000000-0000-0000-0000-000000000001"),
20+
host: "https://example.com/engine1",
21+
name: "engine1",
22+
},
23+
{
24+
uuid: EngineId("00000000-0000-0000-0000-000000000002"),
25+
host: "https://example.com/engine2",
26+
name: "engine2",
27+
},
28+
]);
29+
30+
// ファイル書き出し
31+
await runtimeInfoManager.exportFile();
32+
33+
// ファイル読み込みしてスナップショットの比較
34+
// NOTE: スナップショットが変わった場合、破壊的変更ならformatVersionを上げる
35+
const savedRuntimeInfo = JSON.parse(fs.readFileSync(tempFilePath, "utf-8"));
36+
expect(savedRuntimeInfo).toMatchInlineSnapshot(`
37+
{
38+
"appVersion": "999.999.999",
39+
"engineInfos": [
40+
{
41+
"name": "engine1",
42+
"url": "https://example.com/engine1",
43+
"uuid": "00000000-0000-0000-0000-000000000001",
44+
},
45+
{
46+
"name": "engine2",
47+
"url": "https://example.com/engine2",
48+
"uuid": "00000000-0000-0000-0000-000000000002",
49+
},
50+
],
51+
"formatVersion": 1,
52+
}
53+
`);
54+
});

0 commit comments

Comments
 (0)