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

[project-library-manage] ライブラリのインストール・アンインストール処理を実装 #1497

Open
wants to merge 76 commits into
base: project-library-manage
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
96480ca
move q-drawer
y-chan Aug 5, 2023
ec228e1
add panel to library manage dialog
y-chan Aug 6, 2023
d0fddac
add package
y-chan Aug 10, 2023
e7a613f
backend of install library
y-chan Aug 10, 2023
d078744
impl library install/uninstall action
y-chan Aug 10, 2023
a79834d
Merge remote-tracking branch 'upstream/main' into feat/library-instal…
y-chan Aug 15, 2023
859e986
fix browser sandbox error
y-chan Aug 15, 2023
3da7c45
add comment to library manager's default api
y-chan Aug 15, 2023
c8be57a
remove unused param
y-chan Aug 15, 2023
393d163
move library store
y-chan Aug 15, 2023
d418caf
use finally for tempfile post process
y-chan Aug 15, 2023
1a4b559
revert
y-chan Aug 15, 2023
8724791
remove unused import
y-chan Aug 15, 2023
5115f2f
use reload app
y-chan Aug 15, 2023
9da64ea
make it independent of DownloadableLibrary
y-chan Aug 17, 2023
a43dcec
split dialog
y-chan Aug 17, 2023
f8563e7
remove unused part
y-chan Aug 17, 2023
07030d8
remove unused part
y-chan Aug 17, 2023
1ef4a5e
use ui dispatch
y-chan Aug 17, 2023
8db4a21
remove unused arg
y-chan Aug 17, 2023
ad8b85b
move fetch statuses to store
y-chan Aug 17, 2023
1de8a2a
add reloadNeeded status
y-chan Aug 17, 2023
4871483
fix func name
y-chan Aug 17, 2023
551d695
refactor
y-chan Aug 17, 2023
43ac370
refactor (ipc/electron/vuex name change)
y-chan Aug 17, 2023
0494da2
fix test
y-chan Aug 17, 2023
3a1fb3e
lint
y-chan Aug 17, 2023
9f9dc7b
move uninstall library part to main process
y-chan Aug 17, 2023
705cd68
Merge remote-tracking branch 'upstream/project-library-manage' into f…
y-chan Aug 18, 2023
603747f
fix uninstall error
y-chan Aug 18, 2023
56be04e
fix watch error
y-chan Aug 18, 2023
15e3d33
remove size helper
y-chan Aug 18, 2023
277eb52
show more details of error status
y-chan Aug 18, 2023
3c9f6ce
update error message
y-chan Aug 20, 2023
097464c
change progress bar condition
y-chan Sep 4, 2023
443e3c0
using library size
y-chan Sep 4, 2023
e3d5d0b
fix uninstall status
y-chan Sep 6, 2023
8469f39
fix miss(remove unused import)
y-chan Sep 9, 2023
51fbb49
add comment for library manager
y-chan Sep 9, 2023
7e6aa6d
fix return type
y-chan Sep 9, 2023
9bc96bb
move to private func (set engine api)
y-chan Sep 9, 2023
51236bc
move to private func(lock by engine id)
y-chan Sep 9, 2023
21d798c
use exists sync
y-chan Sep 9, 2023
71db5ec
improve temp file close and remove process
y-chan Sep 9, 2023
3ff5d84
repeal black list style (using white list style)
y-chan Sep 9, 2023
dad6b42
remove track-color
y-chan Sep 9, 2023
2bd1e55
rename func in library install dialog
y-chan Sep 9, 2023
7e8537d
fix func name in library manage dialog
y-chan Sep 9, 2023
fff7f1b
remove comment (fix error)
y-chan Sep 9, 2023
8f9986e
use result type in library install/uninstall
y-chan Sep 10, 2023
2a66258
rename store state and dispatch/mutation
y-chan Sep 10, 2023
01fe343
remove debug log
y-chan Sep 10, 2023
a7a2133
move library install dialog in manage dialog
y-chan Sep 10, 2023
f639c1a
use props in library install dialog and refactor store
y-chan Sep 10, 2023
0e55a09
remove uninstall dialog in manage dialog
y-chan Sep 11, 2023
a2dfbe1
improve error message
y-chan Sep 11, 2023
4a14ffa
more improve error message in install/uninstall library
y-chan Sep 11, 2023
889f6c6
fix json parse error
y-chan Sep 11, 2023
7ba5553
use quasar dialog plugin
y-chan Sep 12, 2023
fa100ff
move type
y-chan Sep 12, 2023
d7c679a
more detail in error log
y-chan Sep 12, 2023
7db5ae1
remove node-fetch in devDependencies
y-chan Sep 15, 2023
0e99564
fix result type
y-chan Sep 15, 2023
c821680
change func name
y-chan Sep 15, 2023
d6ffa74
move to private func(extract error message)
y-chan Sep 15, 2023
f301c26
use content length variable
y-chan Sep 15, 2023
87b63a1
change func name in library install dialog
y-chan Sep 15, 2023
1699aaa
remove unnecessary process
y-chan Sep 15, 2023
e05d745
use tempfile closed
y-chan Sep 15, 2023
7469adb
fix store order
y-chan Sep 15, 2023
d0f990f
fix typo and right type
y-chan Sep 15, 2023
97d1028
remove unused const
y-chan Sep 15, 2023
1daec5f
fix button text
y-chan Sep 15, 2023
9b0c5b1
use "show warning dialog"
y-chan Sep 15, 2023
a88396a
remove "話者"
y-chan Sep 15, 2023
530c8ef
Merge branch 'project-library-manage' into feat/library-install-action
y-chan Sep 19, 2023
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
6,667 changes: 5,526 additions & 1,141 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"markdown-it": "12.0.4",
"move-file": "3.0.0",
"multistream": "4.1.0",
"node-fetch": "3.3.1",
y-chan marked this conversation as resolved.
Show resolved Hide resolved
"quasar": "2.11.6",
"semver": "7.3.5",
"shlex": "2.1.2",
Expand Down Expand Up @@ -83,6 +84,7 @@
"@types/markdown-it": "12.2.0",
"@types/mousetrap": "1.6.8",
"@types/multistream": "4.1.0",
"@types/node": "16.18.23",
"@types/semver": "7.3.9",
"@types/unzipper": "0.10.5",
"@types/uuid": "8.3.4",
Expand Down
65 changes: 65 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
defaultToolbarButtonSetting,
engineSettingSchema,
EngineId,
LibraryInstallStatus,
} from "./type/preload";
import {
ContactTextFileName,
Expand All @@ -46,6 +47,7 @@ import EngineManager from "./background/engineManager";
import VvppManager, { isVvppFile } from "./background/vvppManager";
import configMigration014 from "./background/configMigration014";
import { failure, success } from "./type/result";
import LibraryManager from "./background/libraryManager";
import { ipcMainHandle, ipcMainSend } from "@/electron/ipc";
import { getStoreWithError } from "@/background/electronStore";

Expand Down Expand Up @@ -167,6 +169,10 @@ const engineManager = new EngineManager({
onEngineProcessError,
});
const vvppManager = new VvppManager({ vvppEngineDir });
const libraryManager = new LibraryManager({
engineManager,
tempDir: app.getPath("temp"),
});

// エンジンのフォルダを開く
function openEngineDirectory(engineId: EngineId) {
Expand Down Expand Up @@ -942,6 +948,65 @@ ipcMainHandle("READ_FILE", async (_, { filePath }) => {
}
});

ipcMainHandle(
"START_LIBRARY_DOWNLOAD_AND_INSTALL",
(_, { engineId, libraryId, libraryName, libraryDownloadUrl }) => {
libraryManager.startLibraryDownloadAndInstall(
engineId,
libraryId,
libraryName,
libraryDownloadUrl,
(status: LibraryInstallStatus) => {
if (status.status === "downloading" && status.contentLength) {
win.setProgressBar(status.downloaded / status.contentLength);
} else if (status.status === "installing") {
win.setProgressBar(2);
} else if (status.status === "error") {
win.setProgressBar(-1);
dialog.showErrorBox(
"ライブラリのインストールに失敗しました",
status.message
);
} else {
win.setProgressBar(-1);
y-chan marked this conversation as resolved.
Show resolved Hide resolved
}
ipcMainSend(win, "UPDATE_LIBRARY_INSTALL_STATUS", {
libraryId,
status,
});
}
);
}
);

ipcMainHandle(
"UNINSTALL_LIBRARY",
async (_, { engineId, libraryId, libraryName }) => {
await libraryManager.uninstallLibrary(
engineId,
libraryId,
libraryName,
(status: LibraryInstallStatus) => {
if (status.status === "uninstalling") {
win.setProgressBar(2);
} else if (status.status === "error") {
win.setProgressBar(-1);
dialog.showErrorBox(
"ライブラリのインストールに失敗しました",
status.message
);
} else {
win.setProgressBar(-1);
y-chan marked this conversation as resolved.
Show resolved Hide resolved
}
ipcMainSend(win, "UPDATE_LIBRARY_INSTALL_STATUS", {
libraryId,
status,
});
}
);
}
);

// app callback
app.on("web-contents-created", (e, contents) => {
// リンククリック時はブラウザを開く
Expand Down
231 changes: 231 additions & 0 deletions src/background/libraryManager.ts
y-chan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import fs from "fs";
import path from "path";
import fetch from "node-fetch";
import log from "electron-log";
import AsyncLock from "async-lock";
import EngineManager from "./engineManager";
import { EngineId, LibraryId, LibraryInstallStatus } from "@/type/preload";
import { Configuration, DefaultApi } from "@/openapi";

export class LibraryManager {
engineManager: EngineManager;
tempDir: string;
lock: AsyncLock = new AsyncLock();
engineApis: Record<EngineId, DefaultApi> = {}; // ライブラリ管理のためだけに使う

constructor({
engineManager,
tempDir,
}: {
engineManager: EngineManager;
tempDir: string;
}) {
this.engineManager = engineManager;
this.tempDir = tempDir;
}

async startLibraryDownloadAndInstall(
y-chan marked this conversation as resolved.
Show resolved Hide resolved
engineId: EngineId,
libraryId: LibraryId,
libraryName: string,
libraryDownloadUrl: string,
onUpdate: (status: LibraryInstallStatus) => void
): Promise<Buffer | undefined> {
y-chan marked this conversation as resolved.
Show resolved Hide resolved
const engine = this.engineManager.fetchEngineInfo(engineId);
const prefix = `LIBRARY INSTALL ${libraryName}: `;
log.log(
prefix +
`Started ${libraryName}, Engine: ${engine.name}, URL: ${libraryDownloadUrl}`
);
const downloadRes = await fetch(libraryDownloadUrl);
if (!downloadRes.ok) {
log.error(
prefix +
`Failed to download library: Server returned ${downloadRes.status}`
);
let reason: string;
if (downloadRes.status >= 400 && downloadRes.status < 500) {
switch (downloadRes.status) {
case 401:
case 403:
reason = "ダウンロードが拒否された";
break;
case 404:
reason = "ダウンロードするライブラリが見つからない";
break;
default:
reason = "アクセスに失敗した";
break;
}
} else if (downloadRes.status >= 500) {
switch (downloadRes.status) {
case 502:
case 503:
reason = "サーバーがダウンしている";
break;
case 504:
reason = "サーバーが時間内に応答しなかった";
break;
default:
reason = "サーバーでエラーが発生した";
break;
}
} else {
reason = "ダウンロード先のサーバーが不明なエラーを返した";
}
onUpdate({
status: "error",
message: `${reason}ため、現在このライブラリをダウンロードできません。(ステータスコード:${downloadRes.status})`,
});
return;
} else if (downloadRes.body === null) {
log.error(prefix + `Failed to download library: No body`);
onUpdate({
status: "error",
message: `ダウンロード先のサーバーがライブラリを返しませんでした`,
});
return;
}
const tempFilePath = path.join(
this.tempDir,
`vv-temp-${engineId}-${libraryId}`
);
log.log(prefix + `Writing to ${tempFilePath}`);

const total = Number(downloadRes.headers.get("content-length"));
let downloaded = 0;
onUpdate({
status: "downloading",
contentLength: total,
downloaded,
});
const tempFile = fs.createWriteStream(tempFilePath);
try {
const progressInterval = 1024 * 1024;
let lastProgress = 0;
downloadRes.body.on("data", (chunk) => {
downloaded += chunk.length;
tempFile.write(chunk);
if (
Math.floor(downloaded) - Math.floor(lastProgress) >=
progressInterval
) {
onUpdate({
status: "downloading",
contentLength: total,
downloaded,
});
if (total) {
log.log(
prefix +
`Downloaded ${downloaded}/${total} bytes (${(
(downloaded / total) *
100
).toFixed(1)}%)`
);
} else {
log.log(
prefix +
`Downloaded ${downloaded} bytes (content-length header is not set)`
);
}
lastProgress = downloaded;
}
});
await new Promise((resolve) => {
if (!downloadRes.body) throw new Error("res.body is null");
downloadRes.body.on("end", resolve);
});
tempFile.close();
log.log(prefix + "Download complete");

if (this.engineApis[engineId] === undefined) {
this.engineApis[engineId] = new DefaultApi(
new Configuration({ basePath: engine.host })
);
}
y-chan marked this conversation as resolved.
Show resolved Hide resolved
const libraryBuffer = await fs.promises.readFile(tempFilePath);

onUpdate({
status: "installing",
});
log.log(prefix + "Waiting for lock");

await this.lock.acquire(`${engineId}`, async () => {
y-chan marked this conversation as resolved.
Show resolved Hide resolved
log.log(prefix + "Installing library");
await this.engineApis[
engineId
].installLibraryInstallLibraryLibraryUuidPost(
{
libraryUuid: libraryId,
},
{
body: libraryBuffer,
}
);
log.log(prefix + "Library installed");
});
await fs.promises.rm(tempFilePath);
onUpdate({
status: "done",
});
} catch (e) {
log.error(prefix + "Failed to install library");
log.error(e);
onUpdate({
status: "error",
message: `ライブラリのインストールに失敗しました。エラー内容:${e}`,
});
} finally {
tempFile.close();
y-chan marked this conversation as resolved.
Show resolved Hide resolved
y-chan marked this conversation as resolved.
Show resolved Hide resolved
log.log(prefix + "Removing temp file");
await fs.promises.rm(tempFilePath);
y-chan marked this conversation as resolved.
Show resolved Hide resolved
}
}

async uninstallLibrary(
y-chan marked this conversation as resolved.
Show resolved Hide resolved
engineId: EngineId,
libraryId: LibraryId,
libraryName: string,
onUpdate: (status: LibraryInstallStatus) => void
) {
const engine = this.engineManager.fetchEngineInfo(engineId);
const prefix = `LIBRARY UNINSTALL ${libraryName}: `;
log.log(prefix + `Started ${libraryName}, Engine: ${engine.name}`);

onUpdate({
status: "uninstalling",
});
log.log(prefix + "Waiting for lock");

if (this.engineApis[engineId] === undefined) {
this.engineApis[engineId] = new DefaultApi(
new Configuration({ basePath: engine.host })
);
}

try {
await this.lock.acquire(`${engineId}`, async () => {
log.log(prefix + "Uninstalling library");
await this.engineApis[
engineId
].uninstallLibraryUninstallLibraryLibraryUuidPost({
libraryUuid: libraryId,
});
log.log(prefix + "Library uninstalled");
});
onUpdate({
status: "done",
});
} catch (e) {
log.error(prefix + "Failed to uninstall library");
log.error(e);
onUpdate({
status: "error",
message: `ライブラリのアンインストールに失敗しました。エラー内容:${e}`,
});
}
}
}

export default LibraryManager;
8 changes: 8 additions & 0 deletions src/browser/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,12 @@ export const api: Sandbox = {
reloadApp(/* obj: { isMultiEngineOffMode: boolean } */) {
throw new Error(`Not supported on Browser version: reloadApp`);
},
startLibraryDownloadAndInstall(/* obj: { engineId: EngineId; library: DownloadableLibrary; } */) {
throw new Error(
`Not supported on Browser version: startLibraryDownloadAndInstall`
);
},
uninstallLibrary(/* pbj: { engineId: EngineId; libraryId: LibraryId; libraryName: string; } */) {
y-chan marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`Not supported on Browser version: uninstallLibrary`);
},
};
Loading