diff --git a/package-lock.json b/package-lock.json index 4471f9645b..c464c20ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2282,8 +2282,7 @@ "@types/semver": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", - "dev": true + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" }, "@types/serve-static": { "version": "1.13.9", @@ -8418,6 +8417,77 @@ "integrity": "sha512-qKD5Pbq+QMk4nea4lMuncUMhpEiQwaJyCW7MrvissnRcBDENhVfDmAqQYRQ3X525oTzhar9Zh1cK0L2d1UKYcw==", "dev": true }, + "electron-updater": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz", + "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==", + "requires": { + "@types/semver": "^7.3.6", + "builder-util-runtime": "8.9.1", + "fs-extra": "^10.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "builder-util-runtime": { + "version": "8.9.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz", + "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==", + "requires": { + "debug": "^4.3.2", + "sax": "^1.2.4" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, "electron-window-state": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-5.0.3.tgz", @@ -11824,8 +11894,7 @@ "lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" }, "levn": { "version": "0.3.0", @@ -12289,12 +12358,22 @@ "integrity": "sha1-uvr7yRi1UVTheRdqALsK76rIVLc=", "dev": true }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -13513,8 +13592,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==" }, "multicast-dns": { "version": "6.2.3", @@ -16104,8 +16182,7 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "saxes": { "version": "3.1.11", diff --git a/package.json b/package.json index 76a1d759ff..3169e7e047 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dotenv": "16.0.0", "electron-log": "4.4.1", "electron-store": "8.0.0", + "electron-updater": "4.6.1", "electron-window-state": "5.0.3", "encoding-japanese": "1.0.30", "immer": "9.0.2", diff --git a/src/background.ts b/src/background.ts index e17f64ccfd..278eaa9ea5 100644 --- a/src/background.ts +++ b/src/background.ts @@ -39,6 +39,7 @@ import { SplitterPosition, } from "./type/preload"; +import { autoUpdater } from "electron-updater"; import log from "electron-log"; import dayjs from "dayjs"; import windowStateKeeper from "electron-window-state"; @@ -62,6 +63,9 @@ if (isDevelopment) { ); } +autoUpdater.logger = log; +autoUpdater.autoDownload = false; + let win: BrowserWindow; // 多重起動防止 @@ -219,6 +223,7 @@ const store = new Store<{ experimentalSetting: ExperimentalSetting; acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus; acceptTerms: AcceptTermsStatus; + isAutoUpdateCheck: boolean; splitTextWhenPaste: SplitTextWhenPasteType; splitterPosition: SplitterPosition; }>({ @@ -348,6 +353,10 @@ const store = new Store<{ type: "string", default: "Default", }, + isAutoUpdateCheck: { + type: "boolean", + default: false, + }, experimentalSetting: { type: "object", properties: { @@ -1056,6 +1065,22 @@ ipcMainHandle("ACTIVE_POINT_SCROLL_MODE", (_, { newValue }) => { return store.get("activePointScrollMode", "OFF"); }); +ipcMainHandle("IS_AUTO_UPDATE_CHECK", (_, { newValue }) => { + if (newValue !== undefined) { + store.set("isAutoUpdateCheck", newValue); + } + + return store.get("isAutoUpdateCheck", false); +}); + +ipcMainHandle("UPDATE_CHECK", async () => { + try { + await autoUpdater.checkForUpdatesAndNotify(); + } catch (err: unknown) { + return; + } +}); + ipcMainHandle("IS_AVAILABLE_GPU_MODE", () => { return hasSupportedGpu(); }); @@ -1310,6 +1335,10 @@ app.on("ready", async () => { } createWindow().then(() => runEngineAll()); + const isAutoUpdateCheck = store.get("isAutoUpdateCheck"); + if (isAutoUpdateCheck) { + await autoUpdater.checkForUpdatesAndNotify(); + } }); app.on("second-instance", () => { @@ -1333,3 +1362,36 @@ if (isDevelopment) { }); } } + +//------------------------------------------- +// 自動アップデート関連のイベント処理 +//------------------------------------------- +// アップデートをチェック開始 +autoUpdater.on("checking-for-update", () => { + log.info(process.pid, "checking-for-update..."); +}); +// アップデートが見つかった +autoUpdater.on("update-available", () => { + log.info(process.pid, "Update available."); + const dialogOpts = { + type: "info", + buttons: ["はい", "いいえ"], + message: "アップデート", + detail: "新しいバージョンをがありました。公式サイトを開きますか?", + }; + // ダイアログを表示しすぐに再起動するか確認 + dialog.showMessageBox(win, dialogOpts).then((returnValue) => { + if (returnValue.response === 0) { + shell.openExternal("https://voicevox.hiroshiba.jp/"); + log.info(process.pid, "Open Official Site."); + } + }); +}); +// アップデートがなかった(最新版だった) +autoUpdater.on("update-not-available", () => { + log.info(process.pid, "Update not available."); +}); +// エラーが発生 +autoUpdater.on("error", (err) => { + log.error(process.pid, err); +}); diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index 1066d76d9c..48455eb57a 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -361,6 +361,16 @@ export default defineComponent({ }); }, }, + { type: "separator" }, + { + type: "button", + label: "アップデート確認", + onClick() { + store.dispatch("IS_UPDATE_CHECK_DIALOG_OPEN", { + isUpdateCheckDialogOpen: true, + }); + }, + }, ], }, { diff --git a/src/components/SettingDialog.vue b/src/components/SettingDialog.vue index d322ec1a46..b3f9babf24 100644 --- a/src/components/SettingDialog.vue +++ b/src/components/SettingDialog.vue @@ -573,6 +573,24 @@ > + +
自動アップデートチェック
+ + + + 起動時にアップデートチェックを行います + + +
@@ -779,6 +797,8 @@ export default defineComponent({ const experimentalSetting = computed(() => store.state.experimentalSetting); + const isAutoUpdateCheck = computed(() => store.state.isAutoUpdateCheck); + const currentThemeNameComputed = computed({ get: () => store.state.themeSetting.currentTheme, set: (currentTheme: string) => { @@ -890,6 +910,11 @@ export default defineComponent({ }); }; + const changeIsAutoUpdateCheck = async (isAutoUpdateCheck: boolean) => { + if (store.state.isAutoUpdateCheck === isAutoUpdateCheck) return; + store.dispatch("SET_IS_AUTO_UPDATE_CHECK", { isAutoUpdateCheck }); + }; + const restartEngineProcess = () => { store.dispatch("RESTART_ENGINE", { engineKey: store.state.engineInfos[0].key, @@ -966,6 +991,8 @@ export default defineComponent({ availableAudioOutputDevices, changeinheritAudioInfo, changeExperimentalSetting, + isAutoUpdateCheck, + changeIsAutoUpdateCheck, restartEngineProcess, savingSetting, handleSavingSettingChange, diff --git a/src/electron/preload.ts b/src/electron/preload.ts index a2c20356e4..3773ec44fb 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -166,6 +166,14 @@ const api: Sandbox = { return ipcRendererInvoke("ACTIVE_POINT_SCROLL_MODE", { newValue }); }, + isAutoUpdateCheck: (newValue) => { + return ipcRendererInvoke("IS_AUTO_UPDATE_CHECK", { newValue }); + }, + + updateCheck: () => { + return ipcRendererInvoke("UPDATE_CHECK"); + }, + isAvailableGPUMode: () => { return ipcRendererInvoke("IS_AVAILABLE_GPU_MODE"); }, diff --git a/src/store/type.ts b/src/store/type.ts index 8ffa020cf3..c16cdc30ce 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -891,6 +891,7 @@ export type UiStoreState = { isHelpDialogOpen: boolean; isSettingDialogOpen: boolean; isCharacterOrderDialogOpen: boolean; + isUpdateCheckDialogOpen: boolean; isDefaultStyleSelectDialogOpen: boolean; isHotkeySettingDialogOpen: boolean; isToolbarSettingDialogOpen: boolean; @@ -899,6 +900,7 @@ export type UiStoreState = { isDictionaryManageDialogOpen: boolean; isMaximized: boolean; isPinned: boolean; + isAutoUpdateCheck: boolean; isFullscreen: boolean; }; @@ -949,6 +951,11 @@ type UiStoreTypes = { action(payload: { isSettingDialogOpen: boolean }): void; }; + IS_UPDATE_CHECK_DIALOG_OPEN: { + mutation: { isUpdateCheckDialogOpen: boolean }; + action(payload: { isUpdateCheckDialogOpen: boolean }): void; + }; + IS_HOTKEY_SETTING_DIALOG_OPEN: { mutation: { isHotkeySettingDialogOpen: boolean }; action(payload: { isHotkeySettingDialogOpen: boolean }): void; @@ -1021,6 +1028,11 @@ type UiStoreTypes = { action(payload: { activePointScrollMode: ActivePointScrollMode }): void; }; + SET_IS_AUTO_UPDATE_CHECK: { + mutation: { isAutoUpdateCheck: boolean }; + action(payload: { isAutoUpdateCheck: boolean }): void; + }; + DETECT_UNMAXIMIZED: { mutation: undefined; action(): void; diff --git a/src/store/ui.ts b/src/store/ui.ts index d6a2b496b2..47555a13dd 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -35,6 +35,7 @@ export const uiStoreState: UiStoreState = { activePointScrollMode: "OFF", isHelpDialogOpen: false, isSettingDialogOpen: false, + isUpdateCheckDialogOpen: false, isHotkeySettingDialogOpen: false, isToolbarSettingDialogOpen: false, isCharacterOrderDialogOpen: false, @@ -45,6 +46,7 @@ export const uiStoreState: UiStoreState = { isMaximized: false, isPinned: false, isFullscreen: false, + isAutoUpdateCheck: false, }; export const uiStore: VoiceVoxStoreOptions = @@ -94,6 +96,12 @@ export const uiStore: VoiceVoxStoreOptions = ) { state.isSettingDialogOpen = isSettingDialogOpen; }, + IS_UPDATE_CHECK_DIALOG_OPEN( + state, + { isUpdateCheckDialogOpen }: { isUpdateCheckDialogOpen: boolean } + ) { + state.isUpdateCheckDialogOpen = isUpdateCheckDialogOpen; + }, IS_HOTKEY_SETTING_DIALOG_OPEN(state, { isHotkeySettingDialogOpen }) { state.isHotkeySettingDialogOpen = isHotkeySettingDialogOpen; }, @@ -155,6 +163,12 @@ export const uiStore: VoiceVoxStoreOptions = ) { state.activePointScrollMode = activePointScrollMode; }, + SET_IS_AUTO_UPDATE_CHECK( + state, + { isAutoUpdateCheck }: { isAutoUpdateCheck: boolean } + ) { + state.isAutoUpdateCheck = isAutoUpdateCheck; + }, DETECT_UNMAXIMIZED(state) { state.isMaximized = false; }, @@ -225,6 +239,31 @@ export const uiStore: VoiceVoxStoreOptions = commit("IS_SETTING_DIALOG_OPEN", { isSettingDialogOpen }); }, + async IS_UPDATE_CHECK_DIALOG_OPEN( + { state, commit }, + { isUpdateCheckDialogOpen }: { isUpdateCheckDialogOpen: boolean } + ) { + if (state.isUpdateCheckDialogOpen === isUpdateCheckDialogOpen) return; + + if (isUpdateCheckDialogOpen) { + commit("LOCK_UI"); + commit("LOCK_MENUBAR"); + + const result: number = await window.electron.showQuestionDialog({ + type: "info", + title: "アップデートチェック", + message: "アップデートチェックを行います。\nよろしいですか?", + buttons: ["はい", "いいえ"], + cancelId: 1, + }); + commit("UNLOCK_UI"); + commit("UNLOCK_MENUBAR"); + if (result == 1) { + return; + } + window.electron.updateCheck(); + } + }, IS_HOTKEY_SETTING_DIALOG_OPEN( { state, commit }, { isHotkeySettingDialogOpen } @@ -384,6 +423,16 @@ export const uiStore: VoiceVoxStoreOptions = ), }); }, + async SET_IS_AUTO_UPDATE_CHECK( + { commit }, + { isAutoUpdateCheck }: { isAutoUpdateCheck: boolean } + ) { + commit("SET_IS_AUTO_UPDATE_CHECK", { + isAutoUpdateCheck: await window.electron.isAutoUpdateCheck( + isAutoUpdateCheck + ), + }); + }, async GET_ACTIVE_POINT_SCROLL_MODE({ commit }) { commit("SET_ACTIVE_POINT_SCROLL_MODE", { activePointScrollMode: await window.electron.activePointScrollMode(), diff --git a/src/type/ipc.d.ts b/src/type/ipc.d.ts index 8054057061..bcd65f3e6d 100644 --- a/src/type/ipc.d.ts +++ b/src/type/ipc.d.ts @@ -123,6 +123,16 @@ type IpcIHData = { return: import("@/type/preload").ActivePointScrollMode; }; + IS_AUTO_UPDATE_CHECK: { + args: [obj: { newValue?: boolean }]; + return: boolean; + }; + + UPDATE_CHECK: { + args: []; + return: void; + }; + IS_AVAILABLE_GPU_MODE: { args: []; return: boolean; diff --git a/src/type/preload.d.ts b/src/type/preload.d.ts index 40d64f2831..61b9d3b58c 100644 --- a/src/type/preload.d.ts +++ b/src/type/preload.d.ts @@ -51,6 +51,8 @@ export interface Sandbox { activePointScrollMode( newValue?: ActivePointScrollMode ): Promise; + isAutoUpdateCheck(newValue?: boolean): Promise; + updateCheck(): void; isAvailableGPUMode(): Promise; onReceivedIPCMsg( channel: T, diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index 22b73beab6..53cdfb6f6f 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -35,6 +35,7 @@ describe("store/vuex.js test", () => { activePointScrollMode: "OFF", isHelpDialogOpen: false, isSettingDialogOpen: false, + isUpdateCheckDialogOpen: false, isHotkeySettingDialogOpen: false, isToolbarSettingDialogOpen: false, isCharacterOrderDialogOpen: false, @@ -44,6 +45,7 @@ describe("store/vuex.js test", () => { isAcceptTermsDialogOpen: false, isMaximized: false, savedLastCommandUnixMillisec: null, + isAutoUpdateCheck: false, savingSetting: { fileEncoding: "UTF-8", fileNamePattern: "", @@ -147,8 +149,10 @@ describe("store/vuex.js test", () => { assert.equal(store.state.useGpu, false); assert.equal(store.state.inheritAudioInfo, true); assert.equal(store.state.activePointScrollMode, "OFF"); + assert.equal(store.state.isAutoUpdateCheck, false); assert.equal(store.state.isHelpDialogOpen, false); assert.equal(store.state.isSettingDialogOpen, false); + assert.equal(store.state.isUpdateCheckDialogOpen, false); assert.equal(store.state.isHotkeySettingDialogOpen, false); assert.equal(store.state.isCharacterOrderDialogOpen, false); assert.equal(store.state.isDefaultStyleSelectDialogOpen, false);