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

マルチトラック:mainをマージ #2166

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 23 additions & 4 deletions src/backend/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import path from "path";

import fs from "fs";
import { pathToFileURL } from "url";
import {
app,
protocol,
Expand Down Expand Up @@ -428,8 +429,23 @@ async function createWindow() {
// ソフトウェア起動時はプロトコルを app にする
if (process.env.VITE_DEV_SERVER_URL == undefined) {
protocol.handle("app", (request) => {
const filePath = path.join(__dirname, new URL(request.url).pathname);
return net.fetch(`file://${filePath}`);
// 読み取り先のファイルがインストールディレクトリ内であることを確認する
// ref: https://www.electronjs.org/ja/docs/latest/api/protocol#protocolhandlescheme-handler
const { pathname } = new URL(request.url);
const pathToServe = path.resolve(path.join(__dirname, pathname));
const relativePath = path.relative(__dirname, pathToServe);
const isUnsafe =
path.isAbsolute(relativePath) ||
relativePath.startsWith("..") ||
relativePath === "";
if (isUnsafe) {
log.error(`Bad Request URL: ${request.url}`);
return new Response("bad", {
status: 400,
headers: { "content-type": "text/html" },
});
}
return net.fetch(pathToFileURL(pathToServe).toString());
});
}

Expand Down Expand Up @@ -1029,8 +1045,11 @@ app.on("web-contents-created", (e, contents) => {

// ナビゲーションを無効化
contents.on("will-navigate", (event) => {
log.error(`ナビゲーションは無効化されています。url: ${event.url}`);
event.preventDefault();
// preloadスクリプト変更時のホットリロードを許容する
if (contents.getURL() !== event.url) {
log.error(`ナビゲーションは無効化されています。url: ${event.url}`);
event.preventDefault();
}
});
});

Expand Down
24 changes: 24 additions & 0 deletions src/helpers/fileHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ResultError } from "@/type/result";

/** ファイル書き込み時のエラーメッセージを生成する */
export function generateWriteErrorMessage(writeFileResult: ResultError) {
const code = writeFileResult.code?.toUpperCase();

if (code?.startsWith("ENOSPC")) {
return "空き容量が足りません。";
}

if (code?.startsWith("EACCES")) {
return "ファイルにアクセスする許可がありません。";
}

if (code?.startsWith("EBUSY")) {
return "ファイルが開かれています。";
}

if (code?.startsWith("ENOENT")) {
return "ファイルが見つかりません。";
}

return `何らかの理由で失敗しました。${writeFileResult.message}`;
}
107 changes: 50 additions & 57 deletions src/store/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
import { AudioQuery, AccentPhrase, Speaker, SpeakerInfo } from "@/openapi";
import { base64ImageToUri, base64ToUri } from "@/helpers/base64Helper";
import { getValueOrThrow, ResultError } from "@/type/result";
import { generateWriteErrorMessage } from "@/helpers/fileHelper";

function generateAudioKey() {
return AudioKey(crypto.randomUUID());
Expand Down Expand Up @@ -166,25 +167,6 @@ export async function writeTextFile(obj: {
});
}

function generateWriteErrorMessage(writeFileResult: ResultError) {
if (!writeFileResult.code) {
return `何らかの理由で失敗しました。${writeFileResult.message}`;
}
const code = writeFileResult.code.toUpperCase();

if (code.startsWith("ENOSPC")) {
return "空き容量が足りません。";
}

if (code.startsWith("EACCES")) {
return "ファイルにアクセスする許可がありません。";
}

if (code.startsWith("EBUSY")) {
return "ファイルが開かれています。";
}
}

// TODO: GETTERに移動する。
export function getCharacterInfo(
state: State,
Expand Down Expand Up @@ -1522,8 +1504,6 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
const labs: string[] = [];
const texts: string[] = [];

let labOffset = 0;

const base64Encoder = (blob: Blob): Promise<string | undefined> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
Expand All @@ -1544,6 +1524,7 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
const totalCount = state.audioKeys.length;
let finishedCount = 0;

let labOffset = 0;
for (const audioKey of state.audioKeys) {
let fetchAudioResult: FetchAudioResult;
try {
Expand All @@ -1565,21 +1546,21 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
return { result: "WRITE_ERROR", path: filePath };
}
encodedBlobs.push(encodedBlob);

// 大して処理能力を要しないので、生成設定のon/offにかかわらず生成してしまう
const lab = await generateLabFromAudioQuery(audioQuery, labOffset);
if (lab == undefined) {
return { result: "WRITE_ERROR", path: filePath };
}
labs.push(lab);

// 最終音素の終了時刻を取得する
const splitLab = lab.split(" ");
labOffset = Number(splitLab[splitLab.length - 2]);

texts.push(
extractExportText(state.audioItems[audioKey].text, {
enableMemoNotation: state.enableMemoNotation,
enableRubyNotation: state.enableRubyNotation,
}),
);
// 最終音素の終了時刻を取得する
const splitLab = lab.split(" ");
labOffset = Number(splitLab[splitLab.length - 2]);
}

const connectedWav = await dispatch("CONNECT_AUDIO", {
Expand All @@ -1589,40 +1570,48 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
return { result: "ENGINE_ERROR", path: filePath };
}

const writeFileResult = await window.backend.writeFile({
filePath,
buffer: await connectedWav.arrayBuffer(),
});
if (!writeFileResult.ok) {
window.backend.logError(writeFileResult.error);
return { result: "WRITE_ERROR", path: filePath };
}
try {
await window.backend
.writeFile({
filePath,
buffer: await connectedWav.arrayBuffer(),
})
.then(getValueOrThrow);

if (state.savingSetting.exportLab) {
const labResult = await writeTextFile({
// `generateLabFromAudioQuery`で生成される文字列はすべて改行で終わるので、追加で改行を挟む必要はない
text: labs.join(""),
filePath: filePath.replace(/\.wav$/, ".lab"),
});
if (!labResult.ok) {
window.backend.logError(labResult.error);
return { result: "WRITE_ERROR", path: filePath };
if (state.savingSetting.exportLab) {
await writeTextFile({
// `generateLabFromAudioQuery`で生成される文字列はすべて改行で終わるので、追加で改行を挟む必要はない
text: labs.join(""),
filePath: filePath.replace(/\.wav$/, ".lab"),
}).then(getValueOrThrow);
}
}

if (state.savingSetting.exportText) {
const textResult = await writeTextFile({
text: texts.join("\n"),
filePath: filePath.replace(/\.wav$/, ".txt"),
encoding: state.savingSetting.fileEncoding,
});
if (!textResult.ok) {
window.backend.logError(textResult.error);
return { result: "WRITE_ERROR", path: filePath };
if (state.savingSetting.exportText) {
await writeTextFile({
text: texts.join("\n"),
filePath: filePath.replace(/\.wav$/, ".txt"),
encoding: state.savingSetting.fileEncoding,
}).then(getValueOrThrow);
}
}

return { result: "SUCCESS", path: filePath };
return { result: "SUCCESS", path: filePath };
} catch (e) {
window.backend.logError(e);
if (e instanceof ResultError) {
return {
result: "WRITE_ERROR",
path: filePath,
errorMessage: generateWriteErrorMessage(e),
};
}
return {
result: "UNKNOWN_ERROR",
path: filePath,
errorMessage:
(e instanceof Error ? e.message : String(e)) ||
"不明なエラーが発生しました。",
};
}
},
),
},
Expand Down Expand Up @@ -1701,7 +1690,11 @@ export const audioStore = createPartialStore<AudioStoreTypes>({
});
if (!result.ok) {
window.backend.logError(result.error);
return { result: "WRITE_ERROR", path: filePath };
return {
result: "WRITE_ERROR",
path: filePath,
errorMessage: generateWriteErrorMessage(new ResultError(result)),
};
}

return { result: "SUCCESS", path: filePath };
Expand Down
18 changes: 4 additions & 14 deletions src/store/command.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { toRaw } from "vue";
import { enablePatches, enableMapSet, Immer } from "immer";
// immerの内部関数であるgetPlugin("Patches").applyPatches_はexportされていないので
// ビルド前のsrcからソースコードを読み込んで使う必要がある
import { enablePatches as enablePatchesImpl } from "immer/src/plugins/patches";
import { enableMapSet as enableMapSetImpl } from "immer/src/plugins/mapset";
import { getPlugin } from "immer/src/utils/plugins";

import { Command, CommandStoreState, CommandStoreTypes, State } from "./type";
import { applyPatches } from "@/store/immerPatchUtility";
import {
createPartialStore,
Mutation,
Expand All @@ -15,14 +11,8 @@ import {
} from "@/store/vuex";
import { EditorType } from "@/type/preload";

// ビルド後のモジュールとビルド前のモジュールは別のスコープで変数を持っているので
// enable * も両方叩く必要がある。
enablePatches();
enableMapSet();
enablePatchesImpl();
enableMapSetImpl();
// immerのPatchをmutableに適応する内部関数
const applyPatchesImpl = getPlugin("Patches").applyPatches_;

const immer = new Immer();
immer.setAutoFreeze(false);
Expand Down Expand Up @@ -60,7 +50,7 @@ export const createCommandMutation =
): Mutation<S, M, K> =>
(state: S, payload: M[K]): void => {
const command = recordPatches(payloadRecipe)(state, payload);
applyPatchesImpl(state, command.redoPatches);
applyPatches(state, command.redoPatches);
state.undoCommands[editor].push(command);
state.redoCommands[editor].splice(0);
};
Expand Down Expand Up @@ -112,7 +102,7 @@ export const commandStore = createPartialStore<CommandStoreTypes>({
const command = state.undoCommands[editor].pop();
if (command != null) {
state.redoCommands[editor].push(command);
applyPatchesImpl(state, command.undoPatches);
applyPatches(state, command.undoPatches);
}
},
action({ commit, dispatch }, { editor }: { editor: EditorType }) {
Expand All @@ -130,7 +120,7 @@ export const commandStore = createPartialStore<CommandStoreTypes>({
const command = state.redoCommands[editor].pop();
if (command != null) {
state.undoCommands[editor].push(command);
applyPatchesImpl(state, command.redoPatches);
applyPatches(state, command.redoPatches);
}
},
action({ commit, dispatch }, { editor }: { editor: EditorType }) {
Expand Down
Loading
Loading