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

VVPPをデフォルトエンジンに指定可能にし、未インストール時にインストールするか聞くようにする #2270

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f1bc063
stash
Hiroshiba Sep 7, 2024
3310c6f
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Sep 20, 2024
06ac776
stash
Hiroshiba Sep 25, 2024
8237c8f
Merge remote-tracking branch 'upstream/main' into pr/Hiroshiba/2270
Hiroshiba Oct 6, 2024
674d116
とりあえずデフォルトエンジンをダウンロードできるようにした
Hiroshiba Oct 6, 2024
f5f705d
typo
Hiroshiba Oct 6, 2024
e2e3b88
docsを移動
Hiroshiba Oct 7, 2024
3b642aa
リファクタリング
Hiroshiba Oct 7, 2024
f6a51da
コントローラーに移す
Hiroshiba Oct 7, 2024
87e666f
fetchInfosToInstall
Hiroshiba Oct 7, 2024
2c6a974
typos
Hiroshiba Oct 7, 2024
187cc8a
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Oct 29, 2024
8884110
.envのデフォルトエンジン情報を読み出す部分を共通化する
Hiroshiba Oct 29, 2024
6e637ad
.
Hiroshiba Oct 29, 2024
1627f00
Merge branch 'loadEnvEngineInfosに切り出し' into 指定されているVVPPがなかったら確認後にインスト…
Hiroshiba Oct 29, 2024
6d56d0b
m
Hiroshiba Oct 29, 2024
a198568
typing
Hiroshiba Oct 29, 2024
51e6461
a
Hiroshiba Oct 29, 2024
c9d4edf
微調整
Hiroshiba Oct 30, 2024
e179306
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Oct 30, 2024
692f001
戻す
Hiroshiba Oct 30, 2024
ad41881
名称変更とか
Hiroshiba Oct 30, 2024
016e142
fetchAdditionalEngineInfosをfetchVvppEngineInfosとfetchRegisteredEngine…
Hiroshiba Oct 30, 2024
965cee5
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Nov 6, 2024
d9cc465
fetchDefaultEngineInfosをメソッド化
Hiroshiba Nov 6, 2024
c6f1d71
loadEngineInfoを中に移動
Hiroshiba Nov 6, 2024
cb5d716
要らない引数わたしを除去
Hiroshiba Nov 6, 2024
6e155bf
fetchAdditionalEngineInfosをなくす
Hiroshiba Nov 6, 2024
b74cce6
要らない処理を省いた
Hiroshiba Nov 6, 2024
166ea3d
refactor: エンジン情報管理のロジックを改善し、エンジン情報の取得メソッドを整理
Hiroshiba Nov 6, 2024
13e9a14
まだ要らなかった
Hiroshiba Nov 6, 2024
7c8c4f8
戻したり移動したり
Hiroshiba Nov 6, 2024
f7b17d6
移動はしない方が良さそう
Hiroshiba Nov 6, 2024
32a2f2e
FIXME要らなくなってた
Hiroshiba Nov 6, 2024
bacf688
ちょっとわかりやすく
Hiroshiba Nov 6, 2024
c0219f2
エンジン情報の取得に関するコメントを修正
Hiroshiba Nov 6, 2024
d8013ae
エンジン情報の取得に関するコメントを修正
Hiroshiba Nov 6, 2024
e903075
Merge branch 'main' into EngineInfoManagerのリファクタリング
Hiroshiba Nov 7, 2024
b2434d6
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Nov 7, 2024
02e758a
Merge remote-tracking branch 'origin/EngineInfoManagerのリファクタリング' into…
Hiroshiba Nov 7, 2024
687212c
逆だった
Hiroshiba Nov 7, 2024
40e180a
Merge branch 'main' into 指定されているVVPPがなかったら確認後にインストールし、使えるようにするようにする
Hiroshiba Nov 9, 2024
973a5a5
Merge remote-tracking branch 'upstream/main' into 指定されているVVPPがなかったら確認…
Hiroshiba Nov 10, 2024
cee971a
refactor: エンジンとパッケージ情報の取得メソッドを改善し、型定義を更新
Hiroshiba Nov 10, 2024
c054061
refactor: ダウンロード処理のエラーハンドリングを改善し、失敗時のファイル削除を追加
Hiroshiba Nov 10, 2024
b5756d2
refactor: getSuitableVariantをgetSuitablePackageInfoに名称変更し、関連する変数名を更新
Hiroshiba Nov 10, 2024
091df5f
docs: エンジン周りに関するドキュメントを削除
Hiroshiba Nov 10, 2024
fa29982
refactor: デフォルトエンジンのタイプチェックを追加し、パス以外の場合にエラーをスロー
Hiroshiba Nov 10, 2024
32f334c
Merge branch 'main' into 指定されているVVPPがなかったら確認後にインストールし、使えるようにするようにする
Hiroshiba Nov 18, 2024
9fa130e
コメント追記
Hiroshiba Nov 18, 2024
39b86d8
Merge branch 'main' into 指定されているVVPPがなかったら確認後にインストールし、使えるようにするようにする
Hiroshiba Nov 26, 2024
78c8b88
Add TODO comment for memory optimization
Hiroshiba Nov 26, 2024
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
3 changes: 3 additions & 0 deletions src/backend/browser/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { loadEnvEngineInfos } from "@/domain/defaultEngine/envEngineInfo";
import { type EngineInfo } from "@/type/preload";

