Skip to content

Commit

Permalink
Change: ブラウザ版のConfigをBaseConfigManagerベースに (#1629)
Browse files Browse the repository at this point in the history
Co-authored-by: Hiroshiba <[email protected]>
  • Loading branch information
sevenc-nanashi and Hiroshiba authored Nov 13, 2023
1 parent aee7d1f commit 8f2099c
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 127 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
e2e-test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
Expand Down Expand Up @@ -78,9 +79,10 @@ jobs:
chmod +x ${{ steps.download-engine.outputs.run_path }}
# .env
sed -i -e 's|"074fc39e-678b-4c13-8916-ffca8d505d1d"|"208cf94d-43d2-4cf5-abc0-9783cac36d29"|' .env.test
sed -i -e 's|"../voicevox_engine/run.exe"|"${{ steps.download-engine.outputs.run_path }}"|' .env.test
sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port=50021"],|' .env.test
cp .env.test .env
sed -i -e 's|"../voicevox_engine/run.exe"|"${{ steps.download-engine.outputs.run_path }}"|' .env
sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port=50021"],|' .env
- name: Run npm run test:browser-e2e
run: |
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ if (isElectron) {
const config: PlaywrightTestConfig = {
testDir: "./tests/e2e",
/* Maximum time one test can run for. */
timeout: 120 * 1000,
timeout: 60 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
Expand Down
1 change: 1 addition & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ async function launchEngines() {
// エンジンの追加と削除を反映させるためEngineInfoとAltPortInfoを再生成する。
engineManager.initializeEngineInfosAndAltPortInfo();

// TODO: デフォルトエンジンの処理をConfigManagerに移してブラウザ版と共通化する
const engineInfos = engineManager.fetchEngineInfos();
const engineSettings = configManager.get("engineSettings");
for (const engineInfo of engineInfos) {
Expand Down
8 changes: 4 additions & 4 deletions src/background/electronConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import { BaseConfigManager, Metadata } from "@/shared/ConfigManager";
import { ConfigType } from "@/type/preload";

export class ElectronConfigManager extends BaseConfigManager {
getAppVersion() {
protected getAppVersion() {
return app.getVersion();
}

public async exists() {
protected async exists() {
return await fs.promises
.stat(this.configPath)
.then(() => true)
.catch(() => false);
}

public async load(): Promise<Record<string, unknown> & Metadata> {
protected async load(): Promise<Record<string, unknown> & Metadata> {
return JSON.parse(await fs.promises.readFile(this.configPath, "utf-8"));
}

public async save(config: ConfigType) {
protected async save(config: ConfigType & Metadata) {
await fs.promises.writeFile(
this.configPath,
JSON.stringify(config, undefined, 2)
Expand Down
15 changes: 2 additions & 13 deletions src/background/engineManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import shlex from "shlex";
import { app, dialog } from "electron"; // FIXME: ここでelectronをimportするのは良くない

import log from "electron-log/main";
import { z } from "zod";
import {
findAltPort,
getPidFromPort,
Expand All @@ -21,8 +20,8 @@ import {
EngineDirValidationResult,
MinimumEngineManifest,
EngineId,
engineIdSchema,
minimumEngineManifestSchema,
envEngineInfoSchema,
} from "@/type/preload";
import { AltPortInfos } from "@/store/type";
import { BaseConfigManager } from "@/shared/ConfigManager";
Expand All @@ -40,17 +39,7 @@ function createDefaultEngineInfos(defaultEngineDir: string): EngineInfo[] {
const defaultEngineInfosEnv =
import.meta.env.VITE_DEFAULT_ENGINE_INFOS ?? "[]";

const envSchema = 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(),
})
.array();
const envSchema = envEngineInfoSchema.array();
const engines = envSchema.parse(JSON.parse(defaultEngineInfosEnv));

return engines.map((engineInfo) => {
Expand Down
15 changes: 6 additions & 9 deletions src/browser/contract.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { EngineInfo, EngineId } from "@/type/preload";
import { EngineInfo, envEngineInfoSchema } from "@/type/preload";

const baseEngineInfo = envEngineInfoSchema
.array()
.parse(JSON.parse(import.meta.env.VITE_DEFAULT_ENGINE_INFOS))[0];

export const defaultEngine: EngineInfo = {
uuid: EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"),
host: "http://127.0.0.1:50021",
name: "VOICEVOX Engine",
path: undefined,
executionEnabled: false,
executionFilePath: "",
executionArgs: [],
...baseEngineInfo,
type: "default",
};

export const directoryHandleStoreKey = "directoryHandle";
6 changes: 3 additions & 3 deletions src/browser/fileImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const showWritableDirectoryPicker = async (): Promise<
export const showOpenDirectoryDialogImpl: typeof window[typeof SandboxKey]["showOpenDirectoryDialog"] =
async () => {
const _directoryHandler = await showWritableDirectoryPicker();
if (_directoryHandler === undefined) {
if (_directoryHandler == undefined) {
return undefined;
}

Expand All @@ -83,15 +83,15 @@ const getDirectoryHandleFromDirectoryPath = async (
): Promise<FileSystemDirectoryHandle> => {
const maybeHandle = directoryHandleMap.get(maybeDirectoryPathKey);

if (maybeHandle !== undefined) {
if (maybeHandle != undefined) {
return maybeHandle;
} else {
// NOTE: fixedDirectoryの場合こっちに落ちる場合がある
const maybeFixedDirectory = await fetchStoredDirectoryHandle(
maybeDirectoryPathKey
);

if (maybeFixedDirectory === undefined) {
if (maybeFixedDirectory == undefined) {
throw new Error(
`フォルダへのアクセス許可がありません。アクセスしようとしたフォルダ名: ${maybeDirectoryPathKey}`
);
Expand Down
13 changes: 8 additions & 5 deletions src/browser/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
showOpenDirectoryDialogImpl,
writeFileImpl,
} from "./fileImpl";
import { getSettingEntry, setSettingEntry } from "./storeImpl";
import { getConfigManager } from "./storeImpl";

import { IpcSOData } from "@/type/ipc";
import {
Expand Down Expand Up @@ -285,11 +285,14 @@ export const api: Sandbox = {
// NOTE: 何もしなくて良さそう
return Promise.resolve();
},
getSetting(key) {
return getSettingEntry(key);
async getSetting(key) {
const configManager = await getConfigManager();
return configManager.get(key);
},
setSetting(key, newValue) {
return setSettingEntry(key, newValue).then(() => getSettingEntry(key));
async setSetting(key, newValue) {
const configManager = await getConfigManager();
configManager.set(key, newValue);
return newValue;
},
async setEngineSetting(engineId: EngineId, engineSetting: EngineSetting) {
const engineSettings = (await this.getSetting(
Expand Down
141 changes: 82 additions & 59 deletions src/browser/storeImpl.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import AsyncLock from "async-lock";
import { defaultEngine, directoryHandleStoreKey } from "./contract";

import {
configSchema,
ConfigType,
EngineId,
engineSettingSchema,
} from "@/type/preload";
import { BaseConfigManager, Metadata } from "@/shared/ConfigManager";
import { ConfigType, EngineId, engineSettingSchema } from "@/type/preload";

const dbName = `${import.meta.env.VITE_APP_NAME}-web`;
const settingStoreKey = "electronStore";
// FIXME: DBのschemaを変更したら、dbVersionを上げる
// TODO: 気づけるようにしたい
const dbVersion = 1;
const settingStoreKey = "config";
const dbVersion = 1; // 固定値。configのmigrationには使用していない。
// NOTE: settingを複数持つことはないと仮定して、keyを固定してしまう
export const entryKey = "value";
const entryKey = "value";

let configManager: BrowserConfigManager | undefined;

const configManagerLock = new AsyncLock();
const defaultEngineId = EngineId(defaultEngine.uuid);

export async function getConfigManager() {
await configManagerLock.acquire("configManager", async () => {
if (!configManager) {
configManager = new BrowserConfigManager();
await configManager.initialize();
}
});

if (!configManager) {
throw new Error("configManager is undefined");
}

return configManager;
}

const waitRequest = (request: IDBRequest) =>
new Promise<void>((resolve, reject) => {
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(request.error);
};
});

export const openDB = () =>
new Promise<IDBDatabase>((resolve, reject) => {
Expand All @@ -29,20 +54,15 @@ export const openDB = () =>
if (ev.oldVersion === 0) {
// Initialize
const db = request.result;
const baseSchema = configSchema.parse({});

const defaultVoicevoxEngineId = EngineId(defaultEngine.uuid);
baseSchema.engineSettings = {
[defaultVoicevoxEngineId]: engineSettingSchema.parse({}),
};
db.createObjectStore(settingStoreKey).add(baseSchema, entryKey);
db.createObjectStore(settingStoreKey);

// NOTE: fixedExportDirectoryを使用してファイルの書き出しをする際、
// audio.tsの現在の実装では、ディレクトリを選択するモーダルを表示しないようになっている
// ディレクトリへの書き出し権限の要求は、モーダルの表示かディレクトリを指定したファイルの書き出しの時のみで、
// directoryHandleがないと権限の要求が出来ないため、directoryHandleを永続化しておく
db.createObjectStore(directoryHandleStoreKey);
} else if (ev.newVersion !== null && ev.newVersion > ev.oldVersion) {
} else if (ev.newVersion != null && ev.newVersion > ev.oldVersion) {
// TODO: migrate
/* eslint-disable no-console */ // logger みたいなパッケージに切り出して、それに依存する形でもいいかも
console.error(
Expand All @@ -53,49 +73,52 @@ export const openDB = () =>
};
});

export const setSettingEntry = async <Key extends keyof ConfigType>(
key: Key,
newValue: ConfigType[Key]
) => {
const db = await openDB();

// TODO: Schemaに合っているか保存時にvalidationしたい
return new Promise((resolve, reject) => {
const transaction = db.transaction(settingStoreKey, "readwrite");
const store = transaction.objectStore(settingStoreKey);
const getRequest = store.get(entryKey);
getRequest.onsuccess = () => {
const baseSchema = configSchema.parse(getRequest.result);
baseSchema[key] = newValue;
const validatedSchema = configSchema.parse(baseSchema);
const putRequest = store.put(validatedSchema, entryKey);
putRequest.onsuccess = () => {
resolve(putRequest.result);
};
putRequest.onerror = () => {
reject(putRequest.error);
};
};
getRequest.onerror = () => {
reject(getRequest.error);
};
});
};
class BrowserConfigManager extends BaseConfigManager {
protected getAppVersion() {
return import.meta.env.VITE_APP_VERSION;
}
protected async exists() {
const db = await openDB();

export const getSettingEntry = async <Key extends keyof ConfigType>(
key: Key
): Promise<ConfigType[Key]> => {
const db = await openDB();
try {
const transaction = db.transaction(settingStoreKey, "readonly");
const store = transaction.objectStore(settingStoreKey);
const request = store.get(entryKey);
await waitRequest(request);
const result = request.result;
return result != undefined;
} catch (e) {
return false;
}
}
protected async load(): Promise<Record<string, unknown> & Metadata> {
const db = await openDB();

return new Promise((resolve, reject) => {
const transaction = db.transaction(settingStoreKey, "readonly");
const store = transaction.objectStore(settingStoreKey);
const request = store.get(entryKey);
request.onsuccess = () => {
resolve(request.result[key]);
};
request.onerror = () => {
reject(request.error);
};
});
};
await waitRequest(request);
const result = request.result;
if (result == undefined) {
throw new Error("設定ファイルが見つかりません");
}
return JSON.parse(result);
}

protected async save(data: ConfigType & Metadata) {
const db = await openDB();

const transaction = db.transaction(settingStoreKey, "readwrite");
const store = transaction.objectStore(settingStoreKey);
const request = store.put(JSON.stringify(data), entryKey);
await waitRequest(request);
}

protected getDefaultConfig() {
const baseConfig = super.getDefaultConfig();
baseConfig.engineSettings[defaultEngineId] ??= engineSettingSchema.parse(
{}
);
return baseConfig;
}
}
16 changes: 10 additions & 6 deletions src/components/SettingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1087,21 +1087,25 @@ const changeShowAddAudioItemButton = async (
const canSetAudioOutputDevice = computed(() => {
return !!HTMLAudioElement.prototype.setSinkId;
});
const currentAudioOutputDeviceComputed = computed<{
key: string;
label: string;
} | null>({
const currentAudioOutputDeviceComputed = computed<
| {
key: string;
label: string;
}
| undefined
>({
get: () => {
// 再生デバイスが見つからなかったらデフォルト値に戻す
// FIXME: watchなどにしてgetter内で操作しないようにする
const device = availableAudioOutputDevices.value?.find(
(device) => device.key === store.state.savingSetting.audioOutputDevice
);
if (device) {
return device;
} else {
} else if (store.state.savingSetting.audioOutputDevice !== "default") {
handleSavingSettingChange("audioOutputDevice", "default");
return null;
}
return undefined;
},
set: (device) => {
if (device) {
Expand Down
Loading

0 comments on commit 8f2099c

Please sign in to comment.