From da5cc11bff3ebeff1c1a3cad71c69ce9637ea2a4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 15 Jun 2024 02:15:58 -0300 Subject: [PATCH] feat: sync library --- src/main/entity/game.entity.ts | 3 + .../events/library/add-game-to-library.ts | 21 ++++++ .../library/remove-game-from-library.ts | 7 ++ src/main/main.ts | 75 ++++++++++++++++++- src/main/services/hydra-api.ts | 9 +++ src/main/services/process-watcher.ts | 59 ++++++++++++++- 6 files changed, 167 insertions(+), 7 deletions(-) diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index 49ad2716..83cc4001 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -22,6 +22,9 @@ export class Game { @Column("text", { unique: true }) objectID: string; + @Column("text", { unique: true, nullable: true }) + remoteId: string; + @Column("text") title: string; diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index 1c7447e5..7f283636 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -6,6 +6,7 @@ import type { GameShop } from "@types"; import { getFileBase64, getSteamAppAsset } from "@main/helpers"; import { steamGamesWorker } from "@main/workers"; +import { HydraApi } from "@main/services/hydra-api"; const addGameToLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -49,6 +50,26 @@ const addGameToLibrary = async ( } }); } + + const game = await gameRepository.findOne({ where: { objectID } }); + + HydraApi.post("/games", { + objectId: objectID, + playTimeInMilliseconds: game?.playTimeInMilliseconds, + shop, + lastTimePlayed: game?.lastTimePlayed, + }).then((response) => { + const { + id: remoteId, + playTimeInMilliseconds, + lastTimePlayed, + } = response.data; + + gameRepository.update( + { objectID }, + { remoteId, playTimeInMilliseconds, lastTimePlayed } + ); + }); }); }; diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 29a7a635..8bbd83e3 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -1,5 +1,6 @@ import { registerEvent } from "../register-event"; import { gameRepository } from "../../repository"; +import { HydraApi } from "@main/services/hydra-api"; const removeGameFromLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -9,6 +10,12 @@ const removeGameFromLibrary = async ( { id: gameId }, { isDeleted: true, executablePath: null } ); + + const game = await gameRepository.findOne({ where: { id: gameId } }); + + if (game?.remoteId) { + HydraApi.delete(`/games/${game.remoteId}`); + } }; registerEvent("removeGameFromLibrary", removeGameFromLibrary); diff --git a/src/main/main.ts b/src/main/main.ts index 7e5692d5..a866972a 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,15 +1,22 @@ -import { DownloadManager, RepacksManager, startMainLoop } from "./services"; +import { + DownloadManager, + RepacksManager, + logger, + startMainLoop, +} from "./services"; import { downloadQueueRepository, + gameRepository, repackRepository, userPreferencesRepository, } from "./repository"; import { UserPreferences } from "./entity"; import { RealDebridClient } from "./services/real-debrid"; -import { fetchDownloadSourcesAndUpdate } from "./helpers"; +import { fetchDownloadSourcesAndUpdate, getSteamAppAsset } from "./helpers"; import { publishNewRepacksNotifications } from "./services/notifications"; import { MoreThan } from "typeorm"; import { HydraApi } from "./services/hydra-api"; +import { steamGamesWorker } from "./workers"; startMainLoop(); @@ -21,7 +28,69 @@ const loadState = async (userPreferences: UserPreferences | null) => { if (userPreferences?.realDebridApiToken) RealDebridClient.authorize(userPreferences?.realDebridApiToken); - HydraApi.setupApi(); + HydraApi.setupApi() + .then(async () => { + if (HydraApi.isLoggedIn()) { + const games = await HydraApi.get("/games"); + + for (const game of games.data) { + const localGame = await gameRepository.findOne({ + where: { + objectID: game.objectId, + }, + }); + + if (localGame) { + const updatedLastTimePlayed = + localGame.lastTimePlayed == null || + new Date(game.lastTimePlayed) > localGame.lastTimePlayed + ? new Date(game.lastTimePlayed) + : localGame.lastTimePlayed; + + const updatedPlayTime = + localGame.playTimeInMilliseconds < game.playTimeInMilliseconds + ? game.playTimeInMilliseconds + : localGame.playTimeInMilliseconds; + + gameRepository.update( + { + objectID: game.objectId, + shop: "steam", + lastTimePlayed: updatedLastTimePlayed, + playTimeInMilliseconds: updatedPlayTime, + }, + { remoteId: game.id } + ); + } else { + const steamGame = await steamGamesWorker.run( + Number(game.objectId), + { + name: "getById", + } + ); + + if (steamGame) { + const iconUrl = steamGame?.clientIcon + ? getSteamAppAsset("icon", game.objectId, steamGame.clientIcon) + : null; + + gameRepository.insert({ + objectID: game.objectId, + title: steamGame?.name, + remoteId: game.id, + shop: game.shop, + iconUrl, + lastTimePlayed: game.lastTimePlayed, + playTimeInMilliseconds: game.playTimeInMilliseconds, + }); + } + } + } + } + }) + .catch((err) => { + logger.error("erro api GET: /games", err); + }); const [nextQueueItem] = await downloadQueueRepository.find({ order: { diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 6c275df0..a4463723 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -14,6 +14,10 @@ export class HydraApi { expirationTimestamp: 0, }; + static isLoggedIn() { + return this.userAuth.authToken !== ""; + } + static async handleExternalAuth(auth: string) { const { payload } = url.parse(auth, true).query; @@ -140,4 +144,9 @@ export class HydraApi { await this.revalidateAccessTokenIfExpired(); return this.instance.patch(url, data, this.getAxiosConfig()); } + + static async delete(url: string) { + await this.revalidateAccessTokenIfExpired(); + return this.instance.delete(url, this.getAxiosConfig()); + } } diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index ea1b6355..577a7770 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -4,8 +4,12 @@ import { IsNull, Not } from "typeorm"; import { gameRepository } from "@main/repository"; import { getProcesses } from "@main/helpers"; import { WindowManager } from "./window-manager"; +import { HydraApi } from "./hydra-api"; -const gamesPlaytime = new Map(); +const gamesPlaytime = new Map< + number, + { lastTick: number; firstTick: number } +>(); export const watchProcesses = async () => { const games = await gameRepository.find({ @@ -37,7 +41,9 @@ export const watchProcesses = async () => { if (gameProcess) { if (gamesPlaytime.has(game.id)) { - const zero = gamesPlaytime.get(game.id) ?? 0; + const gamePlaytime = gamesPlaytime.get(game.id)!; + + const zero = gamePlaytime.lastTick; const delta = performance.now() - zero; if (WindowManager.mainWindow) { @@ -48,12 +54,57 @@ export const watchProcesses = async () => { playTimeInMilliseconds: game.playTimeInMilliseconds + delta, lastTimePlayed: new Date(), }); - } - gamesPlaytime.set(game.id, performance.now()); + gamesPlaytime.set(game.id, { + ...gamePlaytime, + lastTick: performance.now(), + }); + } else { + if (game.remoteId) { + HydraApi.put(`/games/${game.remoteId}`, { + playTimeDeltaInMilliseconds: 0, + lastTimePlayed: new Date(), + }); + } else { + HydraApi.post("/games", { + objectId: game.objectID, + playTimeInMilliseconds: Math.round(game.playTimeInMilliseconds), + shop: game.shop, + lastTimePlayed: new Date(), + }).then((response) => { + const { id: remoteId } = response.data; + gameRepository.update({ objectID: game.objectID }, { remoteId }); + }); + } + + gamesPlaytime.set(game.id, { + lastTick: performance.now(), + firstTick: performance.now(), + }); + } } else if (gamesPlaytime.has(game.id)) { + const gamePlaytime = gamesPlaytime.get(game.id)!; gamesPlaytime.delete(game.id); + if (game.remoteId) { + HydraApi.put(`/games/${game.remoteId}`, { + playTimeInMilliseconds: Math.round( + performance.now() - gamePlaytime.firstTick + ), + lastTimePlayed: game.lastTimePlayed, + }); + } else { + HydraApi.post("/games", { + objectId: game.objectID, + playTimeInMilliseconds: Math.round(game.playTimeInMilliseconds), + shop: game.shop, + lastTimePlayed: game.lastTimePlayed, + }).then((response) => { + const { id: remoteId } = response.data; + gameRepository.update({ objectID: game.objectID }, { remoteId }); + }); + } + if (WindowManager.mainWindow) { WindowManager.mainWindow.webContents.send("on-game-close", game.id); }