From d168c6249ba017df95dbf9c0a9311a92377a7df1 Mon Sep 17 00:00:00 2001 From: 1-alex98 Date: Tue, 20 Jun 2023 00:21:59 +0200 Subject: [PATCH 1/4] Associating replays Fixes #147 Fixes #231 --- .electron-builder.config.js | 7 +++ src/main/index.ts | 55 ++++++++++++++++++++-- src/renderer/api/cache-db.ts | 8 ++++ src/renderer/api/content/replay-content.ts | 12 +++-- src/renderer/api/game.ts | 2 +- src/renderer/index.ts | 22 +++++---- src/renderer/model/cache/replay.ts | 1 + src/renderer/views/library/replays.vue | 4 ++ src/renderer/workers/parse-replay.ts | 1 + 9 files changed, 94 insertions(+), 18 deletions(-) diff --git a/.electron-builder.config.js b/.electron-builder.config.js index 49f3d27a..c3f9a1e9 100644 --- a/.electron-builder.config.js +++ b/.electron-builder.config.js @@ -30,6 +30,13 @@ const config = { category: "Game", }, publish: null, + fileAssociations: [ + { + "ext": "sdfz", + "description": "BAR Replay File", + "role": "Viewer" + } + ] }; module.exports = config; diff --git a/src/main/index.ts b/src/main/index.ts index ac22a46c..c656ef05 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -101,6 +101,39 @@ export class Application { this.mainWindow.window.on("restore", () => this.mainWindow?.window.flashFrame(false)); this.setupHandlers(); + this.setupSecondInstanceOpened(); + } + + protected setupSecondInstanceOpened() { + app.on("second-instance", (_event, commandLine, _workingDirectory, _additionalData) => { + console.log("Second Instance opening with command line: " + commandLine); + + this.focusWindows(); + + this.openFile(commandLine[commandLine.length-1]); + }); + + app.on("open-file", (_, path) => { + console.log("Mac OS opening file: " + path); + + this.focusWindows(); + + this.openFile(path); + }); + } + + protected openFile(path: string) { + if(!path.endsWith(".sdfz")){ + return; + } + this.mainWindow?.window.webContents.send("open-replay", path); + } + + private focusWindows() { + if (this.mainWindow?.window) { + if (this.mainWindow?.window.isMinimized()) this.mainWindow?.window.restore(); + this.mainWindow?.window.focus(); + } } protected setupHandlers() { @@ -108,11 +141,11 @@ export class Application { return this.getInfo(); }); - ipcMain.handle("flashFrame", (event, flag: boolean) => { + ipcMain.handle("flashFrame", (_event, flag: boolean) => { this.mainWindow?.window.flashFrame(flag); }); - ipcMain.handle("encryptString", async (event, str: string) => { + ipcMain.handle("encryptString", async (_event, str: string) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.encryptString(str); } @@ -120,13 +153,20 @@ export class Application { return str; }); - ipcMain.handle("decryptString", async (event, buffer: Buffer) => { + ipcMain.handle("decryptString", async (_event, buffer: Buffer) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.decryptString(buffer); } console.warn(`encryption not available, returning buffer`); return buffer.toString(); }); + let openedReplayAlready = false; + ipcMain.handle("opened-replay", () => { + console.log(process.argv); + if (process.argv.length == 0 || openedReplayAlready) return null; + openedReplayAlready = true; //in case of reloading the app do not open replay again + return process.argv[process.argv.length - 1].endsWith(".sdfz") ? process.argv[process.argv.length - 1] : null; + }); } protected getInfo() { @@ -161,6 +201,11 @@ export class Application { } } -unhandled(); +const gotTheLock = app.requestSingleInstanceLock(); -new Application(); +if (!gotTheLock) { + app.quit(); +} else { + unhandled(); + new Application(); +} diff --git a/src/renderer/api/cache-db.ts b/src/renderer/api/cache-db.ts index bb13d2c2..a7837b09 100644 --- a/src/renderer/api/cache-db.ts +++ b/src/renderer/api/cache-db.ts @@ -157,6 +157,14 @@ export class CacheDbAPI extends Kysely { .execute(); }, }, + "2023-06-20": { + async up(db) { + await db.schema + .alterTable("replay") + .addColumn("filePath", "varchar", (col) => col) + .execute(); + }, + }, }; } diff --git a/src/renderer/api/content/replay-content.ts b/src/renderer/api/content/replay-content.ts index f96b6a5c..1a7b6cc0 100644 --- a/src/renderer/api/content/replay-content.ts +++ b/src/renderer/api/content/replay-content.ts @@ -58,6 +58,11 @@ export class ReplayContentAPI { return query.offset(options.offset).limit(options.limit).execute(); } + public async parseAndLaunchReplay(replayPath: string) { + const replay = await this.parseReplay(replayPath); + api.game.launch((await replay) as Replay); + } + public async getReplayById(replayId: number) { return api.cacheDb.selectFrom("replay").selectAll().where("replayId", "=", replayId).executeTakeFirst(); } @@ -125,12 +130,12 @@ export class ReplayContentAPI { } } - protected async cacheReplay(replayFilePath: string) { + public async cacheReplay(replayFilePath: string): Promise { const replayFileName = path.parse(replayFilePath).base; console.debug(`Caching: ${replayFileName}`); - + let replayData; try { - const replayData = await this.parseReplay(replayFilePath); + replayData = await this.parseReplay(replayFilePath); if (replayData.gameId === "00000000000000000000000000000000") { throw new Error(`invalid gameId for replay: ${replayFileName}`); @@ -169,5 +174,6 @@ export class ReplayContentAPI { } this.replayCacheQueue.delete(replayFileName); + return replayData?.gameId; } } diff --git a/src/renderer/api/game.ts b/src/renderer/api/game.ts index 371234ce..90e80a6c 100644 --- a/src/renderer/api/game.ts +++ b/src/renderer/api/game.ts @@ -60,7 +60,7 @@ export class GameAPI { const scriptPath = (launchArg = path.join(api.info.contentPath, this.scriptName)); await fs.promises.writeFile(scriptPath, script); } else if (isReplay(arg)) { - launchArg = path.join(api.content.replays.replaysDir, arg.fileName); + launchArg = arg.filePath ? arg.filePath : path.join(api.content.replays.replaysDir, arg.fileName); } const args = ["--write-dir", api.info.contentPath, "--isolation", launchArg]; diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 39955e27..73a51b55 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -4,6 +4,7 @@ import "flag-icons/css/flag-icons.min.css"; import "primeicons/primeicons.css"; import "@/styles/styles.scss"; +import { ipcRenderer } from "electron"; import path from "path"; import PrimeVue from "primevue/config"; import Tooltip from "primevue/tooltip"; @@ -44,15 +45,7 @@ declare module "vue-router" { } }); - // window.addEventListener("beforeunload", async (event) => { - // console.debug("beforeunload", event); - // event.preventDefault(); - // if (api.comms.isConnected.value) { - // //await api.comms.request("c.auth.disconnect", {}); - // api.comms.disconnect(); - // } - // return event; - // }); + await replayOpenedHandlers(); })(); async function setupVue() { @@ -75,6 +68,17 @@ async function setupVue() { } } +async function replayOpenedHandlers() { + const replay = await ipcRenderer.invoke("opened-replay"); + if (replay) { + api.content.replays.parseAndLaunchReplay(replay); + } + ipcRenderer.on("open-replay", (_event, arg) => { + console.log("renderer recaeived replay to launch:" + arg); + api.content.replays.parseAndLaunchReplay(arg); + }); +} + async function setupI18n() { const myLocale = Intl.DateTimeFormat().resolvedOptions().locale.split("-")[0]; diff --git a/src/renderer/model/cache/replay.ts b/src/renderer/model/cache/replay.ts index 8181d22d..bdfc9f4d 100644 --- a/src/renderer/model/cache/replay.ts +++ b/src/renderer/model/cache/replay.ts @@ -5,6 +5,7 @@ export type ReplayTable = { replayId: Generated; gameId: string; fileName: string; + filePath: string | null; engineVersion: string; gameVersion: string; mapScriptName: string; diff --git a/src/renderer/views/library/replays.vue b/src/renderer/views/library/replays.vue index fa28dd35..98ee9d8b 100644 --- a/src/renderer/views/library/replays.vue +++ b/src/renderer/views/library/replays.vue @@ -166,6 +166,10 @@ function watchReplay(replay: Replay) { } function showReplayFile(replay: Replay) { + if(replay.filePath) + { + shell.showItemInFolder(replay.filePath); + } shell.showItemInFolder(path.join(api.content.replays.replaysDir, replay.fileName)); } diff --git a/src/renderer/workers/parse-replay.ts b/src/renderer/workers/parse-replay.ts index c365a84b..33de256f 100644 --- a/src/renderer/workers/parse-replay.ts +++ b/src/renderer/workers/parse-replay.ts @@ -23,6 +23,7 @@ export const parseReplay = exposeWorkerFunction(async (replayPath: string) => { return { gameId: replayData.header.gameId, fileName: path.parse(replayPath).base, + filePath: replayPath, engineVersion: replayData.info.meta.engine, gameVersion: replayData.info.meta.game, mapScriptName: replayData.info.meta.map, From e29feee7115da1a1a572a6f8404031972117160b Mon Sep 17 00:00:00 2001 From: 1-alex98 Date: Tue, 20 Jun 2023 00:21:59 +0200 Subject: [PATCH 2/4] Associating replays Fixes #147 Fixes #231 --- .electron-builder.config.js | 7 +++ src/main/index.ts | 55 ++++++++++++++++++++-- src/renderer/api/cache-db.ts | 8 ++++ src/renderer/api/content/replay-content.ts | 6 ++- src/renderer/api/game.ts | 2 +- src/renderer/index.ts | 22 +++++---- src/renderer/model/cache/replay.ts | 1 + src/renderer/views/library/replays.vue | 4 ++ src/renderer/workers/parse-replay.ts | 1 + 9 files changed, 90 insertions(+), 16 deletions(-) diff --git a/.electron-builder.config.js b/.electron-builder.config.js index 49f3d27a..c3f9a1e9 100644 --- a/.electron-builder.config.js +++ b/.electron-builder.config.js @@ -30,6 +30,13 @@ const config = { category: "Game", }, publish: null, + fileAssociations: [ + { + "ext": "sdfz", + "description": "BAR Replay File", + "role": "Viewer" + } + ] }; module.exports = config; diff --git a/src/main/index.ts b/src/main/index.ts index ac22a46c..f0c1b504 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -101,6 +101,39 @@ export class Application { this.mainWindow.window.on("restore", () => this.mainWindow?.window.flashFrame(false)); this.setupHandlers(); + this.setupSecondInstanceOpened(); + } + + protected setupSecondInstanceOpened() { + app.on("second-instance", (_event, commandLine, _workingDirectory, _additionalData) => { + console.log("Second Instance opening with command line: " + commandLine); + + this.focusWindows(); + + this.openFile(commandLine[commandLine.length - 1]); + }); + + app.on("open-file", (_, path) => { + console.log("Mac OS opening file: " + path); + + this.focusWindows(); + + this.openFile(path); + }); + } + + protected openFile(path: string) { + if (!path.endsWith(".sdfz")) { + return; + } + this.mainWindow?.window.webContents.send("open-replay", path); + } + + private focusWindows() { + if (this.mainWindow?.window) { + if (this.mainWindow?.window.isMinimized()) this.mainWindow?.window.restore(); + this.mainWindow?.window.focus(); + } } protected setupHandlers() { @@ -108,11 +141,11 @@ export class Application { return this.getInfo(); }); - ipcMain.handle("flashFrame", (event, flag: boolean) => { + ipcMain.handle("flashFrame", (_event, flag: boolean) => { this.mainWindow?.window.flashFrame(flag); }); - ipcMain.handle("encryptString", async (event, str: string) => { + ipcMain.handle("encryptString", async (_event, str: string) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.encryptString(str); } @@ -120,13 +153,20 @@ export class Application { return str; }); - ipcMain.handle("decryptString", async (event, buffer: Buffer) => { + ipcMain.handle("decryptString", async (_event, buffer: Buffer) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.decryptString(buffer); } console.warn(`encryption not available, returning buffer`); return buffer.toString(); }); + let openedReplayAlready = false; + ipcMain.handle("opened-replay", () => { + console.log(process.argv); + if (process.argv.length == 0 || openedReplayAlready) return null; + openedReplayAlready = true; //in case of reloading the app do not open replay again + return process.argv[process.argv.length - 1].endsWith(".sdfz") ? process.argv[process.argv.length - 1] : null; + }); } protected getInfo() { @@ -161,6 +201,11 @@ export class Application { } } -unhandled(); +const gotTheLock = app.requestSingleInstanceLock(); -new Application(); +if (!gotTheLock) { + app.quit(); +} else { + unhandled(); + new Application(); +} diff --git a/src/renderer/api/cache-db.ts b/src/renderer/api/cache-db.ts index bb13d2c2..a7837b09 100644 --- a/src/renderer/api/cache-db.ts +++ b/src/renderer/api/cache-db.ts @@ -157,6 +157,14 @@ export class CacheDbAPI extends Kysely { .execute(); }, }, + "2023-06-20": { + async up(db) { + await db.schema + .alterTable("replay") + .addColumn("filePath", "varchar", (col) => col) + .execute(); + }, + }, }; } diff --git a/src/renderer/api/content/replay-content.ts b/src/renderer/api/content/replay-content.ts index f96b6a5c..1610d38b 100644 --- a/src/renderer/api/content/replay-content.ts +++ b/src/renderer/api/content/replay-content.ts @@ -58,6 +58,11 @@ export class ReplayContentAPI { return query.offset(options.offset).limit(options.limit).execute(); } + public async parseAndLaunchReplay(replayPath: string) { + const replay = await this.parseReplay(replayPath); + api.game.launch((await replay) as Replay); + } + public async getReplayById(replayId: number) { return api.cacheDb.selectFrom("replay").selectAll().where("replayId", "=", replayId).executeTakeFirst(); } @@ -124,7 +129,6 @@ export class ReplayContentAPI { } } } - protected async cacheReplay(replayFilePath: string) { const replayFileName = path.parse(replayFilePath).base; console.debug(`Caching: ${replayFileName}`); diff --git a/src/renderer/api/game.ts b/src/renderer/api/game.ts index 371234ce..90e80a6c 100644 --- a/src/renderer/api/game.ts +++ b/src/renderer/api/game.ts @@ -60,7 +60,7 @@ export class GameAPI { const scriptPath = (launchArg = path.join(api.info.contentPath, this.scriptName)); await fs.promises.writeFile(scriptPath, script); } else if (isReplay(arg)) { - launchArg = path.join(api.content.replays.replaysDir, arg.fileName); + launchArg = arg.filePath ? arg.filePath : path.join(api.content.replays.replaysDir, arg.fileName); } const args = ["--write-dir", api.info.contentPath, "--isolation", launchArg]; diff --git a/src/renderer/index.ts b/src/renderer/index.ts index 39955e27..73a51b55 100644 --- a/src/renderer/index.ts +++ b/src/renderer/index.ts @@ -4,6 +4,7 @@ import "flag-icons/css/flag-icons.min.css"; import "primeicons/primeicons.css"; import "@/styles/styles.scss"; +import { ipcRenderer } from "electron"; import path from "path"; import PrimeVue from "primevue/config"; import Tooltip from "primevue/tooltip"; @@ -44,15 +45,7 @@ declare module "vue-router" { } }); - // window.addEventListener("beforeunload", async (event) => { - // console.debug("beforeunload", event); - // event.preventDefault(); - // if (api.comms.isConnected.value) { - // //await api.comms.request("c.auth.disconnect", {}); - // api.comms.disconnect(); - // } - // return event; - // }); + await replayOpenedHandlers(); })(); async function setupVue() { @@ -75,6 +68,17 @@ async function setupVue() { } } +async function replayOpenedHandlers() { + const replay = await ipcRenderer.invoke("opened-replay"); + if (replay) { + api.content.replays.parseAndLaunchReplay(replay); + } + ipcRenderer.on("open-replay", (_event, arg) => { + console.log("renderer recaeived replay to launch:" + arg); + api.content.replays.parseAndLaunchReplay(arg); + }); +} + async function setupI18n() { const myLocale = Intl.DateTimeFormat().resolvedOptions().locale.split("-")[0]; diff --git a/src/renderer/model/cache/replay.ts b/src/renderer/model/cache/replay.ts index 8181d22d..bdfc9f4d 100644 --- a/src/renderer/model/cache/replay.ts +++ b/src/renderer/model/cache/replay.ts @@ -5,6 +5,7 @@ export type ReplayTable = { replayId: Generated; gameId: string; fileName: string; + filePath: string | null; engineVersion: string; gameVersion: string; mapScriptName: string; diff --git a/src/renderer/views/library/replays.vue b/src/renderer/views/library/replays.vue index fa28dd35..ea405612 100644 --- a/src/renderer/views/library/replays.vue +++ b/src/renderer/views/library/replays.vue @@ -166,6 +166,10 @@ function watchReplay(replay: Replay) { } function showReplayFile(replay: Replay) { + if (replay.filePath) { + shell.showItemInFolder(replay.filePath); + return; + } shell.showItemInFolder(path.join(api.content.replays.replaysDir, replay.fileName)); } diff --git a/src/renderer/workers/parse-replay.ts b/src/renderer/workers/parse-replay.ts index c365a84b..33de256f 100644 --- a/src/renderer/workers/parse-replay.ts +++ b/src/renderer/workers/parse-replay.ts @@ -23,6 +23,7 @@ export const parseReplay = exposeWorkerFunction(async (replayPath: string) => { return { gameId: replayData.header.gameId, fileName: path.parse(replayPath).base, + filePath: replayPath, engineVersion: replayData.info.meta.engine, gameVersion: replayData.info.meta.game, mapScriptName: replayData.info.meta.map, From 5fdbd2495119cd479f5cb8ce87ca5115741359d7 Mon Sep 17 00:00:00 2001 From: 1-alex98 Date: Sat, 24 Jun 2023 18:46:52 +0200 Subject: [PATCH 3/4] missing --- src/renderer/api/content/replay-content.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/renderer/api/content/replay-content.ts b/src/renderer/api/content/replay-content.ts index be6aca1b..8b1c7eca 100644 --- a/src/renderer/api/content/replay-content.ts +++ b/src/renderer/api/content/replay-content.ts @@ -132,9 +132,8 @@ export class ReplayContentAPI { protected async cacheReplay(replayFilePath: string) { const replayFileName = path.parse(replayFilePath).base; console.debug(`Caching: ${replayFileName}`); - let replayData; try { - replayData = await this.parseReplay(replayFilePath); + const replayData = await this.parseReplay(replayFilePath); if (replayData.gameId === "00000000000000000000000000000000") { throw new Error(`invalid gameId for replay: ${replayFileName}`); @@ -173,6 +172,5 @@ export class ReplayContentAPI { } this.replayCacheQueue.delete(replayFileName); - return replayData?.gameId; - } + } } From 89a6041ac3d121cc2c702103328d9527e541296f Mon Sep 17 00:00:00 2001 From: 1-alex98 Date: Sat, 24 Jun 2023 18:48:04 +0200 Subject: [PATCH 4/4] Format --- src/renderer/api/content/replay-content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/api/content/replay-content.ts b/src/renderer/api/content/replay-content.ts index 8b1c7eca..d95da3bb 100644 --- a/src/renderer/api/content/replay-content.ts +++ b/src/renderer/api/content/replay-content.ts @@ -172,5 +172,5 @@ export class ReplayContentAPI { } this.replayCacheQueue.delete(replayFileName); - } + } }