const baseEngineInfo = loadEnvEngineInfos()[0];
if (baseEngineInfo.type != "path") {
throw new Error("default engine type must be path");
}

export const defaultEngine: EngineInfo = (() => {
const { protocol, hostname, port, pathname } = new URL(baseEngineInfo.host);
Expand Down
98 changes: 98 additions & 0 deletions src/backend/electron/engineAndVvppController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from "path";
import fs from "fs";
import log from "electron-log/main";
import { BrowserWindow, dialog } from "electron";

Expand All @@ -12,6 +14,13 @@ import {
engineSettingSchema,
EngineSettingType,
} from "@/type/preload";
import {
PackageInfo,
fetchLatestDefaultEngineInfo,
getSuitablePackageInfo,
} from "@/domain/defaultEngine/latetDefaultEngine";
import { loadEnvEngineInfos } from "@/domain/defaultEngine/envEngineInfo";
import { UnreachableError } from "@/type/utility";

/**
* エンジンとVVPP周りの処理の流れを制御するクラス。
Expand Down Expand Up @@ -131,6 +140,95 @@ export class EngineAndVvppController {
}
}

/**
* インストール可能なデフォルトエンジンの情報とパッケージの情報を取得する。
*/
async fetchInsallablePackageInfos(): Promise<
{ engineName: string; packageInfo: PackageInfo }[]
> {
// ダウンロード可能なVVPPのうち、未インストールのものを返す
const targetInfos = [];
for (const envEngineInfo of loadEnvEngineInfos()) {
if (envEngineInfo.type != "downloadVvpp") {
continue;
}

// 最新情報を取得
const latestUrl = envEngineInfo.latestUrl;
if (latestUrl == undefined) throw new Error("latestUrl is undefined");

const latestInfo = await fetchLatestDefaultEngineInfo(latestUrl);
if (latestInfo.formatVersion != 1) {
log.error(`Unsupported format version: ${latestInfo.formatVersion}`);
continue;
}

// 実行環境に合うパッケージを取得
const packageInfo = getSuitablePackageInfo(latestInfo);
log.info(`Latest default engine version: ${packageInfo.version}`);

// インストール済みだった場合はスキップ
// FIXME: より新しいバージョンがあれば更新できるようにする
if (this.engineInfoManager.hasEngineInfo(envEngineInfo.uuid)) {
log.info(`Default engine ${envEngineInfo.uuid} is already installed.`);
continue;
}

targetInfos.push({ engineName: envEngineInfo.name, packageInfo });
}

return targetInfos;
}

/** VVPPパッケージをダウンロードし、インストールする */
async downloadAndInstallVvppEngine(
downloadDir: string,
packageInfo: PackageInfo,
) {
if (packageInfo.packages.length === 0) {
throw new UnreachableError("No packages to download");
}

let failed = false;
const downloadedPaths: string[] = [];
try {
// ダウンロード
await Promise.all(
packageInfo.packages.map(async (p) => {
const { url, name, size } = p;

log.info(`Download ${name} from ${url}, size: ${size}`);
const res = await fetch(url);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

globalのfetchを使っていますがelectronのfetch(net.fetch)を使うべきだと思います。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

みた感じそこまで変わらなさそう?( https://www.electronjs.org/ja/docs/latest/tutorial/progress-bar が適用されるというわけでもなさそうだし)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sabonerune
ちなみにelectronのfetchを使うべき理由って何でしょうか 👀
聞いてる理由は単純に、知識がなくて違いがわからないためです!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hiroshiba
Chromiumの機能を使うためシステムのプロキシ検出などがあるようですがあまりかわらないかもしれません。
https://www.electronjs.org/ja/docs/latest/api/net

const buffer = await res.arrayBuffer();
if (failed) return; // 他のダウンロードが失敗していたら中断

const downloadPath = path.join(downloadDir, name);
await fs.promises.writeFile(downloadPath, Buffer.from(buffer)); // TODO: オンメモリじゃなくする
log.info(`Downloaded ${name} to ${downloadPath}`);

downloadedPaths.push(downloadPath);

// TODO: ハッシュチェック
}),
);

// インストール
await this.installVvppEngine(downloadedPaths[0]);
} catch (e) {
failed = true;
log.error(`Failed to download and install VVPP engine:`, e);
throw e;
} finally {
// ダウンロードしたファイルを削除
await Promise.all(
downloadedPaths.map(async (path) => {
log.info(`Delete downloaded file: ${path}`);
await fs.promises.unlink(path);
}),
);
}
}

/** エンジンの設定を更新し、保存する */
updateEngineSetting(engineId: EngineId, engineSetting: EngineSettingType) {
const engineSettings = this.configManager.get("engineSettings");
Expand Down
24 changes: 24 additions & 0 deletions src/backend/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,30 @@ app.on("ready", async () => {
}
}

// VVPPがデフォルトエンジンに指定されていたらインストールする
// NOTE: この機能は工事中。参照: https://github.com/VOICEVOX/voicevox/issues/1194
const packageInfos =
await engineAndVvppController.fetchInsallablePackageInfos();
for (const { engineName, packageInfo } of packageInfos) {
// インストールするか確認
const result = dialog.showMessageBoxSync(win, {
type: "info",
title: "デフォルトエンジンのインストール",
message: `${engineName} をインストールしますか?`,
buttons: ["インストール", "キャンセル"],
cancelId: 1,
});
if (result == 1) {
continue;
}

// ダウンロードしてインストールする
await engineAndVvppController.downloadAndInstallVvppEngine(
app.getPath("downloads"),
packageInfo,
);
}

// runEngineAllの前にVVPPを読み込む
let filePath: string | undefined;
if (process.platform === "darwin") {
Expand Down
51 changes: 31 additions & 20 deletions src/backend/electron/manager/engineInfoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import { AltPortInfos } from "@/store/type";
import { loadEnvEngineInfos } from "@/domain/defaultEngine/envEngineInfo";
import { failure, Result, success } from "@/type/result";

/** エンジンの情報を管理するクラス */
/** 利用可能なエンジンの情報を管理するクラス */
export class EngineInfoManager {
defaultEngineDir: string;
vvppEngineDir: string;

/** 代替ポート情報 */
public altPortInfos: AltPortInfos = {};

private envEngineInfos = loadEnvEngineInfos();

constructor(payload: { defaultEngineDir: string; vvppEngineDir: string }) {
this.defaultEngineDir = payload.defaultEngineDir;
this.vvppEngineDir = payload.vvppEngineDir;
Expand Down Expand Up @@ -74,28 +76,29 @@ export class EngineInfoManager {

/**
* .envにあるエンジンの情報を取得する。
* ダウンロードが必要なものは除外されている。
*/
private fetchEnvEngineInfos(): EngineInfo[] {
// TODO: envから直接ではなく、envに書いたengine_manifest.jsonから情報を得るようにする
const engines = loadEnvEngineInfos();

return engines.map((engineInfo) => {
const { protocol, hostname, port, pathname } = new URL(engineInfo.host);
return {
...engineInfo,
protocol,
hostname,
defaultPort: port,
pathname: pathname === "/" ? "" : pathname,
isDefault: true,
type: "path",
executionFilePath: path.resolve(engineInfo.executionFilePath),
path:
engineInfo.path == undefined
? undefined
: path.resolve(this.defaultEngineDir, engineInfo.path),
} satisfies EngineInfo;
});
return this.envEngineInfos
.filter((engineInfo) => engineInfo.type != "downloadVvpp")
.map((engineInfo) => {
const { protocol, hostname, port, pathname } = new URL(engineInfo.host);
return {
...engineInfo,
protocol,
hostname,
defaultPort: port,
pathname: pathname === "/" ? "" : pathname,
isDefault: true,
type: engineInfo.type,
executionFilePath: path.resolve(engineInfo.executionFilePath),
path:
engineInfo.path == undefined
? undefined
: path.resolve(this.defaultEngineDir, engineInfo.path),
} satisfies EngineInfo;
});
}

/**
Expand Down Expand Up @@ -178,6 +181,14 @@ export class EngineInfoManager {
return engineInfo;
}

/**
* 指定したエンジンの情報が存在するかどうかを判定する。
*/
hasEngineInfo(engineId: EngineId): boolean {
const engineInfos = this.fetchEngineInfos();
return engineInfos.some((engineInfo) => engineInfo.uuid === engineId);
}

/**
* エンジンのディレクトリを取得する。存在しない場合はエラーを返す。
*/
Expand Down
34 changes: 24 additions & 10 deletions src/domain/defaultEngine/envEngineInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,30 @@ import { z } from "zod";
import { engineIdSchema } from "@/type/preload";

/** .envに書くデフォルトエンジン情報のスキーマ */
export const envEngineInfoSchema = z.object({
uuid: engineIdSchema,
host: z.string(),
name: z.string(),
executionEnabled: z.boolean(),
executionFilePath: z.string(),
executionArgs: z.array(z.string()),
path: z.string().optional(),
});
export type EnvEngineInfoType = z.infer<typeof envEngineInfoSchema>;
const envEngineInfoSchema = z
.object({
uuid: engineIdSchema,
host: z.string(),
name: z.string(),
executionEnabled: z.boolean(),
executionArgs: z.array(z.string()),
})
.and(
z.union([
// エンジンをパス指定する場合
z.object({
type: z.literal("path").default("path"),
executionFilePath: z.string(),
path: z.string().optional(),
}),
// VVPPダウンロードする場合
z.object({
type: z.literal("downloadVvpp"),
latestUrl: z.string(),
}),
]),
);
type EnvEngineInfoType = z.infer<typeof envEngineInfoSchema>;

/** .envからデフォルトエンジン情報を読み込む */
export function loadEnvEngineInfos(): EnvEngineInfoType[] {
Expand Down
44 changes: 37 additions & 7 deletions src/domain/defaultEngine/latetDefaultEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { z } from "zod";

/** パッケージ情報のスキーマ */
const engineVariantSchema = z.object({
const packageInfoSchema = z.object({
version: z.string(),
packages: z
.object({
Expand All @@ -16,28 +16,29 @@ const engineVariantSchema = z.object({
})
.array(),
});
export type PackageInfo = z.infer<typeof packageInfoSchema>;

/** デフォルトエンジンの最新情報のスキーマ */
const latestDefaultEngineInfoSchema = z.object({
formatVersion: z.number(),
windows: z.object({
x64: z.object({
CPU: engineVariantSchema,
"GPU/CPU": engineVariantSchema,
CPU: packageInfoSchema,
"GPU/CPU": packageInfoSchema,
}),
}),
macos: z.object({
x64: z.object({
CPU: engineVariantSchema,
CPU: packageInfoSchema,
}),
arm64: z.object({
CPU: engineVariantSchema,
CPU: packageInfoSchema,
}),
}),
linux: z.object({
x64: z.object({
CPU: engineVariantSchema,
"GPU/CPU": engineVariantSchema,
CPU: packageInfoSchema,
"GPU/CPU": packageInfoSchema,
}),
}),
});
Expand All @@ -47,3 +48,32 @@ export const fetchLatestDefaultEngineInfo = async (url: string) => {
const response = await fetch(url);
return latestDefaultEngineInfoSchema.parse(await response.json());
};

/**
* 実行環境に合うパッケージを取得する。GPU版があればGPU版を返す。
* TODO: どのデバイス版にするかはユーザーが選べるようにするべき。
*/
export const getSuitablePackageInfo = (
updateInfo: z.infer<typeof latestDefaultEngineInfoSchema>,
): PackageInfo => {
const platform = process.platform;
const arch = process.arch;

if (platform === "win32") {
if (arch === "x64") {
return updateInfo.windows.x64["GPU/CPU"];
}
} else if (platform === "darwin") {
if (arch === "x64") {
return updateInfo.macos.x64.CPU;
} else if (arch === "arm64") {
return updateInfo.macos.arm64.CPU;
}
} else if (platform === "linux") {
if (arch === "x64") {
return updateInfo.linux.x64["GPU/CPU"];
}
}

throw new Error(`Unsupported platform: ${platform} ${arch}`);
};
Loading