From f1e0ba4dd659626ec61a8615d998e9892299a513 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 21 Jan 2025 03:48:46 +0000 Subject: [PATCH] feat: migrating user preferences --- package.json | 1 - src/main/data-source.ts | 11 - src/main/entity/index.ts | 1 - src/main/entity/user-preferences.entity.ts | 55 ----- .../events/catalogue/get-trending-games.ts | 13 +- src/main/events/helpers/get-downloads-path.ts | 15 +- .../events/library/get-game-by-object-id.ts | 11 +- src/main/events/library/get-library.ts | 6 +- .../library/open-game-installer-path.ts | 10 +- src/main/events/library/open-game.ts | 2 +- .../publish-new-repacks-notification.ts | 13 +- .../events/torrenting/cancel-game-download.ts | 6 +- .../events/torrenting/pause-game-download.ts | 24 ++- src/main/events/torrenting/pause-game-seed.ts | 2 +- .../events/torrenting/resume-game-download.ts | 50 ++--- .../events/torrenting/start-game-download.ts | 145 +++++++------ .../user-preferences/get-user-preferences.ts | 7 +- .../update-user-preferences.ts | 20 +- .../get-compared-unlocked-achievements.ts | 15 +- .../events/user/get-unlocked-achievements.ts | 14 +- src/main/index.ts | 15 +- src/main/level/sublevels/keys.ts | 3 + src/main/main.ts | 129 +++++++++++- src/main/repository.ts | 5 - .../achievements/get-game-achievement-data.ts | 13 +- .../achievements/merge-achievements.ts | 23 ++- .../services/download/download-manager.ts | 194 +++++++++--------- src/main/services/download/types.ts | 2 +- src/main/services/notifications/index.ts | 14 +- src/main/services/window-manager.ts | 36 ++-- src/preload/index.ts | 6 +- src/renderer/src/app.tsx | 2 +- .../components/bottom-panel/bottom-panel.tsx | 27 +-- .../src/components/sidebar/sidebar.tsx | 33 +-- .../game-details/game-details.context.tsx | 15 +- .../game-details.context.types.ts | 4 +- src/renderer/src/declaration.d.ts | 8 +- src/renderer/src/features/download-slice.ts | 10 +- src/renderer/src/features/library-slice.ts | 4 +- .../src/pages/downloads/download-group.tsx | 98 +++++---- .../src/pages/downloads/downloads.tsx | 41 ++-- .../game-details/hero/hero-panel-actions.tsx | 13 +- .../game-details/hero/hero-panel-playtime.tsx | 11 +- .../pages/game-details/hero/hero-panel.tsx | 12 +- .../modals/game-options-modal.tsx | 64 +++--- .../game-details/modals/repacks-modal.tsx | 4 +- .../profile/profile-hero/profile-hero.tsx | 2 +- .../src/pages/settings/settings-general.tsx | 53 +++-- src/types/download.types.ts | 20 +- src/types/game.types.ts | 9 - src/types/index.ts | 25 +-- src/types/level.types.ts | 26 ++- yarn.lock | 185 +---------------- 53 files changed, 737 insertions(+), 790 deletions(-) delete mode 100644 src/main/data-source.ts delete mode 100644 src/main/entity/index.ts delete mode 100644 src/main/entity/user-preferences.entity.ts delete mode 100644 src/main/repository.ts diff --git a/package.json b/package.json index 4630ad41..d019669f 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "sound-play": "^1.1.0", "sudo-prompt": "^9.2.1", "tar": "^7.4.3", - "typeorm": "^0.3.20", "user-agents": "^1.1.387", "yaml": "^2.6.1", "yup": "^1.5.0", diff --git a/src/main/data-source.ts b/src/main/data-source.ts deleted file mode 100644 index 69e54667..00000000 --- a/src/main/data-source.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DataSource } from "typeorm"; -import { UserPreferences } from "@main/entity"; - -import { databasePath } from "./constants"; - -export const dataSource = new DataSource({ - type: "better-sqlite3", - entities: [UserPreferences], - synchronize: false, - database: databasePath, -}); diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts deleted file mode 100644 index ebf29400..00000000 --- a/src/main/entity/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./user-preferences.entity"; diff --git a/src/main/entity/user-preferences.entity.ts b/src/main/entity/user-preferences.entity.ts deleted file mode 100644 index a850b42f..00000000 --- a/src/main/entity/user-preferences.entity.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, -} from "typeorm"; - -@Entity("user_preferences") -export class UserPreferences { - @PrimaryGeneratedColumn() - id: number; - - @Column("text", { nullable: true }) - downloadsPath: string | null; - - @Column("text", { default: "en" }) - language: string; - - @Column("text", { nullable: true }) - realDebridApiToken: string | null; - - @Column("boolean", { default: false }) - downloadNotificationsEnabled: boolean; - - @Column("boolean", { default: false }) - repackUpdatesNotificationsEnabled: boolean; - - @Column("boolean", { default: true }) - achievementNotificationsEnabled: boolean; - - @Column("boolean", { default: false }) - preferQuitInsteadOfHiding: boolean; - - @Column("boolean", { default: false }) - runAtStartup: boolean; - - @Column("boolean", { default: false }) - startMinimized: boolean; - - @Column("boolean", { default: false }) - disableNsfwAlert: boolean; - - @Column("boolean", { default: true }) - seedAfterDownloadComplete: boolean; - - @Column("boolean", { default: false }) - showHiddenAchievementsDescription: boolean; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/events/catalogue/get-trending-games.ts b/src/main/events/catalogue/get-trending-games.ts index acfebfd6..32072c10 100644 --- a/src/main/events/catalogue/get-trending-games.ts +++ b/src/main/events/catalogue/get-trending-games.ts @@ -1,14 +1,15 @@ +import { levelKeys } from "@main/level"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; -import { userPreferencesRepository } from "@main/repository"; import type { TrendingGame } from "@types"; +import { db } from "@main/level"; const getTrendingGames = async (_event: Electron.IpcMainInvokeEvent) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); - - const language = userPreferences?.language || "en"; + const language = await db + .get(levelKeys.language, { + valueEncoding: "utf-8", + }) + .then((language) => language || "en"); const trendingGames = await HydraApi.get( "/games/trending", diff --git a/src/main/events/helpers/get-downloads-path.ts b/src/main/events/helpers/get-downloads-path.ts index c78a0ede..0823109e 100644 --- a/src/main/events/helpers/get-downloads-path.ts +++ b/src/main/events/helpers/get-downloads-path.ts @@ -1,12 +1,15 @@ -import { userPreferencesRepository } from "@main/repository"; import { defaultDownloadsPath } from "@main/constants"; +import { levelKeys } from "@main/level"; +import type { UserPreferences } from "@types"; +import { db } from "@main/level"; export const getDownloadsPath = async () => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { - id: 1, - }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); if (userPreferences && userPreferences.downloadsPath) return userPreferences.downloadsPath; diff --git a/src/main/events/library/get-game-by-object-id.ts b/src/main/events/library/get-game-by-object-id.ts index 57e4a2bd..b835c5db 100644 --- a/src/main/events/library/get-game-by-object-id.ts +++ b/src/main/events/library/get-game-by-object-id.ts @@ -1,5 +1,5 @@ import { registerEvent } from "../register-event"; -import { levelKeys } from "@main/level"; +import { downloadsSublevel, levelKeys } from "@main/level"; import { gamesSublevel } from "@main/level"; import type { GameShop } from "@types"; @@ -9,9 +9,14 @@ const getGameByObjectId = async ( objectId: string ) => { const gameKey = levelKeys.game(shop, objectId); - const game = await gamesSublevel.get(gameKey); + const [game, download] = await Promise.all([ + gamesSublevel.get(gameKey), + downloadsSublevel.get(gameKey), + ]); - return game; + if (!game) return null; + + return { id: gameKey, ...game, download }; }; registerEvent("getGameByObjectId", getGameByObjectId); diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts index bdaabc87..86c0fd29 100644 --- a/src/main/events/library/get-library.ts +++ b/src/main/events/library/get-library.ts @@ -1,7 +1,8 @@ +import type { LibraryGame } from "@types"; import { registerEvent } from "../register-event"; import { downloadsSublevel, gamesSublevel } from "@main/level"; -const getLibrary = async () => { +const getLibrary = async (): Promise => { return gamesSublevel .iterator() .all() @@ -13,8 +14,9 @@ const getLibrary = async () => { const download = await downloadsSublevel.get(key); return { + id: key, ...game, - download, + download: download ?? null, }; }) ); diff --git a/src/main/events/library/open-game-installer-path.ts b/src/main/events/library/open-game-installer-path.ts index 971bd3ca..b61246fa 100644 --- a/src/main/events/library/open-game-installer-path.ts +++ b/src/main/events/library/open-game-installer-path.ts @@ -3,20 +3,20 @@ import path from "node:path"; import { getDownloadsPath } from "../helpers/get-downloads-path"; import { registerEvent } from "../register-event"; import { GameShop } from "@types"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { downloadsSublevel, levelKeys } from "@main/level"; const openGameInstallerPath = async ( _event: Electron.IpcMainInvokeEvent, shop: GameShop, objectId: string ) => { - const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); + const download = await downloadsSublevel.get(levelKeys.game(shop, objectId)); - if (!game || !game.folderName || !game.downloadPath) return true; + if (!download || !download.folderName || !download.downloadPath) return true; const gamePath = path.join( - game.downloadPath ?? (await getDownloadsPath()), - game.folderName! + download.downloadPath ?? (await getDownloadsPath()), + download.folderName! ); shell.showItemInFolder(gamePath); diff --git a/src/main/events/library/open-game.ts b/src/main/events/library/open-game.ts index f60cd200..2a74c5d4 100644 --- a/src/main/events/library/open-game.ts +++ b/src/main/events/library/open-game.ts @@ -10,7 +10,7 @@ const openGame = async ( shop: GameShop, objectId: string, executablePath: string, - launchOptions: string | null + launchOptions?: string | null ) => { // TODO: revisit this for launchOptions const parsedPath = parseExecutablePath(executablePath); diff --git a/src/main/events/notifications/publish-new-repacks-notification.ts b/src/main/events/notifications/publish-new-repacks-notification.ts index 5230c209..cb5c1f5f 100644 --- a/src/main/events/notifications/publish-new-repacks-notification.ts +++ b/src/main/events/notifications/publish-new-repacks-notification.ts @@ -1,7 +1,9 @@ import { Notification } from "electron"; import { registerEvent } from "../register-event"; -import { userPreferencesRepository } from "@main/repository"; import { t } from "i18next"; +import { db } from "@main/level"; +import { levelKeys } from "@main/level"; +import type { UserPreferences } from "@types"; const publishNewRepacksNotification = async ( _event: Electron.IpcMainInvokeEvent, @@ -9,9 +11,12 @@ const publishNewRepacksNotification = async ( ) => { if (newRepacksCount < 1) return; - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); if (userPreferences?.repackUpdatesNotificationsEnabled) { new Notification({ diff --git a/src/main/events/torrenting/cancel-game-download.ts b/src/main/events/torrenting/cancel-game-download.ts index 20ba0820..5d80337f 100644 --- a/src/main/events/torrenting/cancel-game-download.ts +++ b/src/main/events/torrenting/cancel-game-download.ts @@ -9,9 +9,11 @@ const cancelGameDownload = async ( shop: GameShop, objectId: string ) => { - await DownloadManager.cancelDownload(shop, objectId); + const downloadKey = levelKeys.game(shop, objectId); - await downloadsSublevel.del(levelKeys.game(shop, objectId)); + await DownloadManager.cancelDownload(downloadKey); + + await downloadsSublevel.del(downloadKey); }; registerEvent("cancelGameDownload", cancelGameDownload); diff --git a/src/main/events/torrenting/pause-game-download.ts b/src/main/events/torrenting/pause-game-download.ts index 03bb2781..27b21943 100644 --- a/src/main/events/torrenting/pause-game-download.ts +++ b/src/main/events/torrenting/pause-game-download.ts @@ -1,24 +1,26 @@ import { registerEvent } from "../register-event"; import { DownloadManager } from "@main/services"; -import { dataSource } from "@main/data-source"; -import { DownloadQueue, Game } from "@main/entity"; +import { GameShop } from "@types"; +import { downloadsSublevel, levelKeys } from "@main/level"; const pauseGameDownload = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number + shop: GameShop, + objectId: string ) => { - await dataSource.transaction(async (transactionalEntityManager) => { + const gameKey = levelKeys.game(shop, objectId); + + const download = await downloadsSublevel.get(gameKey); + + if (download) { await DownloadManager.pauseDownload(); - await transactionalEntityManager.getRepository(DownloadQueue).delete({ - game: { id: gameId }, + await downloadsSublevel.put(gameKey, { + ...download, + status: "paused", }); - - await transactionalEntityManager - .getRepository(Game) - .update({ id: gameId }, { status: "paused" }); - }); + } }; registerEvent("pauseGameDownload", pauseGameDownload); diff --git a/src/main/events/torrenting/pause-game-seed.ts b/src/main/events/torrenting/pause-game-seed.ts index 62dfca96..49aefc66 100644 --- a/src/main/events/torrenting/pause-game-seed.ts +++ b/src/main/events/torrenting/pause-game-seed.ts @@ -19,7 +19,7 @@ const pauseGameSeed = async ( shouldSeed: false, }); - await DownloadManager.pauseSeeding(download); + await DownloadManager.pauseSeeding(downloadKey); }; registerEvent("pauseGameSeed", pauseGameSeed); diff --git a/src/main/events/torrenting/resume-game-download.ts b/src/main/events/torrenting/resume-game-download.ts index 2327e929..c27aea84 100644 --- a/src/main/events/torrenting/resume-game-download.ts +++ b/src/main/events/torrenting/resume-game-download.ts @@ -1,44 +1,36 @@ -import { Not } from "typeorm"; - import { registerEvent } from "../register-event"; import { DownloadManager } from "@main/services"; -import { dataSource } from "@main/data-source"; +import { downloadsSublevel, levelKeys } from "@main/level"; +import { GameShop } from "@types"; const resumeGameDownload = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number + shop: GameShop, + objectId: string ) => { - const game = await gameRepository.findOne({ - where: { - id: gameId, - isDeleted: false, - }, - }); + const gameKey = levelKeys.game(shop, objectId); - if (!game) return; + const download = await downloadsSublevel.get(gameKey); - if (game.status === "paused") { - await dataSource.transaction(async (transactionalEntityManager) => { - await DownloadManager.pauseDownload(); + if (download?.status === "paused") { + await DownloadManager.pauseDownload(); - await transactionalEntityManager - .getRepository(Game) - .update({ status: "active", progress: Not(1) }, { status: "paused" }); + for await (const [key, value] of downloadsSublevel.iterator()) { + if (value.status === "active" && value.progress !== 1) { + await downloadsSublevel.put(key, { + ...value, + status: "paused", + }); + } + } - await DownloadManager.resumeDownload(game); + await DownloadManager.resumeDownload(download); - await transactionalEntityManager - .getRepository(DownloadQueue) - .delete({ game: { id: gameId } }); - - await transactionalEntityManager - .getRepository(DownloadQueue) - .insert({ game: { id: gameId } }); - - await transactionalEntityManager - .getRepository(Game) - .update({ id: gameId }, { status: "active" }); + await downloadsSublevel.put(gameKey, { + ...download, + status: "active", + timestamp: Date.now(), }); } }; diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index de10b07d..6d818d55 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -1,13 +1,11 @@ import { registerEvent } from "../register-event"; -import type { StartGameDownloadPayload } from "@types"; +import type { Download, StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi } from "@main/services"; -import { Not } from "typeorm"; import { steamGamesWorker } from "@main/workers"; import { createGame } from "@main/services/library-sync"; import { steamUrlBuilder } from "@shared"; -import { dataSource } from "@main/data-source"; -import { DownloadQueue, Game } from "@main/entity"; +import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, @@ -15,85 +13,84 @@ const startGameDownload = async ( ) => { const { objectId, title, shop, downloadPath, downloader, uri } = payload; - return dataSource.transaction(async (transactionalEntityManager) => { - const gameRepository = transactionalEntityManager.getRepository(Game); - const downloadQueueRepository = - transactionalEntityManager.getRepository(DownloadQueue); + const gameKey = levelKeys.game(shop, objectId); - const game = await gameRepository.findOne({ - where: { - objectID: objectId, - shop, - }, - }); + await DownloadManager.pauseDownload(); - await DownloadManager.pauseDownload(); - - await gameRepository.update( - { status: "active", progress: Not(1) }, - { status: "paused" } - ); - - if (game) { - await gameRepository.update( - { - id: game.id, - }, - { - status: "active", - progress: 0, - bytesDownloaded: 0, - downloadPath, - downloader, - uri, - isDeleted: false, - } - ); - } else { - const steamGame = await steamGamesWorker.run(Number(objectId), { - name: "getById", - }); - - const iconUrl = steamGame?.clientIcon - ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) - : null; - - await gameRepository.insert({ - title, - iconUrl, - objectID: objectId, - downloader, - shop, - status: "active", - downloadPath, - uri, + for await (const [key, value] of downloadsSublevel.iterator()) { + if (value.status === "active" && value.progress !== 1) { + await downloadsSublevel.put(key, { + ...value, + status: "paused", }); } + } - const updatedGame = await gameRepository.findOne({ - where: { - objectID: objectId, - }, + const game = await gamesSublevel.get(gameKey); + + /* Delete any previous download */ + await downloadsSublevel.del(gameKey); + + if (game?.isDeleted) { + await gamesSublevel.put(gameKey, { + ...game, + isDeleted: false, + }); + } else { + const steamGame = await steamGamesWorker.run(Number(objectId), { + name: "getById", }); - await DownloadManager.cancelDownload(updatedGame!.id); - await DownloadManager.startDownload(updatedGame!); + const iconUrl = steamGame?.clientIcon + ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) + : null; - await downloadQueueRepository.delete({ game: { id: updatedGame!.id } }); - await downloadQueueRepository.insert({ game: { id: updatedGame!.id } }); + await gamesSublevel.put(gameKey, { + title, + iconUrl, + objectId, + shop, + remoteId: null, + playTimeInMilliseconds: 0, + lastTimePlayed: null, + isDeleted: false, + }); + } - await Promise.all([ - createGame(updatedGame!).catch(() => {}), - HydraApi.post( - "/games/download", - { - objectId: updatedGame!.objectID, - shop: updatedGame!.shop, - }, - { needsAuth: false } - ).catch(() => {}), - ]); - }); + await DownloadManager.cancelDownload(gameKey); + + const download: Download = { + shop, + objectId, + status: "active", + progress: 0, + bytesDownloaded: 0, + downloadPath, + downloader, + uri, + folderName: null, + fileSize: null, + shouldSeed: false, + timestamp: Date.now(), + }; + + await downloadsSublevel.put(gameKey, download); + + await DownloadManager.startDownload(download); + + const updatedGame = await gamesSublevel.get(gameKey); + + await Promise.all([ + createGame(updatedGame!).catch(() => {}), + HydraApi.post( + "/games/download", + { + objectId, + shop, + }, + { needsAuth: false } + ).catch(() => {}), + ]); }; registerEvent("startGameDownload", startGameDownload); diff --git a/src/main/events/user-preferences/get-user-preferences.ts b/src/main/events/user-preferences/get-user-preferences.ts index 2a2df254..5a20b2b7 100644 --- a/src/main/events/user-preferences/get-user-preferences.ts +++ b/src/main/events/user-preferences/get-user-preferences.ts @@ -1,9 +1,10 @@ -import { userPreferencesRepository } from "@main/repository"; import { registerEvent } from "../register-event"; +import { db, levelKeys } from "@main/level"; +import type { UserPreferences } from "@types"; const getUserPreferences = async () => - userPreferencesRepository.findOne({ - where: { id: 1 }, + db.get(levelKeys.userPreferences, { + valueEncoding: "json", }); registerEvent("getUserPreferences", getUserPreferences); diff --git a/src/main/events/user-preferences/update-user-preferences.ts b/src/main/events/user-preferences/update-user-preferences.ts index f45af519..6001a85c 100644 --- a/src/main/events/user-preferences/update-user-preferences.ts +++ b/src/main/events/user-preferences/update-user-preferences.ts @@ -1,23 +1,35 @@ -import { userPreferencesRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import type { UserPreferences } from "@types"; import i18next from "i18next"; +import { db, levelKeys } from "@main/level"; const updateUserPreferences = async ( _event: Electron.IpcMainInvokeEvent, preferences: Partial ) => { + const userPreferences = await db.get( + levelKeys.userPreferences, + { valueEncoding: "json" } + ); + if (preferences.language) { + await db.put(levelKeys.language, preferences.language, { + valueEncoding: "utf-8", + }); + i18next.changeLanguage(preferences.language); } - return userPreferencesRepository.upsert( + await db.put( + levelKeys.userPreferences, { - id: 1, + ...userPreferences, ...preferences, }, - ["id"] + { + valueEncoding: "json", + } ); }; diff --git a/src/main/events/user/get-compared-unlocked-achievements.ts b/src/main/events/user/get-compared-unlocked-achievements.ts index 0b665212..b2358199 100644 --- a/src/main/events/user/get-compared-unlocked-achievements.ts +++ b/src/main/events/user/get-compared-unlocked-achievements.ts @@ -1,7 +1,9 @@ -import type { ComparedAchievements, GameShop } from "@types"; +import type { ComparedAchievements, GameShop, UserPreferences } from "@types"; import { registerEvent } from "../register-event"; -import { userPreferencesRepository } from "@main/repository"; + import { HydraApi } from "@main/services"; +import { db } from "@main/level"; +import { levelKeys } from "@main/level"; const getComparedUnlockedAchievements = async ( _event: Electron.IpcMainInvokeEvent, @@ -9,9 +11,12 @@ const getComparedUnlockedAchievements = async ( shop: GameShop, userId: string ) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); const showHiddenAchievementsDescription = userPreferences?.showHiddenAchievementsDescription || false; diff --git a/src/main/events/user/get-unlocked-achievements.ts b/src/main/events/user/get-unlocked-achievements.ts index 78820a94..9cb44423 100644 --- a/src/main/events/user/get-unlocked-achievements.ts +++ b/src/main/events/user/get-unlocked-achievements.ts @@ -1,8 +1,7 @@ -import type { GameShop, UserAchievement } from "@types"; +import type { GameShop, UserAchievement, UserPreferences } from "@types"; import { registerEvent } from "../register-event"; -import { userPreferencesRepository } from "@main/repository"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; -import { gameAchievementsSublevel, levelKeys } from "@main/level"; +import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; export const getUnlockedAchievements = async ( objectId: string, @@ -13,9 +12,12 @@ export const getUnlockedAchievements = async ( levelKeys.game(shop, objectId) ); - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); const showHiddenAchievementsDescription = userPreferences?.showHiddenAchievementsDescription || false; diff --git a/src/main/index.ts b/src/main/index.ts index a60c4569..0f7c0297 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,11 +5,10 @@ import path from "node:path"; import url from "node:url"; import { electronApp, optimizer } from "@electron-toolkit/utils"; import { logger, WindowManager } from "@main/services"; -import { dataSource } from "@main/data-source"; import resources from "@locales"; -import { userPreferencesRepository } from "@main/repository"; import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; +import { db, levelKeys } from "./level"; const { autoUpdater } = updater; @@ -58,23 +57,19 @@ app.whenReady().then(async () => { return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString()); }); - await dataSource.initialize(); - await import("./main"); - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, + const language = await db.get(levelKeys.language, { + valueEncoding: "utf-8", }); - if (userPreferences?.language) { - i18n.changeLanguage(userPreferences.language); - } + if (language) i18n.changeLanguage(language); if (!process.argv.includes("--hidden")) { WindowManager.createMainWindow(); } - WindowManager.createSystemTray(userPreferences?.language || "en"); + WindowManager.createSystemTray(language || "en"); }); app.on("browser-window-created", (_, window) => { diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index 5a787f13..53eae44b 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -10,4 +10,7 @@ export const levelKeys = { `${shop}:${objectId}:${language}`, gameAchievements: "gameAchievements", downloads: "downloads", + userPreferences: "userPreferences", + language: "language", + sqliteMigrationDone: "sqliteMigrationDone", }; diff --git a/src/main/main.ts b/src/main/main.ts index 5f5ec768..4e7bb5ab 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,6 +1,4 @@ -import { DownloadManager, Ludusavi, startMainLoop } from "./services"; -import { userPreferencesRepository } from "./repository"; -import { UserPreferences } from "./entity"; +import { DownloadManager, logger, Ludusavi, startMainLoop } from "./services"; import { RealDebridClient } from "./services/download/real-debrid"; import { HydraApi } from "./services/hydra-api"; import { uploadGamesBatch } from "./services/library-sync"; @@ -8,6 +6,10 @@ import { Aria2 } from "./services/aria2"; import { downloadsSublevel } from "./level/sublevels/downloads"; import { sortBy } from "lodash-es"; import { Downloader } from "@shared"; +import { gameAchievementsSublevel, gamesSublevel, levelKeys } from "./level"; +import { Auth, User, type UserPreferences } from "@types"; +import { db } from "./level"; +import { knexClient } from "./knex-client"; const loadState = async (userPreferences: UserPreferences | null) => { import("./events"); @@ -46,10 +48,121 @@ const loadState = async (userPreferences: UserPreferences | null) => { startMainLoop(); }; -userPreferencesRepository - .findOne({ - where: { id: 1 }, - }) - .then((userPreferences) => { +const migrateFromSqlite = async () => { + const sqliteMigrationDone = await db.get(levelKeys.sqliteMigrationDone); + + if (sqliteMigrationDone) { + return; + } + + const migrateGames = knexClient("game") + .select("*") + .then((games) => { + return gamesSublevel.batch( + games.map((game) => ({ + type: "put", + key: levelKeys.game(game.shop, game.objectID), + value: { + objectId: game.objectID, + shop: game.shop, + title: game.title, + iconUrl: game.iconUrl, + playTimeInMilliseconds: game.playTimeInMilliseconds, + lastTimePlayed: game.lastTimePlayed, + remoteId: game.remoteId, + isDeleted: game.isDeleted, + }, + })) + ); + }) + .then(() => { + logger.info("Games migrated successfully"); + }); + + const migrateUserPreferences = knexClient("user_preferences") + .select("*") + .then(async (userPreferences) => { + if (userPreferences.length > 0) { + await db.put(levelKeys.userPreferences, userPreferences[0]); + + if (userPreferences[0].language) { + await db.put(levelKeys.language, userPreferences[0].language); + } + } + }) + .then(() => { + logger.info("User preferences migrated successfully"); + }); + + const migrateAchievements = knexClient("game_achievement") + .select("*") + .then((achievements) => { + return gameAchievementsSublevel.batch( + achievements.map((achievement) => ({ + type: "put", + key: levelKeys.game(achievement.shop, achievement.objectId), + value: { + achievements: JSON.parse(achievement.achievements), + unlockedAchievements: JSON.parse(achievement.unlockedAchievements), + }, + })) + ); + }) + .then(() => { + logger.info("Achievements migrated successfully"); + }); + + const migrateUser = knexClient("user_auth") + .select("*") + .then(async (users) => { + if (users.length > 0) { + await db.put( + levelKeys.user, + { + id: users[0].userId, + displayName: users[0].displayName, + profileImageUrl: users[0].profileImageUrl, + backgroundImageUrl: users[0].backgroundImageUrl, + subscription: users[0].subscription, + }, + { + valueEncoding: "json", + } + ); + + await db.put( + levelKeys.auth, + { + accessToken: users[0].accessToken, + refreshToken: users[0].refreshToken, + tokenExpirationTimestamp: users[0].tokenExpirationTimestamp, + }, + { + valueEncoding: "json", + } + ); + } + }) + .then(() => { + logger.info("User data migrated successfully"); + }); + + return Promise.all([ + migrateGames, + migrateUserPreferences, + migrateAchievements, + migrateUser, + ]); +}; + +migrateFromSqlite().then(async () => { + await db.put(levelKeys.sqliteMigrationDone, true, { + valueEncoding: "json", + }); + + db.get(levelKeys.userPreferences, { + valueEncoding: "json", + }).then((userPreferences) => { loadState(userPreferences); }); +}); diff --git a/src/main/repository.ts b/src/main/repository.ts deleted file mode 100644 index 1a6975a2..00000000 --- a/src/main/repository.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { dataSource } from "./data-source"; -import { UserPreferences } from "@main/entity"; - -export const userPreferencesRepository = - dataSource.getRepository(UserPreferences); diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 2dc643c1..4c03c7e1 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -1,9 +1,8 @@ -import { userPreferencesRepository } from "@main/repository"; import { HydraApi } from "../hydra-api"; import type { GameShop, SteamAchievement } from "@types"; import { UserNotLoggedInError } from "@shared"; import { logger } from "../logger"; -import { gameAchievementsSublevel, levelKeys } from "@main/level"; +import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; export const getGameAchievementData = async ( objectId: string, @@ -17,14 +16,16 @@ export const getGameAchievementData = async ( if (cachedAchievements && useCachedData) return cachedAchievements.achievements; - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const language = await db + .get(levelKeys.language, { + valueEncoding: "utf-8", + }) + .then((language) => language || "en"); return HydraApi.get("/games/achievements", { shop, objectId, - language: userPreferences?.language || "en", + language, }) .then(async (achievements) => { await gameAchievementsSublevel.put(levelKeys.game(shop, objectId), { diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index ac2f69d1..a2541d5e 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -1,13 +1,16 @@ -import { userPreferencesRepository } from "@main/repository"; -import type { GameShop, UnlockedAchievement } from "@types"; +import type { + Game, + GameShop, + UnlockedAchievement, + UserPreferences, +} from "@types"; import { WindowManager } from "../window-manager"; import { HydraApi } from "../hydra-api"; import { getUnlockedAchievements } from "@main/events/user/get-unlocked-achievements"; -import { Game } from "@main/entity"; import { publishNewAchievementNotification } from "../notifications"; import { SubscriptionRequiredError } from "@shared"; import { achievementsLogger } from "../logger"; -import { gameAchievementsSublevel, levelKeys } from "@main/level"; +import { db, gameAchievementsSublevel, levelKeys } from "@main/level"; const saveAchievementsOnLocal = async ( objectId: string, @@ -46,8 +49,10 @@ export const mergeAchievements = async ( publishNotification: boolean ) => { const [localGameAchievement, userPreferences] = await Promise.all([ - gameAchievementsSublevel.get(levelKeys.game(game.shop, game.objectID)), - userPreferencesRepository.findOne({ where: { id: 1 } }), + gameAchievementsSublevel.get(levelKeys.game(game.shop, game.objectId)), + db.get(levelKeys.userPreferences, { + valueEncoding: "json", + }), ]); const achievementsData = localGameAchievement?.achievements ?? []; @@ -131,13 +136,13 @@ export const mergeAchievements = async ( if (err! instanceof SubscriptionRequiredError) { achievementsLogger.log( "Achievements not synchronized on API due to lack of subscription", - game.objectID, + game.objectId, game.title ); } return saveAchievementsOnLocal( - game.objectID, + game.objectId, game.shop, mergedLocalAchievements, publishNotification @@ -145,7 +150,7 @@ export const mergeAchievements = async ( }); } else { await saveAchievementsOnLocal( - game.objectID, + game.objectId, game.shop, mergedLocalAchievements, publishNotification diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 9f726ad9..45e4bab5 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -1,8 +1,7 @@ import { Downloader } from "@shared"; import { WindowManager } from "../window-manager"; -import { userPreferencesRepository } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; -import type { Download, DownloadProgress } from "@types"; +import type { Download, DownloadProgress, UserPreferences } from "@types"; import { GofileApi, QiwiApi, DatanodesApi } from "../hosters"; import { PythonRPC } from "../python-rpc"; import { @@ -11,11 +10,11 @@ import { PauseDownloadPayload, } from "./types"; import { calculateETA, getDirSize } from "./helpers"; -import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; import { RealDebridClient } from "./real-debrid"; import path from "path"; import { logger } from "../logger"; -import { downloadsSublevel, levelKeys } from "@main/level"; +import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; +import { sortBy } from "lodash-es"; export class DownloadManager { private static downloadingGameId: string | null = null; @@ -44,10 +43,8 @@ export class DownloadManager { const response = await PythonRPC.rpc.get( "/status" ); - if (response.data === null || !this.downloadingGameId) return null; - - const gameId = this.downloadingGameId; + const downloadId = this.downloadingGameId; try { const { @@ -63,24 +60,21 @@ export class DownloadManager { const isDownloadingMetadata = status === LibtorrentStatus.DownloadingMetadata; - const isCheckingFiles = status === LibtorrentStatus.CheckingFiles; + const download = await downloadsSublevel.get(downloadId); + if (!isDownloadingMetadata && !isCheckingFiles) { - const update: QueryDeepPartialEntity = { + if (!download) return null; + + await downloadsSublevel.put(downloadId, { + ...download, bytesDownloaded, fileSize, progress, + folderName, status: "active", - }; - - await gameRepository.update( - { id: gameId }, - { - ...update, - folderName, - } - ); + }); } return { @@ -91,7 +85,8 @@ export class DownloadManager { isDownloadingMetadata, isCheckingFiles, progress, - gameId, + gameId: downloadId, + download, } as DownloadProgress; } catch (err) { return null; @@ -103,15 +98,22 @@ export class DownloadManager { if (status) { const { gameId, progress } = status; - const game = await gameRepository.findOne({ - where: { id: gameId, isDeleted: false }, - }); - const userPreferences = await userPreferencesRepository.findOneBy({ - id: 1, - }); + const [download, game] = await Promise.all([ + downloadsSublevel.get(gameId), + gamesSublevel.get(gameId), + ]); - if (WindowManager.mainWindow && game) { + if (!download || !game) return; + + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); + + if (WindowManager.mainWindow && download) { WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); WindowManager.mainWindow.webContents.send( "on-download-progress", @@ -123,40 +125,42 @@ export class DownloadManager { ) ); } - if (progress === 1 && game) { + + if (progress === 1 && download) { publishDownloadCompleteNotification(game); if ( userPreferences?.seedAfterDownloadComplete && - game.downloader === Downloader.Torrent + download.downloader === Downloader.Torrent ) { - gameRepository.update( - { id: gameId }, - { status: "seeding", shouldSeed: true } - ); + downloadsSublevel.put(gameId, { + ...download, + status: "seeding", + shouldSeed: true, + }); } else { - gameRepository.update( - { id: gameId }, - { status: "complete", shouldSeed: false } - ); + downloadsSublevel.put(gameId, { + ...download, + status: "complete", + shouldSeed: false, + }); this.cancelDownload(gameId); } - await downloadsSublevel.del(levelKeys.game(game.shop, game.objectId)); + const downloads = await downloadsSublevel + .values() + .all() + .then((games) => { + return sortBy(games, "timestamp", "DESC"); + }); - const [nextQueueItem] = await downloadQueueRepository.find({ - order: { - id: "DESC", - }, - relations: { - game: true, - }, - }); - if (nextQueueItem) { - this.resumeDownload(nextQueueItem.game); + const [nextItemOnQueue] = downloads; + + if (nextItemOnQueue) { + this.resumeDownload(nextItemOnQueue); } else { - this.downloadingGameId = -1; + this.downloadingGameId = null; } } } @@ -172,20 +176,19 @@ export class DownloadManager { logger.log(seedStatus); seedStatus.forEach(async (status) => { - const game = await gameRepository.findOne({ - where: { id: status.gameId }, - }); + const download = await downloadsSublevel.get(status.gameId); - if (!game) return; + if (!download) return; const totalSize = await getDirSize( - path.join(game.downloadPath!, status.folderName) + path.join(download.downloadPath!, status.folderName) ); if (totalSize < status.fileSize) { - await this.cancelDownload(game.id); + await this.cancelDownload(status.gameId); - await gameRepository.update(game.id, { + await downloadsSublevel.put(status.gameId, { + ...download, status: "paused", shouldSeed: false, progress: totalSize / status.fileSize, @@ -207,114 +210,109 @@ export class DownloadManager { .catch(() => {}); WindowManager.mainWindow?.setProgressBar(-1); - this.downloadingGameId = null; } - static async resumeDownload(game: Game) { - return this.startDownload(game); + static async resumeDownload(download: Download) { + return this.startDownload(download); } - static async cancelDownload(gameId = this.downloadingGameId!) { + static async cancelDownload(downloadKey = this.downloadingGameId!) { await PythonRPC.rpc.post("/action", { action: "cancel", - game_id: gameId, + game_id: downloadKey, }); WindowManager.mainWindow?.setProgressBar(-1); - - if (gameId === this.downloadingGameId) { + if (downloadKey === this.downloadingGameId) { this.downloadingGameId = null; } } - static async resumeSeeding(game: Game) { + static async resumeSeeding(download: Download) { await PythonRPC.rpc.post("/action", { action: "resume_seeding", - game_id: game.id, - url: game.uri, - save_path: game.downloadPath, + game_id: levelKeys.game(download.shop, download.objectId), + url: download.uri, + save_path: download.downloadPath, }); } - static async pauseSeeding(gameId: number) { + static async pauseSeeding(downloadKey: string) { await PythonRPC.rpc.post("/action", { action: "pause_seeding", - game_id: gameId, + game_id: downloadKey, }); } - private static async getDownloadPayload(game: Game) { - switch (game.downloader) { - case Downloader.Gofile: { - const id = game.uri!.split("/").pop(); + private static async getDownloadPayload(download: Download) { + const downloadId = levelKeys.game(download.shop, download.objectId); + switch (download.downloader) { + case Downloader.Gofile: { + const id = download.uri!.split("/").pop(); const token = await GofileApi.authorize(); const downloadLink = await GofileApi.getDownloadLink(id!); return { action: "start", - game_id: game.id, + game_id: downloadId, url: downloadLink, - save_path: game.downloadPath!, + save_path: download.downloadPath!, header: `Cookie: accountToken=${token}`, }; } case Downloader.PixelDrain: { - const id = game.uri!.split("/").pop(); - + const id = download.uri!.split("/").pop(); return { action: "start", - game_id: game.id, + game_id: downloadId, url: `https://pixeldrain.com/api/file/${id}?download`, - save_path: game.downloadPath!, + save_path: download.downloadPath!, }; } case Downloader.Qiwi: { - const downloadUrl = await QiwiApi.getDownloadUrl(game.uri!); - + const downloadUrl = await QiwiApi.getDownloadUrl(download.uri!); return { action: "start", - game_id: game.id, + game_id: downloadId, url: downloadUrl, - save_path: game.downloadPath!, + save_path: download.downloadPath!, }; } case Downloader.Datanodes: { - const downloadUrl = await DatanodesApi.getDownloadUrl(game.uri!); - + const downloadUrl = await DatanodesApi.getDownloadUrl(download.uri!); return { action: "start", - game_id: game.id, + game_id: downloadId, url: downloadUrl, - save_path: game.downloadPath!, + save_path: download.downloadPath!, }; } case Downloader.Torrent: return { action: "start", - game_id: game.id, - url: game.uri!, - save_path: game.downloadPath!, + game_id: downloadId, + url: download.uri!, + save_path: download.downloadPath!, }; case Downloader.RealDebrid: { - const downloadUrl = await RealDebridClient.getDownloadUrl(game.uri!); - + const downloadUrl = await RealDebridClient.getDownloadUrl( + download.uri! + ); return { action: "start", - game_id: game.id, + game_id: downloadId, url: downloadUrl!, - save_path: game.downloadPath!, + save_path: download.downloadPath!, }; } } } - static async startDownload(game: Game) { - const payload = await this.getDownloadPayload(game); - + static async startDownload(download: Download) { + const payload = await this.getDownloadPayload(download); await PythonRPC.rpc.post("/action", payload); - - this.downloadingGameId = game.id; + this.downloadingGameId = levelKeys.game(download.shop, download.objectId); } } diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index bbd3efc5..0e868318 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -24,7 +24,7 @@ export interface LibtorrentPayload { fileSize: number; folderName: string; status: LibtorrentStatus; - gameId: number; + gameId: string; } export interface ProcessPayload { diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index 23230589..c0098ddd 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -1,7 +1,6 @@ import { Notification, app } from "electron"; import { t } from "i18next"; import trayIcon from "@resources/tray-icon.png?asset"; -import { userPreferencesRepository } from "@main/repository"; import fs from "node:fs"; import axios from "axios"; import path from "node:path"; @@ -10,7 +9,9 @@ import { achievementSoundPath } from "@main/constants"; import icon from "@resources/icon.png?asset"; import { NotificationOptions, toXmlString } from "./xml"; import { logger } from "../logger"; -import type { Game } from "@types"; +import type { Game, UserPreferences } from "@types"; +import { levelKeys } from "@main/level"; +import { db } from "@main/level"; async function downloadImage(url: string | null) { if (!url) return undefined; @@ -38,9 +39,12 @@ async function downloadImage(url: string | null) { } export const publishDownloadCompleteNotification = async (game: Game) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); if (userPreferences?.downloadNotificationsEnabled) { new Notification({ diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index e7a6db2b..df8b08a3 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -13,11 +13,11 @@ import i18next, { t } from "i18next"; import path from "node:path"; import icon from "@resources/icon.png?asset"; import trayIcon from "@resources/tray-icon.png?asset"; -import { userPreferencesRepository } from "@main/repository"; import { HydraApi } from "./hydra-api"; import UserAgent from "user-agents"; -import { gamesSublevel } from "@main/level"; +import { db, gamesSublevel, levelKeys } from "@main/level"; import { slice, sortBy } from "lodash-es"; +import type { UserPreferences } from "@types"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -131,9 +131,12 @@ export class WindowManager { }); this.mainWindow.on("close", async () => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); + const userPreferences = await db.get( + levelKeys.userPreferences, + { + valueEncoding: "json", + } + ); if (userPreferences?.preferQuitInsteadOfHiding) { app.quit(); @@ -211,19 +214,16 @@ export class WindowManager { const games = await gamesSublevel .values() .all() - .then((games) => - slice( - sortBy( - games.filter( - (game) => - !game.isDeleted && game.executablePath && game.lastTimePlayed - ), - "lastTimePlayed", - "DESC" - ), - 5 - ) - ); + .then((games) => { + const filteredGames = games.filter( + (game) => + !game.isDeleted && game.executablePath && game.lastTimePlayed + ); + + const sortedGames = sortBy(filteredGames, "lastTimePlayed", "DESC"); + + return slice(sortedGames, 5); + }); const recentlyPlayedGames: Array = games.map(({ title, executablePath }) => ({ diff --git a/src/preload/index.ts b/src/preload/index.ts index cda910b3..3c1e9e83 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -132,7 +132,7 @@ contextBridge.exposeInMainWorld("electron", { shop: GameShop, objectId: string, executablePath: string, - launchOptions: string | null + launchOptions?: string | null ) => ipcRenderer.invoke( "openGame", @@ -149,8 +149,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("removeGame", shop, objectId), deleteGameFolder: (shop: GameShop, objectId: string) => ipcRenderer.invoke("deleteGameFolder", shop, objectId), - getGameByObjectId: (objectId: string) => - ipcRenderer.invoke("getGameByObjectId", objectId), + getGameByObjectId: (shop: GameShop, objectId: string) => + ipcRenderer.invoke("getGameByObjectId", shop, objectId), resetGameAchievements: (shop: GameShop, objectId: string) => ipcRenderer.invoke("resetGameAchievements", shop, objectId), onGamesRunning: ( diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 5fefe90c..b03fb540 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -84,7 +84,7 @@ export function App() { useEffect(() => { const unsubscribe = window.electron.onDownloadProgress( (downloadProgress) => { - if (downloadProgress.game.progress === 1) { + if (downloadProgress.progress === 1) { clearDownload(); updateLibrary(); return; diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index bf8f71d5..9f9f32c1 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useDownload, useUserDetails } from "@renderer/hooks"; +import { useDownload, useLibrary, useUserDetails } from "@renderer/hooks"; import "./bottom-panel.scss"; @@ -15,9 +15,11 @@ export function BottomPanel() { const { userDetails } = useUserDetails(); + const { library } = useLibrary(); + const { lastPacket, progress, downloadSpeed, eta } = useDownload(); - const isGameDownloading = !!lastPacket?.game; + const isGameDownloading = !!lastPacket; const [version, setVersion] = useState(""); const [sessionHash, setSessionHash] = useState(""); @@ -32,27 +34,29 @@ export function BottomPanel() { const status = useMemo(() => { if (isGameDownloading) { + const game = library.find((game) => game.id === lastPacket?.gameId)!; + if (lastPacket?.isCheckingFiles) return t("checking_files", { - title: lastPacket?.game.title, + title: game.title, percentage: progress, }); if (lastPacket?.isDownloadingMetadata) return t("downloading_metadata", { - title: lastPacket?.game.title, + title: game.title, percentage: progress, }); if (!eta) { return t("calculating_eta", { - title: lastPacket?.game.title, + title: game.title, percentage: progress, }); } return t("downloading", { - title: lastPacket?.game.title, + title: game.title, percentage: progress, eta, speed: downloadSpeed, @@ -60,16 +64,7 @@ export function BottomPanel() { } return t("no_downloads_in_progress"); - }, [ - t, - isGameDownloading, - lastPacket?.game, - lastPacket?.isDownloadingMetadata, - lastPacket?.isCheckingFiles, - progress, - eta, - downloadSpeed, - ]); + }, [t, isGameDownloading, library, lastPacket, progress, eta, downloadSpeed]); return (