diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index 83cc4001..b8607b73 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -23,7 +23,7 @@ export class Game { objectID: string; @Column("text", { unique: true, nullable: true }) - remoteId: string; + remoteId: string | null; @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 7f283636..8187d41e 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -6,7 +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"; +import { createGame } from "@main/services/library-sync"; const addGameToLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -53,12 +53,7 @@ const addGameToLibrary = async ( const game = await gameRepository.findOne({ where: { objectID } }); - HydraApi.post("/games", { - objectId: objectID, - playTimeInMilliseconds: game?.playTimeInMilliseconds, - shop, - lastTimePlayed: game?.lastTimePlayed, - }).then((response) => { + createGame(game!).then((response) => { const { id: remoteId, playTimeInMilliseconds, diff --git a/src/main/main.ts b/src/main/main.ts index a866972a..f1a365fc 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,22 +1,16 @@ -import { - DownloadManager, - RepacksManager, - logger, - startMainLoop, -} from "./services"; +import { DownloadManager, RepacksManager, startMainLoop } from "./services"; import { downloadQueueRepository, - gameRepository, repackRepository, userPreferencesRepository, } from "./repository"; import { UserPreferences } from "./entity"; import { RealDebridClient } from "./services/real-debrid"; -import { fetchDownloadSourcesAndUpdate, getSteamAppAsset } from "./helpers"; +import { fetchDownloadSourcesAndUpdate } from "./helpers"; import { publishNewRepacksNotifications } from "./services/notifications"; import { MoreThan } from "typeorm"; import { HydraApi } from "./services/hydra-api"; -import { steamGamesWorker } from "./workers"; +import { getRemoteGames } from "./services/library-sync"; startMainLoop(); @@ -28,69 +22,11 @@ const loadState = async (userPreferences: UserPreferences | null) => { if (userPreferences?.realDebridApiToken) RealDebridClient.authorize(userPreferences?.realDebridApiToken); - 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); - }); + HydraApi.setupApi().then(async () => { + if (HydraApi.isLoggedIn()) { + getRemoteGames(); + } + }); const [nextQueueItem] = await downloadQueueRepository.find({ order: { diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index a4463723..7d2a1fdf 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -2,6 +2,7 @@ import { userAuthRepository } from "@main/repository"; import axios, { AxiosError, AxiosInstance } from "axios"; import { WindowManager } from "./window-manager"; import url from "url"; +import { getRemoteGames, uploadBatchGames } from "./library-sync"; export class HydraApi { private static instance: AxiosInstance; @@ -49,6 +50,9 @@ export class HydraApi { if (WindowManager.mainWindow) { WindowManager.mainWindow.webContents.send("on-signin"); + + await uploadBatchGames(); + await getRemoteGames(); } } diff --git a/src/main/services/library-sync/create-game.ts b/src/main/services/library-sync/create-game.ts new file mode 100644 index 00000000..823f56a6 --- /dev/null +++ b/src/main/services/library-sync/create-game.ts @@ -0,0 +1,11 @@ +import { Game } from "@main/entity"; +import { HydraApi } from "../hydra-api"; + +export const createGame = async (game: Game) => { + return HydraApi.post(`/games`, { + objectId: game.objectID, + playTimeInMilliseconds: Math.round(game.playTimeInMilliseconds), + shop: game.shop, + lastTimePlayed: game.lastTimePlayed, + }); +}; diff --git a/src/main/services/library-sync/get-remote-games.ts b/src/main/services/library-sync/get-remote-games.ts new file mode 100644 index 00000000..1a85ca2d --- /dev/null +++ b/src/main/services/library-sync/get-remote-games.ts @@ -0,0 +1,69 @@ +import { gameRepository } from "@main/repository"; +import { HydraApi } from "../hydra-api"; +import { steamGamesWorker } from "@main/workers"; +import { getSteamAppAsset } from "@main/helpers"; +import { logger } from "../logger"; +import { AxiosError } from "axios"; + +export const getRemoteGames = async () => { + try { + 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) { + if (err instanceof AxiosError) { + logger.error("getRemoteGames", err.response, err.message); + } else { + logger.error("getRemoteGames", err); + } + } +}; diff --git a/src/main/services/library-sync/index.ts b/src/main/services/library-sync/index.ts new file mode 100644 index 00000000..aa0d94de --- /dev/null +++ b/src/main/services/library-sync/index.ts @@ -0,0 +1,4 @@ +export * from "./get-remote-games"; +export * from "./upload-batch-games"; +export * from "./update-game-playtime"; +export * from "./create-game"; diff --git a/src/main/services/library-sync/update-game-playtime.ts b/src/main/services/library-sync/update-game-playtime.ts new file mode 100644 index 00000000..271dc6a5 --- /dev/null +++ b/src/main/services/library-sync/update-game-playtime.ts @@ -0,0 +1,13 @@ +import { Game } from "@main/entity"; +import { HydraApi } from "../hydra-api"; + +export const updateGamePlaytime = async ( + game: Game, + delta: number, + lastTimePlayed: Date +) => { + return HydraApi.put(`/games/${game.remoteId}`, { + playTimeDeltaInSeconds: delta, + lastTimePlayed, + }); +}; diff --git a/src/main/services/library-sync/upload-batch-games.ts b/src/main/services/library-sync/upload-batch-games.ts new file mode 100644 index 00000000..cfea9d39 --- /dev/null +++ b/src/main/services/library-sync/upload-batch-games.ts @@ -0,0 +1,35 @@ +import { gameRepository } from "@main/repository"; +import { chunk } from "lodash-es"; +import { IsNull } from "typeorm"; +import { HydraApi } from "../hydra-api"; +import { logger } from "../logger"; +import { AxiosError } from "axios"; + +export const uploadBatchGames = async () => { + try { + const games = await gameRepository.find({ + where: { remoteId: IsNull(), isDeleted: false }, + }); + + const gamesChunks = chunk(games, 200); + for (const chunk of gamesChunks) { + await HydraApi.post( + "/games/batch", + chunk.map((game) => { + return { + objectId: game.objectID, + playTimeInMilliseconds: Math.round(game.playTimeInMilliseconds), + shop: game.shop, + lastTimePlayed: game.lastTimePlayed, + }; + }) + ); + } + } catch (err) { + if (err instanceof AxiosError) { + logger.error("uploadBatchGames", err.response, err.message); + } else { + logger.error("uploadBatchGames", err); + } + } +}; diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 577a7770..de3af727 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -4,7 +4,7 @@ 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"; +import { createGame, updateGamePlaytime } from "./library-sync"; const gamesPlaytime = new Map< number, @@ -61,20 +61,14 @@ export const watchProcesses = async () => { }); } else { if (game.remoteId) { - HydraApi.put(`/games/${game.remoteId}`, { - playTimeDeltaInMilliseconds: 0, - lastTimePlayed: new Date(), - }); + updateGamePlaytime(game, 0, 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 }); - }); + createGame({ ...game, lastTimePlayed: new Date() }).then( + (response) => { + const { id: remoteId } = response.data; + gameRepository.update({ objectID: game.objectID }, { remoteId }); + } + ); } gamesPlaytime.set(game.id, { @@ -87,19 +81,13 @@ export const watchProcesses = async () => { gamesPlaytime.delete(game.id); if (game.remoteId) { - HydraApi.put(`/games/${game.remoteId}`, { - playTimeInMilliseconds: Math.round( - performance.now() - gamePlaytime.firstTick - ), - lastTimePlayed: game.lastTimePlayed, - }); + updateGamePlaytime( + game, + performance.now() - gamePlaytime.firstTick, + game.lastTimePlayed! + ); } else { - HydraApi.post("/games", { - objectId: game.objectID, - playTimeInMilliseconds: Math.round(game.playTimeInMilliseconds), - shop: game.shop, - lastTimePlayed: game.lastTimePlayed, - }).then((response) => { + createGame(game).then((response) => { const { id: remoteId } = response.data; gameRepository.update({ objectID: game.objectID }, { remoteId }); });