diff --git a/src/main/constants.ts b/src/main/constants.ts index 243891e7..16642d50 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -26,11 +26,10 @@ export const commonRedistPath = path.join( "CommonRedist" ); -export const logsPath = path.join(SystemPath.getPath("userData"), "logs"); - -export const seedsPath = app.isPackaged - ? path.join(process.resourcesPath, "seeds") - : path.join(__dirname, "..", "..", "seeds"); +export const logsPath = path.join( + SystemPath.getPath("userData"), + `logs${isStaging ? "-staging" : ""}` +); export const achievementSoundPath = app.isPackaged ? path.join(process.resourcesPath, "achievement.wav") diff --git a/src/main/index.ts b/src/main/index.ts index 5151b956..de88a548 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,7 +4,7 @@ import i18n from "i18next"; import path from "node:path"; import url from "node:url"; import { electronApp, optimizer } from "@electron-toolkit/utils"; -import { logger, WindowManager } from "@main/services"; +import { logger, clearGamesPlaytime, WindowManager } from "@main/services"; import resources from "@locales"; import { PythonRPC } from "./services/python-rpc"; import { db, levelKeys } from "./level"; @@ -143,9 +143,17 @@ app.on("window-all-closed", () => { WindowManager.mainWindow = null; }); -app.on("before-quit", () => { - /* Disconnects libtorrent */ - PythonRPC.kill(); +let canAppBeClosed = false; + +app.on("before-quit", async (e) => { + if (!canAppBeClosed) { + e.preventDefault(); + /* Disconnects libtorrent */ + PythonRPC.kill(); + await clearGamesPlaytime(); + canAppBeClosed = true; + app.quit(); + } }); app.on("activate", () => { diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 1bd94e2b..deb93f4e 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -28,7 +28,7 @@ interface GameExecutables { [key: string]: ExecutableInfo[]; } -const TICKS_TO_UPDATE_API = 120; +const TICKS_TO_UPDATE_API = 80; let currentTick = 1; const isWindowsPlatform = process.platform === "win32"; @@ -225,7 +225,18 @@ function onOpenGame(game: Game) { }); if (game.remoteId) { - updateGamePlaytime(game, 0, new Date()).catch(() => {}); + updateGamePlaytime( + game, + game.unsyncedDeltaPlayTimeInMilliseconds ?? 0, + new Date() + ) + .then(() => { + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...game, + unsyncedDeltaPlayTimeInMilliseconds: 0, + }); + }) + .catch(() => {}); if (game.automaticCloudSync) { CloudSync.uploadSaveGame( @@ -260,22 +271,34 @@ function onTickGame(game: Game) { }); if (currentTick % TICKS_TO_UPDATE_API === 0) { + const deltaToSync = + now - + gamePlaytime.lastSyncTick + + (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); + const gamePromise = game.remoteId - ? updateGamePlaytime( - game, - now - gamePlaytime.lastSyncTick, - game.lastTimePlayed! - ) + ? updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) : createGame(game); gamePromise .then(() => { + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...game, + unsyncedDeltaPlayTimeInMilliseconds: 0, + }); + }) + .catch(() => { + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...game, + unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, + }); + }) + .finally(() => { gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { ...gamePlaytime, lastSyncTick: now, }); - }) - .catch(() => {}); + }); } } @@ -286,12 +309,6 @@ const onCloseGame = (game: Game) => { gamesPlaytime.delete(levelKeys.game(game.shop, game.objectId)); if (game.remoteId) { - updateGamePlaytime( - game, - performance.now() - gamePlaytime.lastSyncTick, - game.lastTimePlayed! - ).catch(() => {}); - if (game.automaticCloudSync) { CloudSync.uploadSaveGame( game.objectId, @@ -300,7 +317,38 @@ const onCloseGame = (game: Game) => { CloudSync.getBackupLabel(true) ); } + + const deltaToSync = + performance.now() - + gamePlaytime.lastSyncTick + + (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); + + return updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) + .then(() => { + return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...game, + unsyncedDeltaPlayTimeInMilliseconds: 0, + }); + }) + .catch(() => { + return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...game, + unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, + }); + }); } else { - createGame(game).catch(() => {}); + return createGame(game).catch(() => {}); } }; + +export const clearGamesPlaytime = async () => { + for (const game of gamesPlaytime.keys()) { + const gameData = await gamesSublevel.get(game); + + if (gameData) { + await onCloseGame(gameData); + } + } + + gamesPlaytime.clear(); +}; diff --git a/src/types/level.types.ts b/src/types/level.types.ts index fd904ca5..6a729cc5 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -34,6 +34,7 @@ export interface Game { title: string; iconUrl: string | null; playTimeInMilliseconds: number; + unsyncedDeltaPlayTimeInMilliseconds?: number; lastTimePlayed: Date | null; objectId: string; shop: GameShop;