From 3adb7c19708d9f16d70d1c7bc2e834900386a89e Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Thu, 30 Mar 2023 15:42:24 +0900 Subject: [PATCH 01/23] =?UTF-8?q?Add:=20=E3=83=9D=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=8C=E5=89=B2=E3=82=8A=E5=BD=93=E3=81=A6=E6=B8=88=E3=81=BF?= =?UTF-8?q?=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=82=92=E3=81=BE=E3=81=9A?= =?UTF-8?q?=E6=A4=9C=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit net の createServer はなぜかエラーを検知してくれない... いったん外部ライブラリに頼る --- package-lock.json | 116 +++++++++++++++++++++++++++++--- package.json | 2 + src/background/engineManager.ts | 19 ++++++ src/background/portManager.ts | 27 ++++++++ 4 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 src/background/portManager.ts diff --git a/package-lock.json b/package-lock.json index ab251874c3..9975a0128e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "shlex": "2.1.2", "source-map-support": "0.5.19", "systeminformation": "5.8.0", + "tcp-port-used": "1.0.2", "tree-kill": "1.2.2", "unzipper": "github:ZJONSSON/node-unzipper#341f258", "uuid": "9.0.0", @@ -52,6 +53,7 @@ "@types/mousetrap": "1.6.8", "@types/multistream": "4.1.0", "@types/semver": "7.3.9", + "@types/tcp-port-used": "1.0.1", "@types/unzipper": "0.10.5", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.38.1", @@ -1924,6 +1926,12 @@ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, + "node_modules/@types/tcp-port-used": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz", + "integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==", + "dev": true + }, "node_modules/@types/unzipper": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.5.tgz", @@ -6473,8 +6481,7 @@ "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "node_modules/defaults": { "version": "1.0.3", @@ -9719,6 +9726,14 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -10189,6 +10204,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -10221,6 +10241,19 @@ "node": ">=4" } }, + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, + "engines": { + "node": ">=v0.10.0" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -11630,8 +11663,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multistream": { "version": "4.1.0", @@ -14863,6 +14895,31 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "dependencies": { + "debug": "4.3.1", + "is2": "^2.0.6" + } + }, + "node_modules/tcp-port-used/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", @@ -18358,6 +18415,12 @@ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, + "@types/tcp-port-used": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz", + "integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==", + "dev": true + }, "@types/unzipper": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.5.tgz", @@ -22130,8 +22193,7 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "defaults": { "version": "1.0.3", @@ -24687,6 +24749,11 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -25018,6 +25085,11 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -25041,6 +25113,16 @@ "dev": true, "peer": true }, + "is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "requires": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -26167,8 +26249,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multistream": { "version": "4.1.0", @@ -28690,6 +28771,25 @@ } } }, + "tcp-port-used": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", + "requires": { + "debug": "4.3.1", + "is2": "^2.0.6" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, "temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", diff --git a/package.json b/package.json index a240ec5669..acf388701c 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "shlex": "2.1.2", "source-map-support": "0.5.19", "systeminformation": "5.8.0", + "tcp-port-used": "1.0.2", "tree-kill": "1.2.2", "unzipper": "github:ZJONSSON/node-unzipper#341f258", "uuid": "9.0.0", @@ -77,6 +78,7 @@ "@types/mousetrap": "1.6.8", "@types/multistream": "4.1.0", "@types/semver": "7.3.9", + "@types/tcp-port-used": "1.0.1", "@types/unzipper": "0.10.5", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.38.1", diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 88c799ec13..1ebb35d6ae 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -9,6 +9,7 @@ import { BrowserWindow, dialog } from "electron"; import log from "electron-log"; import { z } from "zod"; +import { PortManager } from "./portManager"; import { ipcMainSend } from "@/electron/ipc"; import { @@ -209,6 +210,7 @@ export class EngineManager { const engineInfo = engineInfos.find( (engineInfo) => engineInfo.uuid === engineId ); + if (!engineInfo) throw new Error(`No such engineInfo registered: engineId == ${engineId}`); @@ -224,6 +226,23 @@ export class EngineManager { return; } + const portManager = new PortManager(engineInfo.host); + + log.info( + `ENGINE ${engineId}: Checking whether port ${portManager.port} assignable...` + ); + + if (!(await portManager.isPortAssignable())) { + log.warn( + `ENGINE ${engineId}: Port ${portManager.port} has already been assigned ` + ); + dialog.showErrorBox( + "エンジンの起動に失敗しました", + `ポート${portManager.port}はすでに使用されています。` + ); + process.exit(1); + } + log.info(`ENGINE ${engineId}: Starting process`); if (!(engineId in this.engineProcessContainers)) { diff --git a/src/background/portManager.ts b/src/background/portManager.ts new file mode 100644 index 0000000000..b7ec687fa6 --- /dev/null +++ b/src/background/portManager.ts @@ -0,0 +1,27 @@ +import log from "electron-log"; +import tcpPortUsed from "tcp-port-used"; + +export class PortManager { + constructor(private url: string) {} + + /** + * ex) url: `http://localhost:50021` + * host -> `localhost` + * port -> `50021` + */ + public host = this.url.split(":")[1].slice(2); + public port = parseInt(this.url.split(":")[2]); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private portLog = (...message: any) => + log.info(`PORT ${this.port}: ${message}`); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private portWarn = (...message: any) => + log.warn(`PORT ${this.port}: ${message}`); + + public async isPortAssignable() { + const isAssignable = !(await tcpPortUsed.check(this.port, this.host)); + this.portLog(`isPortAssignable: ${isAssignable}`); + return isAssignable; + } +} From 057e95f5c57d42dc91882abdd99986b9975f8b67 Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Thu, 30 Mar 2023 16:54:31 +0900 Subject: [PATCH 02/23] =?UTF-8?q?Add:=20=E4=BD=BF=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=82=8B=E3=83=97=E3=83=AD=E3=82=BB=E3=82=B9ID?= =?UTF-8?q?=E3=81=A8=E5=90=8D=E5=89=8D=E3=81=AE=E5=8F=96=E5=BE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 外部ライブラリに頼る必要がなくなった - bash 要検証 --- package-lock.json | 116 +++----------------------------- package.json | 1 - src/background/engineManager.ts | 10 ++- src/background/portManager.ts | 106 ++++++++++++++++++++++++++--- 4 files changed, 112 insertions(+), 121 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9975a0128e..ab251874c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "shlex": "2.1.2", "source-map-support": "0.5.19", "systeminformation": "5.8.0", - "tcp-port-used": "1.0.2", "tree-kill": "1.2.2", "unzipper": "github:ZJONSSON/node-unzipper#341f258", "uuid": "9.0.0", @@ -53,7 +52,6 @@ "@types/mousetrap": "1.6.8", "@types/multistream": "4.1.0", "@types/semver": "7.3.9", - "@types/tcp-port-used": "1.0.1", "@types/unzipper": "0.10.5", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.38.1", @@ -1926,12 +1924,6 @@ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, - "node_modules/@types/tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==", - "dev": true - }, "node_modules/@types/unzipper": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.5.tgz", @@ -6481,7 +6473,8 @@ "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "node_modules/defaults": { "version": "1.0.3", @@ -9726,14 +9719,6 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -10204,11 +10189,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -10241,19 +10221,6 @@ "node": ">=4" } }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -11663,7 +11630,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/multistream": { "version": "4.1.0", @@ -14895,31 +14863,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" - } - }, - "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", @@ -18415,12 +18358,6 @@ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, - "@types/tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==", - "dev": true - }, "@types/unzipper": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/@types/unzipper/-/unzipper-0.10.5.tgz", @@ -22193,7 +22130,8 @@ "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "defaults": { "version": "1.0.3", @@ -24749,11 +24687,6 @@ "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true }, - "ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" - }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -25085,11 +25018,6 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -25113,16 +25041,6 @@ "dev": true, "peer": true }, - "is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", - "requires": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -26249,7 +26167,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "multistream": { "version": "4.1.0", @@ -28771,25 +28690,6 @@ } } }, - "tcp-port-used": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", - "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", - "requires": { - "debug": "4.3.1", - "is2": "^2.0.6" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - } - } - }, "temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", diff --git a/package.json b/package.json index acf388701c..94fea867b2 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,6 @@ "@types/mousetrap": "1.6.8", "@types/multistream": "4.1.0", "@types/semver": "7.3.9", - "@types/tcp-port-used": "1.0.1", "@types/unzipper": "0.10.5", "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.38.1", diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 1ebb35d6ae..5d388c97bd 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -232,13 +232,17 @@ export class EngineManager { `ENGINE ${engineId}: Checking whether port ${portManager.port} assignable...` ); - if (!(await portManager.isPortAssignable())) { + const processId = await portManager.getProcessIdFromPort(); + + // port is already in use + if (processId !== undefined) { + const processName = await portManager.getProcessNameFromPid(processId); log.warn( - `ENGINE ${engineId}: Port ${portManager.port} has already been assigned ` + `ENGINE ${engineId}: Port ${portManager.port} has already been assigned by ${processName} (pid=${processId})` ); dialog.showErrorBox( "エンジンの起動に失敗しました", - `ポート${portManager.port}はすでに使用されています。` + `${portManager.port}番ポートは、${processName} (pid=${processId}) によってすでに使用されています。` ); process.exit(1); } diff --git a/src/background/portManager.ts b/src/background/portManager.ts index b7ec687fa6..f0dfde3cab 100644 --- a/src/background/portManager.ts +++ b/src/background/portManager.ts @@ -1,5 +1,7 @@ +import { execFileSync } from "child_process"; import log from "electron-log"; -import tcpPortUsed from "tcp-port-used"; + +const isWindows = process.platform === "win32"; export class PortManager { constructor(private url: string) {} @@ -13,15 +15,101 @@ export class PortManager { public port = parseInt(this.url.split(":")[2]); // eslint-disable-next-line @typescript-eslint/no-explicit-any - private portLog = (...message: any) => - log.info(`PORT ${this.port}: ${message}`); + portLog = (...message: any) => log.info(`PORT ${this.port}: ${message}`); // eslint-disable-next-line @typescript-eslint/no-explicit-any - private portWarn = (...message: any) => - log.warn(`PORT ${this.port}: ${message}`); + portWarn = (...message: any) => log.warn(`PORT ${this.port}: ${message}`); + + /** + * Parses stdout from `netstat -ano` command and returns process id + * + * ex) stdout: + * ``` cmd + * TCP 127.0.0.1:5173 127.0.0.1:50170 TIME_WAIT 0 + * TCP 127.0.0.1:6463 0.0.0.0:0 LISTENING 18692 + * TCP 127.0.0.1:50021 0.0.0.0:0 LISTENING 17320 + * ``` + * -> `17320` + * + * @param stdout stdout from netstat command + * @returns `process id` or `undefined` + */ + private stdout2processId(stdout: string): number | undefined { + const lines = stdout.split("\n"); + for (const line of lines) { + if (line.includes(`${this.host}:${this.port}`)) { + const parts = line.trim().split(/\s+/); + return parseInt(parts[parts.length - 1], 10); + } + } + return undefined; + } + + async getProcessIdFromPort(): Promise { + this.portLog("Getting process id ..."); + const exec = isWindows + ? { + cmd: "netstat", + args: ["-ano"], + } + : { + cmd: "lsof", + args: ["-i", `:${this.port}`, "-t", "-sTCP:LISTEN"], + }; + + this.portLog(`Running command: "${exec.cmd} ${exec.args.join(" ")}"`); + + const stdout = execFileSync(exec.cmd, exec.args, { + shell: true, + }).toString(); + + // windows + if (isWindows) { + const result = this.stdout2processId(stdout); + if (!result) { + this.portLog("Assignable; Nobody uses this port!"); + return undefined; + } + this.portWarn(`Nonassignable; pid=${result} uses this port!`); + return result; + } + + // bash + if (!stdout || !stdout.length) { + this.portLog("Assignable; Nobody uses this port!"); + return undefined; + } + this.portWarn(`Nonassignable; pid=${stdout} uses this port!`); + return parseInt(stdout); + } + + async getProcessNameFromPid(pid: number): Promise { + this.portLog(`Getting process name from pid=${pid}...`); + const exec = isWindows + ? { + cmd: "wmic", + args: ["process", "where", `"ProcessID=${pid}"`, "get", "name"], + } + : { + cmd: "ps", + args: ["-p", pid.toString(), "-o", "comm="], + }; + + let stdout = execFileSync(exec.cmd, exec.args, { shell: true }).toString(); + + if (isWindows) { + /* + * ex) stdout: + * ``` + * Name + * node.exe + * ``` + * -> `node.exe` + */ + stdout = stdout.split("\r\n")[1]; + } + + this.portLog(`Found process name: ${stdout}`); - public async isPortAssignable() { - const isAssignable = !(await tcpPortUsed.check(this.port, this.host)); - this.portLog(`isPortAssignable: ${isAssignable}`); - return isAssignable; + return stdout.trim(); } } From 619809bf0bd90ebbb15b8013fdc8a6fff9bde228 Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Thu, 30 Mar 2023 17:02:44 +0900 Subject: [PATCH 03/23] =?UTF-8?q?Fix:=20dependencies=20=E6=B6=88=E3=81=97?= =?UTF-8?q?=E5=BF=98=E3=82=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 94fea867b2..a240ec5669 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "shlex": "2.1.2", "source-map-support": "0.5.19", "systeminformation": "5.8.0", - "tcp-port-used": "1.0.2", "tree-kill": "1.2.2", "unzipper": "github:ZJONSSON/node-unzipper#341f258", "uuid": "9.0.0", From f05a87a53ce79484f312f303fc0bd36d079acda7 Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Fri, 31 Mar 2023 02:55:19 +0900 Subject: [PATCH 04/23] =?UTF-8?q?Add:=20=E7=A9=BA=E3=81=84=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=82=8B=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E6=8E=A2?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=AB?= =?UTF-8?q?=20port=20=E3=82=92=E4=BC=9D=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - コメントを日本語にした - TODOつけた --- src/background/engineManager.ts | 39 +++++++++++++++++----- src/background/portManager.ts | 58 +++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 5d388c97bd..c6e01e6695 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -226,25 +226,41 @@ export class EngineManager { return; } - const portManager = new PortManager(engineInfo.host); + const engineInfoUrl = new URL(engineInfo.host); + const portManager = new PortManager( + engineInfoUrl.hostname, + parseInt(engineInfoUrl.port) + ); log.info( - `ENGINE ${engineId}: Checking whether port ${portManager.port} assignable...` + `ENGINE ${engineId}: Checking whether port ${engineInfoUrl.port} is assignable...` ); + // ポートを既に割り当てられているプロセスidの取得: undefined → ポートが空いている const processId = await portManager.getProcessIdFromPort(); - // port is already in use if (processId !== undefined) { const processName = await portManager.getProcessNameFromPid(processId); log.warn( - `ENGINE ${engineId}: Port ${portManager.port} has already been assigned by ${processName} (pid=${processId})` + `ENGINE ${engineId}: Port ${engineInfoUrl.port} has already been assigned by ${processName} (pid=${processId})` ); - dialog.showErrorBox( - "エンジンの起動に失敗しました", - `${portManager.port}番ポートは、${processName} (pid=${processId}) によってすでに使用されています。` + + const altPort = await portManager.findAltPort(); + + // TODO: エディターでトースト通知を出す: + // > XXXXX番ポートが使用中であるため、〇〇はYYYYY番ポートで起動しました + + // 代替ポートが見つからないとき + if (!altPort) throw new Error("No alternative port found"); + + // 代替ポートを設定 + engineInfo.host = new PortManager( + engineInfoUrl.hostname, + altPort + ).getUrl(); + log.warn( + `ENGINE ${engineId}: Applied Alterative Port: ${engineInfoUrl.port} -> ${altPort}` ); - process.exit(1); } log.info(`ENGINE ${engineId}: Starting process`); @@ -267,7 +283,12 @@ export class EngineManager { // エンジンプロセスの起動 const enginePath = engineInfo.executionFilePath; - const args = engineInfo.executionArgs.concat(useGpu ? ["--use_gpu"] : []); + const args = engineInfo.executionArgs.concat(useGpu ? ["--use_gpu"] : [], [ + "--host", + new URL(engineInfo.host).hostname, + "--port", + new URL(engineInfo.host).port, + ]); log.info(`ENGINE ${engineId} path: ${enginePath}`); log.info(`ENGINE ${engineId} args: ${JSON.stringify(args)}`); diff --git a/src/background/portManager.ts b/src/background/portManager.ts index f0dfde3cab..cd4ffd167d 100644 --- a/src/background/portManager.ts +++ b/src/background/portManager.ts @@ -4,23 +4,25 @@ import log from "electron-log"; const isWindows = process.platform === "win32"; export class PortManager { - constructor(private url: string) {} + constructor(private hostname: string, private port: number) {} /** * ex) url: `http://localhost:50021` * host -> `localhost` * port -> `50021` */ - public host = this.url.split(":")[1].slice(2); - public port = parseInt(this.url.split(":")[2]); // eslint-disable-next-line @typescript-eslint/no-explicit-any portLog = (...message: any) => log.info(`PORT ${this.port}: ${message}`); // eslint-disable-next-line @typescript-eslint/no-explicit-any portWarn = (...message: any) => log.warn(`PORT ${this.port}: ${message}`); + public getUrl(isHttps = false): string { + return `${isHttps ? "https" : "http"}://${this.hostname}:${this.port}`; + } + /** - * Parses stdout from `netstat -ano` command and returns process id + * "netstat -ano" の stdout から, 指定したポートを使用しているプロセスの process id を取得する * * ex) stdout: * ``` cmd @@ -30,13 +32,13 @@ export class PortManager { * ``` * -> `17320` * - * @param stdout stdout from netstat command - * @returns `process id` or `undefined` + * @param stdout netstat の stdout + * @returns `process id` or `undefined` (ポートが使用されていないとき) */ private stdout2processId(stdout: string): number | undefined { const lines = stdout.split("\n"); for (const line of lines) { - if (line.includes(`${this.host}:${this.port}`)) { + if (line.includes(`${this.hostname}:${this.port}`)) { const parts = line.trim().split(/\s+/); return parseInt(parts[parts.length - 1], 10); } @@ -45,7 +47,7 @@ export class PortManager { } async getProcessIdFromPort(): Promise { - this.portLog("Getting process id ..."); + this.portLog("Getting process id..."); const exec = isWindows ? { cmd: "netstat", @@ -58,19 +60,13 @@ export class PortManager { this.portLog(`Running command: "${exec.cmd} ${exec.args.join(" ")}"`); - const stdout = execFileSync(exec.cmd, exec.args, { + let stdout = execFileSync(exec.cmd, exec.args, { shell: true, }).toString(); // windows if (isWindows) { - const result = this.stdout2processId(stdout); - if (!result) { - this.portLog("Assignable; Nobody uses this port!"); - return undefined; - } - this.portWarn(`Nonassignable; pid=${result} uses this port!`); - return result; + stdout = this.stdout2processId(stdout)?.toString() ?? ""; } // bash @@ -85,11 +81,13 @@ export class PortManager { async getProcessNameFromPid(pid: number): Promise { this.portLog(`Getting process name from pid=${pid}...`); const exec = isWindows - ? { + ? // TODO: `TCPポートの除外範囲` というものがあって, そこに該当しているかを確認する + { cmd: "wmic", args: ["process", "where", `"ProcessID=${pid}"`, "get", "name"], } - : { + : // TODO: MacOS だとroot権限が必要かも? 場合によって, 代替コマンド `ss` や `fuser` を使うかも + { cmd: "ps", args: ["-p", pid.toString(), "-o", "comm="], }; @@ -112,4 +110,28 @@ export class PortManager { return stdout.trim(); } + + /** + * 割り当て可能な他のポートを探します + * + * @returns 割り当て可能なポート番号 or `undefined` (割り当て可能なポートが見つからなかったとき) + */ + async findAltPort(): Promise { + this.portLog(`Find another assignable port from ${this.port}...`); + const altPortMax = 50100; + + for (let altPort = this.port + 1; altPort <= altPortMax; altPort++) { + this.portLog(`Trying whether port ${altPort} is assignable...`); + const altPid = await new PortManager( + this.hostname, + altPort + ).getProcessIdFromPort(); + + // ポートを既に割り当てられているプロセスidの取得: undefined → ポートが空いている + if (altPid === undefined) return altPort; + } + + this.portWarn(`No alternative port found! ${this.port}...${altPortMax}`); + return undefined; + } } From 2f81e09b2eaaa9cefa480ed285aab922f3287f5f Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Fri, 31 Mar 2023 03:13:22 +0900 Subject: [PATCH 05/23] =?UTF-8?q?Fix:=20=E4=BB=A3=E6=9B=BF=E3=83=9D?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=8C=E8=A6=8B=E3=81=A4=E3=81=8B=E3=82=89?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=81=A8=E3=81=8D=E3=81=AE=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Typo: Alterative -> Alternative --- src/background/engineManager.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index c6e01e6695..116746a26f 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -5,7 +5,7 @@ import treeKill from "tree-kill"; import Store from "electron-store"; import shlex from "shlex"; -import { BrowserWindow, dialog } from "electron"; +import { app, BrowserWindow, dialog } from "electron"; import log from "electron-log"; import { z } from "zod"; @@ -251,7 +251,14 @@ export class EngineManager { // > XXXXX番ポートが使用中であるため、〇〇はYYYYY番ポートで起動しました // 代替ポートが見つからないとき - if (!altPort) throw new Error("No alternative port found"); + if (!altPort) { + dialog.showErrorBox( + `${engineInfo.name} の起動に失敗しました`, + `${engineInfoUrl.port}番ポートの代わりに利用可能なポートが見つかりませんでした。PCを再起動してください。` + ); + app.exit(1); + throw new Error("No Alternative Port Found"); + } // 代替ポートを設定 engineInfo.host = new PortManager( @@ -259,7 +266,7 @@ export class EngineManager { altPort ).getUrl(); log.warn( - `ENGINE ${engineId}: Applied Alterative Port: ${engineInfoUrl.port} -> ${altPort}` + `ENGINE ${engineId}: Applied Alternative Port: ${engineInfoUrl.port} -> ${altPort}` ); } From f39ef132c7e2b1491fd692f3eceff1884066a61b Mon Sep 17 00:00:00 2001 From: wappon-28-dev Date: Sun, 2 Apr 2023 15:58:50 +0900 Subject: [PATCH 06/23] =?UTF-8?q?Add:=20ChangePortInfos=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/background.ts | 6 +- src/background/engineManager.ts | 35 ++++++++++- src/components/ToastNotification.vue | 94 ++++++++++++++++++++++++++++ src/store/type.ts | 23 +++++++ src/store/ui.ts | 40 ++++++++++++ src/type/preload.ts | 14 +++++ src/views/EditorHome.vue | 17 +++++ 7 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/components/ToastNotification.vue diff --git a/src/background.ts b/src/background.ts index 608ce6d946..6ba3084441 100644 --- a/src/background.ts +++ b/src/background.ts @@ -20,6 +20,7 @@ import log from "electron-log"; import dayjs from "dayjs"; import windowStateKeeper from "electron-window-state"; import zodToJsonSchema from "zod-to-json-schema"; +import { useStore } from "vuex"; import { hasSupportedGpu } from "./electron/device"; import { textEditContextMenu } from "./electron/contextMenu"; import { @@ -524,7 +525,10 @@ async function start() { store.set("engineSettings", engineSettings); await createWindow(); - await engineManager.runEngineAll(win); + + const altPortInfos = await engineManager.runEngineAll(win); + console.log("altPortInfos => ", altPortInfos); + store.set("altPortInfo", altPortInfos); } const menuTemplateForMac: Electron.MenuItemConstructorOptions[] = [ diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 116746a26f..64e3a97acd 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -27,6 +27,14 @@ type EngineProcessContainer = { engineProcess?: ChildProcess; }; +type AltPortInfo = { + engineInfo: EngineInfo; + port: { + origin: number; + alt: number; + }; +}; + /** * デフォルトエンジンの情報を作成する */ @@ -191,21 +199,31 @@ export class EngineManager { * 全てのエンジンを起動する。 * FIXME: winを受け取らなくても良いようにする */ - async runEngineAll(win: BrowserWindow) { + async runEngineAll(win: BrowserWindow): Promise { const engineInfos = this.fetchEngineInfos(); log.info(`Starting ${engineInfos.length} engine/s...`); + const altPortInfos: AltPortInfo[] = []; + for (const engineInfo of engineInfos) { log.info(`ENGINE ${engineInfo.uuid}: Start launching`); - await this.runEngine(engineInfo.uuid, win); + const altPortInfo = await this.runEngine(engineInfo.uuid, win); + + if (altPortInfo) altPortInfos.push(altPortInfo); } + return altPortInfos; } /** * エンジンを起動する。 * FIXME: winを受け取らなくても良いようにする */ - async runEngine(engineId: EngineId, win: BrowserWindow) { + async runEngine( + engineId: EngineId, + win: BrowserWindow + ): Promise { + let altPortInfo: AltPortInfo | undefined = undefined; + const engineInfos = this.fetchEngineInfos(); const engineInfo = engineInfos.find( (engineInfo) => engineInfo.uuid === engineId @@ -252,6 +270,7 @@ export class EngineManager { // 代替ポートが見つからないとき if (!altPort) { + log.warn(`ENGINE ${engineId}: No Alternative Port Found`); dialog.showErrorBox( `${engineInfo.name} の起動に失敗しました`, `${engineInfoUrl.port}番ポートの代わりに利用可能なポートが見つかりませんでした。PCを再起動してください。` @@ -260,6 +279,14 @@ export class EngineManager { throw new Error("No Alternative Port Found"); } + altPortInfo = { + engineInfo, + port: { + origin: parseInt(engineInfoUrl.port), + alt: altPort, + }, + }; + // 代替ポートを設定 engineInfo.host = new PortManager( engineInfoUrl.hostname, @@ -338,6 +365,8 @@ export class EngineManager { dialog.showErrorBox("音声合成エンジンエラー", dialogMessage); } }); + + return altPortInfo; } /** diff --git a/src/components/ToastNotification.vue b/src/components/ToastNotification.vue new file mode 100644 index 0000000000..82ee7f939b --- /dev/null +++ b/src/components/ToastNotification.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/src/store/type.ts b/src/store/type.ts index 88034e8c27..f8e7ac0a38 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -50,6 +50,7 @@ import { StyleId, AudioKey, PresetKey, + ToastNotification, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; @@ -1104,6 +1105,7 @@ export type UiStoreState = { isPinned: boolean; isFullscreen: boolean; progress: number; + toastNotifications: ToastNotification[]; }; export type UiStoreTypes = { @@ -1250,6 +1252,27 @@ export type UiStoreTypes = { RESET_PROGRESS: { action(): void; }; + + TOAST_NOTIFICATIONS: { + getter: ToastNotification[]; + }; + + SET_TOAST_NOTIFICATIONS: { + mutation: { toastNotifications: ToastNotification[] }; + action(payload: { toastNotifications: ToastNotification[] }): void; + }; + + PUSH_TOAST_NOTIFICATION: { + action(payload: { toastNotification: ToastNotification }): void; + }; + + POP_TOAST_NOTIFICATION: { + action(): void; + }; + + CLEAR_TOAST_NOTIFICATIONS: { + action(): void; + }; }; /* diff --git a/src/store/ui.ts b/src/store/ui.ts index 38908d18b9..0261573c87 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -52,6 +52,7 @@ export const uiStoreState: UiStoreState = { isPinned: false, isFullscreen: false, progress: -1, + toastNotifications: [], }; export const uiStore = createPartialStore({ @@ -346,4 +347,43 @@ export const uiStore = createPartialStore({ dispatch("SET_PROGRESS", { progress: -1 }); }, }, + + TOAST_NOTIFICATIONS: { + getter(state) { + return state.toastNotifications; + }, + }, + + SET_TOAST_NOTIFICATIONS: { + mutation(state, { toastNotifications }) { + state.toastNotifications = toastNotifications; + }, + action({ commit }, { toastNotifications }) { + commit("SET_TOAST_NOTIFICATIONS", { toastNotifications }); + }, + }, + + PUSH_TOAST_NOTIFICATION: { + action({ dispatch, getters }, { toastNotification }) { + const toastNotifications = getters.TOAST_NOTIFICATIONS; + dispatch("SET_TOAST_NOTIFICATIONS", { + toastNotifications: [...toastNotifications, toastNotification], + }); + }, + }, + + POP_TOAST_NOTIFICATION: { + action({ dispatch, getters }) { + const toastNotifications = getters.TOAST_NOTIFICATIONS; + dispatch("SET_TOAST_NOTIFICATIONS", { + toastNotifications: toastNotifications.slice(1), + }); + }, + }, + + CLEAR_TOAST_NOTIFICATIONS: { + action({ dispatch }) { + dispatch("SET_TOAST_NOTIFICATIONS", { toastNotifications: [] }); + }, + }, }); diff --git a/src/type/preload.ts b/src/type/preload.ts index 71ff3df43f..6e86c9ecf2 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -586,6 +586,15 @@ export const electronStoreSchema = z .passthrough() .default({}), registeredEngineDirs: z.string().array().default([]), + altPortInfos: z.array( + z.object({ + engineInfo: z.string(), + port: z.object({ + old: z.number(), + alt: z.number(), + }), + }) + ), }) .passthrough(); export type ElectronStoreType = z.infer; @@ -619,3 +628,8 @@ export type EngineDirValidationResult = | "alreadyExists"; export type VvppFilePathValidationResult = "ok" | "fileNotFound"; + +export type ToastNotification = { + text: string; + showMs?: number; +}; diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index ea7b6618c6..8a43e1edfa 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -162,6 +162,7 @@ v-model="isAcceptRetrieveTelemetryDialogOpenComputed" /> + diff --git a/src/electron/preload.ts b/src/electron/preload.ts index b3561efb69..504d4d3839 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -66,6 +66,10 @@ const api: Sandbox = { return await ipcRendererInvoke("GET_PRIVACY_POLICY_TEXT"); }, + getAltPortInfo: async () => { + return await ipcRendererInvoke("GET_ALT_PORT_INFO"); + }, + saveTempAudioFile: async ({ relativePath, buffer }) => { if (!tempDir) { tempDir = await ipcRendererInvoke("GET_TEMP_DIR"); diff --git a/src/main.ts b/src/main.ts index bebc364f13..3ae3418f59 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { createApp } from "vue"; import { createGtm } from "@gtm-support/vue-gtm"; -import { Quasar, Dialog, Loading } from "quasar"; +import { Quasar, Dialog, Loading, Notify } from "quasar"; import iconSet from "quasar/icon-set/material-icons"; import App from "./App.vue"; import router from "./router"; @@ -33,12 +33,14 @@ createApp(App) primary: "#a5d4ad", secondary: "#212121", negative: "var(--color-warning)", + info: "rgba(var(--color-primary-rgb), 0.4)", }, }, iconSet, plugins: { Dialog, Loading, + Notify, }, }) .use(ipcMessageReceiver, { store }) diff --git a/src/store/engine.ts b/src/store/engine.ts index bd5fed3519..7c47ce8c63 100644 --- a/src/store/engine.ts +++ b/src/store/engine.ts @@ -1,9 +1,4 @@ -import { - AltPortInfo, - EngineState, - EngineStoreState, - EngineStoreTypes, -} from "./type"; +import { EngineState, EngineStoreState, EngineStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; import type { EngineManifest } from "@/openapi"; @@ -12,7 +7,6 @@ import type { EngineId, EngineInfo } from "@/type/preload"; export const engineStoreState: EngineStoreState = { engineStates: {}, engineSupportedDevices: {}, - altPortInfo: {}, }; export const engineStore = createPartialStore({ @@ -52,7 +46,8 @@ export const engineStore = createPartialStore({ }, GET_ALT_PORT_INFO: { - getter: (state) => state.altPortInfo, + getter: () => async (engineId) => + (await window.electron.getAltPortInfo())[engineId], }, SET_ENGINE_INFOS: { @@ -73,12 +68,6 @@ export const engineStore = createPartialStore({ }, }, - SET_ALT_PORT_INFO: { - mutation(state, { altPortInfo }: { altPortInfo: AltPortInfo }) { - state.altPortInfo = altPortInfo; - }, - }, - SET_ENGINE_MANIFESTS: { mutation( state, diff --git a/src/store/type.ts b/src/store/type.ts index 16fbda5f28..1fc578316e 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -80,6 +80,7 @@ export type Command = { }; export type EngineState = "STARTING" | "FAILED_STARTING" | "ERROR" | "READY"; + export type AltPortInfo = Record; export type SaveResult = @@ -97,11 +98,6 @@ export type ErrorTypeForSaveAllResultDialog = { message: string; }; -export type ToastNotification = { - text: string; - showMs?: number; -}; - export type StoreType = { [P in keyof T as Extract extends never ? never @@ -738,7 +734,6 @@ export type CommandStoreTypes = { export type EngineStoreState = { engineStates: Record; engineSupportedDevices: Record; - altPortInfo: AltPortInfo; }; export type EngineStoreTypes = { @@ -751,17 +746,13 @@ export type EngineStoreTypes = { }; GET_ALT_PORT_INFO: { - getter: AltPortInfo; + getter(engineId: EngineId): Promise; }; SET_ENGINE_MANIFESTS: { mutation: { engineManifests: Record }; }; - SET_ALT_PORT_INFO: { - mutation: { altPortInfo: AltPortInfo }; - }; - FETCH_AND_SET_ENGINE_MANIFESTS: { action(): void; }; @@ -1120,7 +1111,6 @@ export type UiStoreState = { isPinned: boolean; isFullscreen: boolean; progress: number; - toastNotifications: ToastNotification[]; }; export type UiStoreTypes = { @@ -1267,27 +1257,6 @@ export type UiStoreTypes = { RESET_PROGRESS: { action(): void; }; - - TOAST_NOTIFICATIONS: { - getter: ToastNotification[]; - }; - - SET_TOAST_NOTIFICATIONS: { - mutation: { toastNotifications: ToastNotification[] }; - action(payload: { toastNotifications: ToastNotification[] }): void; - }; - - PUSH_TOAST_NOTIFICATION: { - action(payload: { toastNotification: ToastNotification }): void; - }; - - POP_TOAST_NOTIFICATION: { - action(): void; - }; - - CLEAR_TOAST_NOTIFICATIONS: { - action(): void; - }; }; /* diff --git a/src/store/ui.ts b/src/store/ui.ts index 0261573c87..38908d18b9 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -52,7 +52,6 @@ export const uiStoreState: UiStoreState = { isPinned: false, isFullscreen: false, progress: -1, - toastNotifications: [], }; export const uiStore = createPartialStore({ @@ -347,43 +346,4 @@ export const uiStore = createPartialStore({ dispatch("SET_PROGRESS", { progress: -1 }); }, }, - - TOAST_NOTIFICATIONS: { - getter(state) { - return state.toastNotifications; - }, - }, - - SET_TOAST_NOTIFICATIONS: { - mutation(state, { toastNotifications }) { - state.toastNotifications = toastNotifications; - }, - action({ commit }, { toastNotifications }) { - commit("SET_TOAST_NOTIFICATIONS", { toastNotifications }); - }, - }, - - PUSH_TOAST_NOTIFICATION: { - action({ dispatch, getters }, { toastNotification }) { - const toastNotifications = getters.TOAST_NOTIFICATIONS; - dispatch("SET_TOAST_NOTIFICATIONS", { - toastNotifications: [...toastNotifications, toastNotification], - }); - }, - }, - - POP_TOAST_NOTIFICATION: { - action({ dispatch, getters }) { - const toastNotifications = getters.TOAST_NOTIFICATIONS; - dispatch("SET_TOAST_NOTIFICATIONS", { - toastNotifications: toastNotifications.slice(1), - }); - }, - }, - - CLEAR_TOAST_NOTIFICATIONS: { - action({ dispatch }) { - dispatch("SET_TOAST_NOTIFICATIONS", { toastNotifications: [] }); - }, - }, }); diff --git a/src/type/ipc.ts b/src/type/ipc.ts index f33120f566..2983ab3f90 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -1,3 +1,4 @@ +import { AltPortInfo } from "@/store/type"; import { AppInfos, ElectronStoreType, @@ -67,6 +68,11 @@ export type IpcIHData = { return: string; }; + GET_ALT_PORT_INFO: { + args: []; + return: AltPortInfo; + }; + SHOW_AUDIO_SAVE_DIALOG: { args: [obj: { title: string; defaultPath?: string }]; return?: string; diff --git a/src/type/preload.ts b/src/type/preload.ts index 71ff3df43f..cfd53886c5 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -1,6 +1,7 @@ import { IpcRenderer, IpcRendererEvent, nativeTheme } from "electron"; import { z } from "zod"; import { IpcSOData } from "./ipc"; +import { AltPortInfo } from "@/store/type"; export const isMac = typeof process === "undefined" @@ -143,6 +144,7 @@ export interface Sandbox { getQAndAText(): Promise; getContactText(): Promise; getPrivacyPolicyText(): Promise; + getAltPortInfo(): Promise; saveTempAudioFile(obj: { relativePath: string; buffer: ArrayBuffer }): void; loadTempFile(): Promise; showAudioSaveDialog(obj: { diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index 89d2de5c39..92620821e7 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -162,7 +162,6 @@ v-model="isAcceptRetrieveTelemetryDialogOpenComputed" /> -