diff --git a/src/main/data-source.ts b/src/main/data-source.ts index 7414a758..69e54667 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -1,11 +1,11 @@ import { DataSource } from "typeorm"; -import { DownloadQueue, Game, UserPreferences } from "@main/entity"; +import { UserPreferences } from "@main/entity"; import { databasePath } from "./constants"; export const dataSource = new DataSource({ type: "better-sqlite3", - entities: [Game, UserPreferences, DownloadQueue], + entities: [UserPreferences], synchronize: false, database: databasePath, }); diff --git a/src/main/entity/download-queue.entity.ts b/src/main/entity/download-queue.entity.ts deleted file mode 100644 index cf618947..00000000 --- a/src/main/entity/download-queue.entity.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - OneToOne, - JoinColumn, -} from "typeorm"; -import type { Game } from "./game.entity"; - -@Entity("download_queue") -export class DownloadQueue { - @PrimaryGeneratedColumn() - id: number; - - @OneToOne("Game", "downloadQueue") - @JoinColumn() - game: Game; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts deleted file mode 100644 index 0fcdcc77..00000000 --- a/src/main/entity/game.entity.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToOne, -} from "typeorm"; - -import type { GameShop, GameStatus } from "@types"; -import { Downloader } from "@shared"; -import type { DownloadQueue } from "./download-queue.entity"; - -@Entity("game") -export class Game { - @PrimaryGeneratedColumn() - id: number; - - @Column("text", { unique: true }) - objectID: string; - - @Column("text", { unique: true, nullable: true }) - remoteId: string | null; - - @Column("text") - title: string; - - @Column("text", { nullable: true }) - iconUrl: string | null; - - @Column("text", { nullable: true }) - folderName: string | null; - - @Column("text", { nullable: true }) - downloadPath: string | null; - - @Column("text", { nullable: true }) - executablePath: string | null; - - @Column("text", { nullable: true }) - launchOptions: string | null; - - @Column("text", { nullable: true }) - winePrefixPath: string | null; - - @Column("int", { default: 0 }) - playTimeInMilliseconds: number; - - @Column("text") - shop: GameShop; - - @Column("text", { nullable: true }) - status: GameStatus | null; - - @Column("int", { default: Downloader.Torrent }) - downloader: Downloader; - - /** - * Progress is a float between 0 and 1 - */ - @Column("float", { default: 0 }) - progress: number; - - @Column("int", { default: 0 }) - bytesDownloaded: number; - - @Column("datetime", { nullable: true }) - lastTimePlayed: Date | null; - - @Column("float", { default: 0 }) - fileSize: number; - - @Column("text", { nullable: true }) - uri: string | null; - - @OneToOne("DownloadQueue", "game") - downloadQueue: DownloadQueue; - - @Column("boolean", { default: false }) - isDeleted: boolean; - - @Column("boolean", { default: false }) - shouldSeed: boolean; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index f35f643d..ebf29400 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -1,3 +1 @@ -export * from "./game.entity"; export * from "./user-preferences.entity"; -export * from "./download-queue.entity"; diff --git a/src/main/events/auth/sign-out.ts b/src/main/events/auth/sign-out.ts index 50ea0c51..4de9c285 100644 --- a/src/main/events/auth/sign-out.ts +++ b/src/main/events/auth/sign-out.ts @@ -1,31 +1,25 @@ import { registerEvent } from "../register-event"; import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services"; -import { dataSource } from "@main/data-source"; -import { DownloadQueue, Game } from "@main/entity"; import { PythonRPC } from "@main/services/python-rpc"; -import { db, levelKeys } from "@main/level"; +import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; const signOut = async (_event: Electron.IpcMainInvokeEvent) => { - const databaseOperations = dataSource - .transaction(async (transactionalEntityManager) => { - await transactionalEntityManager.getRepository(DownloadQueue).delete({}); - - await transactionalEntityManager.getRepository(Game).delete({}); - - await db.batch([ - { - type: "del", - key: levelKeys.auth, - }, - { - type: "del", - key: levelKeys.user, - }, - ]); - }) + const databaseOperations = db + .batch([ + { + type: "del", + key: levelKeys.auth, + }, + { + type: "del", + key: levelKeys.user, + }, + ]) .then(() => { /* Removes all games being played */ gamesPlaytime.clear(); + + return Promise.all([gamesSublevel.clear(), downloadsSublevel.clear()]); }); /* Cancels any ongoing downloads */ diff --git a/src/main/events/helpers/generate-lutris-yaml.ts b/src/main/events/helpers/generate-lutris-yaml.ts deleted file mode 100644 index f47a2a68..00000000 --- a/src/main/events/helpers/generate-lutris-yaml.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Document as YMLDocument } from "yaml"; -import { Game } from "@main/entity"; -import path from "node:path"; - -export const generateYML = (game: Game) => { - const slugifiedGameTitle = game.title.replace(/\s/g, "-").toLocaleLowerCase(); - - const doc = new YMLDocument({ - name: game.title, - game_slug: slugifiedGameTitle, - slug: `${slugifiedGameTitle}-installer`, - version: "Installer", - runner: "wine", - script: { - game: { - prefix: "$GAMEDIR", - arch: "win64", - working_dir: "$GAMEDIR", - }, - installer: [ - { - task: { - name: "create_prefix", - arch: "win64", - prefix: "$GAMEDIR", - }, - }, - { - task: { - executable: path.join( - game.downloadPath!, - game.folderName!, - "setup.exe" - ), - name: "wineexec", - prefix: "$GAMEDIR", - }, - }, - ], - }, - }); - - return doc.toString(); -}; diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index dd9a6bd6..e27709e9 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -1,5 +1,3 @@ -import { gameRepository } from "@main/repository"; - import { registerEvent } from "../register-event"; import type { Game, GameShop } from "@types"; @@ -8,7 +6,7 @@ import { steamGamesWorker } from "@main/workers"; import { createGame } from "@main/services/library-sync"; import { steamUrlBuilder } from "@shared"; import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; const addGameToLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -16,41 +14,42 @@ const addGameToLibrary = async ( objectId: string, title: string ) => { - return gameRepository - .update( - { - objectID: objectId, - }, - { - shop, - status: null, - isDeleted: false, - } - ) - .then(async ({ affected }) => { - if (!affected) { - const steamGame = await steamGamesWorker.run(Number(objectId), { - name: "getById", - }); + const gameKey = levelKeys.game(shop, objectId); + const game = await gamesSublevel.get(gameKey); - const iconUrl = steamGame?.clientIcon - ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) - : null; + if (game) { + await downloadsSublevel.del(gameKey); - const game: Game = { - title, - iconUrl, - objectId, - shop, - }; - - await gamesSublevel.put(levelKeys.game(shop, objectId), game); - } - - updateLocalUnlockedAchivements(game!); - - createGame(game!).catch(() => {}); + await gamesSublevel.put(gameKey, { + ...game, + isDeleted: false, }); + } else { + const steamGame = await steamGamesWorker.run(Number(objectId), { + name: "getById", + }); + + const iconUrl = steamGame?.clientIcon + ? steamUrlBuilder.icon(objectId, steamGame.clientIcon) + : null; + + const game: Game = { + title, + iconUrl, + objectId, + shop, + remoteId: null, + isDeleted: false, + playTimeInMilliseconds: 0, + lastTimePlayed: null, + }; + + await gamesSublevel.put(levelKeys.game(shop, objectId), game); + + updateLocalUnlockedAchivements(game!); + + createGame(game!).catch(() => {}); + } }; registerEvent("addGameToLibrary", addGameToLibrary); diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts index 73dc2e04..bdaabc87 100644 --- a/src/main/events/library/get-library.ts +++ b/src/main/events/library/get-library.ts @@ -1,13 +1,23 @@ import { registerEvent } from "../register-event"; -import { gamesSublevel } from "@main/level"; +import { downloadsSublevel, gamesSublevel } from "@main/level"; const getLibrary = async () => { - // TODO: Add sorting return gamesSublevel - .values() + .iterator() .all() .then((results) => { - return results.filter((game) => game.isDeleted === false); + return Promise.all( + results + .filter(([_key, game]) => game.isDeleted === false) + .map(async ([key, game]) => { + const download = await downloadsSublevel.get(key); + + return { + ...game, + download, + }; + }) + ); }); }; diff --git a/src/main/events/library/open-game-installer.ts b/src/main/events/library/open-game-installer.ts index 949b4364..955d71fd 100644 --- a/src/main/events/library/open-game-installer.ts +++ b/src/main/events/library/open-game-installer.ts @@ -1,13 +1,11 @@ import { shell } from "electron"; import path from "node:path"; import fs from "node:fs"; -import { writeFile } from "node:fs/promises"; import { spawnSync, exec } from "node:child_process"; -import { generateYML } from "../helpers/generate-lutris-yaml"; import { getDownloadsPath } from "../helpers/get-downloads-path"; import { registerEvent } from "../register-event"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { downloadsSublevel, levelKeys } from "@main/level"; import { GameShop } from "@types"; const executeGameInstaller = (filePath: string) => { @@ -29,18 +27,18 @@ const openGameInstaller = async ( shop: GameShop, objectId: string ) => { - const game = await gamesSublevel.get(levelKeys.game(shop, objectId)); + const downloadKey = levelKeys.game(shop, objectId); + const download = await downloadsSublevel.get(downloadKey); - if (!game || game.isDeleted || !game.folderName) return true; + if (!download || !download.folderName) return true; const gamePath = path.join( - game.downloadPath ?? (await getDownloadsPath()), - game.folderName! + download.downloadPath ?? (await getDownloadsPath()), + download.folderName! ); if (!fs.existsSync(gamePath)) { - // TODO: LEVELDB Remove download? - // await gameRepository.update({ id: gameId }, { status: null }); + await downloadsSublevel.del(downloadKey); return true; } @@ -70,13 +68,6 @@ const openGameInstaller = async ( ); } - if (spawnSync("which", ["lutris"]).status === 0) { - const ymlPath = path.join(gamePath, "setup.yml"); - await writeFile(ymlPath, generateYML(game)); - exec(`lutris --install "${ymlPath}"`); - return true; - } - shell.openPath(gamePath); return true; }; diff --git a/src/main/events/library/open-game.ts b/src/main/events/library/open-game.ts index cf73c810..f60cd200 100644 --- a/src/main/events/library/open-game.ts +++ b/src/main/events/library/open-game.ts @@ -1,22 +1,31 @@ -import { gameRepository } from "@main/repository"; - import { registerEvent } from "../register-event"; import { shell } from "electron"; import { parseExecutablePath } from "../helpers/parse-executable-path"; +import { levelKeys } from "@main/level"; +import { gamesSublevel } from "@main/level"; +import { GameShop } from "@types"; const openGame = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number, + shop: GameShop, + objectId: string, executablePath: string, launchOptions: string | null ) => { // TODO: revisit this for launchOptions const parsedPath = parseExecutablePath(executablePath); - await gameRepository.update( - { id: gameId }, - { executablePath: parsedPath, launchOptions } - ); + const gameKey = levelKeys.game(shop, objectId); + + const game = await gamesSublevel.get(gameKey); + + if (!game) return; + + await gamesSublevel.put(gameKey, { + ...game, + executablePath: parsedPath, + launchOptions, + }); shell.openPath(parsedPath); }; diff --git a/src/main/events/torrenting/cancel-game-download.ts b/src/main/events/torrenting/cancel-game-download.ts index fbdf2761..20ba0820 100644 --- a/src/main/events/torrenting/cancel-game-download.ts +++ b/src/main/events/torrenting/cancel-game-download.ts @@ -1,31 +1,17 @@ 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 cancelGameDownload = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number + shop: GameShop, + objectId: string ) => { - await dataSource.transaction(async (transactionalEntityManager) => { - await DownloadManager.cancelDownload(gameId); + await DownloadManager.cancelDownload(shop, objectId); - await transactionalEntityManager.getRepository(DownloadQueue).delete({ - game: { id: gameId }, - }); - - await transactionalEntityManager.getRepository(Game).update( - { - id: gameId, - }, - { - status: "removed", - bytesDownloaded: 0, - progress: 0, - } - ); - }); + await downloadsSublevel.del(levelKeys.game(shop, objectId)); }; registerEvent("cancelGameDownload", cancelGameDownload); diff --git a/src/main/events/torrenting/pause-game-seed.ts b/src/main/events/torrenting/pause-game-seed.ts index df2af756..62dfca96 100644 --- a/src/main/events/torrenting/pause-game-seed.ts +++ b/src/main/events/torrenting/pause-game-seed.ts @@ -1,17 +1,25 @@ +import { downloadsSublevel } from "@main/level"; +import { levelKeys } from "@main/level"; import { registerEvent } from "../register-event"; import { DownloadManager } from "@main/services"; -import { gameRepository } from "@main/repository"; +import type { GameShop } from "@types"; const pauseGameSeed = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number + shop: GameShop, + objectId: string ) => { - await gameRepository.update(gameId, { - status: "complete", + const downloadKey = levelKeys.game(shop, objectId); + const download = await downloadsSublevel.get(downloadKey); + + if (!download) return; + + await downloadsSublevel.put(downloadKey, { + ...download, shouldSeed: false, }); - await DownloadManager.pauseSeeding(gameId); + await DownloadManager.pauseSeeding(download); }; registerEvent("pauseGameSeed", pauseGameSeed); diff --git a/src/main/events/torrenting/resume-game-download.ts b/src/main/events/torrenting/resume-game-download.ts index c8c75545..2327e929 100644 --- a/src/main/events/torrenting/resume-game-download.ts +++ b/src/main/events/torrenting/resume-game-download.ts @@ -1,11 +1,9 @@ import { Not } from "typeorm"; import { registerEvent } from "../register-event"; -import { gameRepository } from "../../repository"; import { DownloadManager } from "@main/services"; import { dataSource } from "@main/data-source"; -import { DownloadQueue, Game } from "@main/entity"; const resumeGameDownload = async ( _event: Electron.IpcMainInvokeEvent, diff --git a/src/main/events/torrenting/resume-game-seed.ts b/src/main/events/torrenting/resume-game-seed.ts index 9f79e53a..62493717 100644 --- a/src/main/events/torrenting/resume-game-seed.ts +++ b/src/main/events/torrenting/resume-game-seed.ts @@ -1,29 +1,24 @@ +import { downloadsSublevel } from "@main/level"; +import { levelKeys } from "@main/level"; import { registerEvent } from "../register-event"; -import { gameRepository } from "../../repository"; import { DownloadManager } from "@main/services"; -import { Downloader } from "@shared"; +import type { GameShop } from "@types"; const resumeGameSeed = async ( _event: Electron.IpcMainInvokeEvent, - gameId: number + shop: GameShop, + objectId: string ) => { - const game = await gameRepository.findOne({ - where: { - id: gameId, - isDeleted: false, - downloader: Downloader.Torrent, - progress: 1, - }, - }); + const download = await downloadsSublevel.get(levelKeys.game(shop, objectId)); - if (!game) return; + if (!download) return; - await gameRepository.update(gameId, { - status: "seeding", + await downloadsSublevel.put(levelKeys.game(shop, objectId), { + ...download, shouldSeed: true, }); - await DownloadManager.resumeSeeding(game); + await DownloadManager.resumeSeeding(download); }; registerEvent("resumeGameSeed", resumeGameSeed); diff --git a/src/main/index.ts b/src/main/index.ts index ca49a9fb..a60c4569 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,14 +3,11 @@ import updater from "electron-updater"; import i18n from "i18next"; import path from "node:path"; import url from "node:url"; -import fs from "node:fs"; 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 { knexClient, migrationConfig } from "./knex-client"; -import { databaseDirectory } from "./constants"; import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; @@ -50,21 +47,6 @@ if (process.defaultApp) { app.setAsDefaultProtocolClient(PROTOCOL); } -const runMigrations = async () => { - if (!fs.existsSync(databaseDirectory)) { - fs.mkdirSync(databaseDirectory, { recursive: true }); - } - - await knexClient.migrate.list(migrationConfig).then((result) => { - logger.log( - "Migrations to run:", - result[1].map((migration) => migration.name) - ); - }); - - await knexClient.migrate.latest(migrationConfig); -}; - // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. @@ -76,14 +58,6 @@ app.whenReady().then(async () => { return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString()); }); - await runMigrations() - .then(() => { - logger.log("Migrations executed successfully"); - }) - .catch((err) => { - logger.log("Migrations failed to run:", err); - }); - await dataSource.initialize(); await import("./main"); diff --git a/src/main/knex-client.ts b/src/main/knex-client.ts index 821efc80..57982332 100644 --- a/src/main/knex-client.ts +++ b/src/main/knex-client.ts @@ -1,53 +1,6 @@ -import knex, { Knex } from "knex"; +import knex from "knex"; import { databasePath } from "./constants"; -import { Hydra2_0_3 } from "./migrations/20240830143811_Hydra_2_0_3"; -import { RepackUris } from "./migrations/20240830143906_RepackUris"; -import { UpdateUserLanguage } from "./migrations/20240913213944_update_user_language"; -import { EnsureRepackUris } from "./migrations/20240915035339_ensure_repack_uris"; import { app } from "electron"; -import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns"; -import { CreateGameAchievement } from "./migrations/20240919030940_create_game_achievement"; -import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference"; -import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription"; -import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_background_image_url"; -import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game"; -import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column"; -import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column"; -import { AddShouldSeedColumn } from "./migrations/20241108200154_add_should_seed_colum"; -import { AddSeedAfterDownloadColumn } from "./migrations/20241108201806_add_seed_after_download"; -import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column "; -import { AddLaunchOptionsColumnToGame } from "./migrations/20241226044022_add_launch_options_column_to_game"; - -export type HydraMigration = Knex.Migration & { name: string }; - -class MigrationSource implements Knex.MigrationSource { - getMigrations(): Promise { - return Promise.resolve([ - Hydra2_0_3, - RepackUris, - UpdateUserLanguage, - EnsureRepackUris, - FixMissingColumns, - CreateGameAchievement, - AddAchievementNotificationPreference, - CreateUserSubscription, - AddBackgroundImageUrl, - AddWinePrefixToGame, - AddStartMinimizedColumn, - AddDisableNsfwAlertColumn, - AddShouldSeedColumn, - AddSeedAfterDownloadColumn, - AddHiddenAchievementDescriptionColumn, - AddLaunchOptionsColumnToGame, - ]); - } - getMigrationName(migration: HydraMigration): string { - return migration.name; - } - getMigration(migration: HydraMigration): Promise { - return Promise.resolve(migration); - } -} export const knexClient = knex({ debug: !app.isPackaged, @@ -56,7 +9,3 @@ export const knexClient = knex({ filename: databasePath, }, }); - -export const migrationConfig: Knex.MigratorConfig = { - migrationSource: new MigrationSource(), -}; diff --git a/src/main/level/sublevels/downloads.ts b/src/main/level/sublevels/downloads.ts new file mode 100644 index 00000000..23030670 --- /dev/null +++ b/src/main/level/sublevels/downloads.ts @@ -0,0 +1,11 @@ +import type { Download } from "@types"; + +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export const downloadsSublevel = db.sublevel( + levelKeys.downloads, + { + valueEncoding: "json", + } +); diff --git a/src/main/migrations/20240830143811_Hydra_2_0_3.ts b/src/main/migrations/20240830143811_Hydra_2_0_3.ts deleted file mode 100644 index 6013f714..00000000 --- a/src/main/migrations/20240830143811_Hydra_2_0_3.ts +++ /dev/null @@ -1,171 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const Hydra2_0_3: HydraMigration = { - name: "Hydra_2_0_3", - up: async (knex: Knex) => { - const timestamp = new Date().getTime(); - - await knex.schema.hasTable("migrations").then(async (exists) => { - if (exists) { - await knex.schema.dropTable("migrations"); - } - }); - - await knex.schema.hasTable("download_source").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("download_source", (table) => { - table.increments("id").primary(); - table - .text("url") - .unique({ indexName: "download_source_url_unique_" + timestamp }); - table.text("name").notNullable(); - table.text("etag"); - table.integer("downloadCount").notNullable().defaultTo(0); - table.text("status").notNullable().defaultTo(0); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - }); - } - }); - - await knex.schema.hasTable("repack").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("repack", (table) => { - table.increments("id").primary(); - table - .text("title") - .notNullable() - .unique({ indexName: "repack_title_unique_" + timestamp }); - table - .text("magnet") - .notNullable() - .unique({ indexName: "repack_magnet_unique_" + timestamp }); - table.integer("page"); - table.text("repacker").notNullable(); - table.text("fileSize").notNullable(); - table.datetime("uploadDate").notNullable(); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - table - .integer("downloadSourceId") - .references("download_source.id") - .onDelete("CASCADE"); - }); - } - }); - - await knex.schema.hasTable("game").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("game", (table) => { - table.increments("id").primary(); - table - .text("objectID") - .notNullable() - .unique({ indexName: "game_objectID_unique_" + timestamp }); - table - .text("remoteId") - .unique({ indexName: "game_remoteId_unique_" + timestamp }); - table.text("title").notNullable(); - table.text("iconUrl"); - table.text("folderName"); - table.text("downloadPath"); - table.text("executablePath"); - table.integer("playTimeInMilliseconds").notNullable().defaultTo(0); - table.text("shop").notNullable(); - table.text("status"); - table.integer("downloader").notNullable().defaultTo(1); - table.float("progress").notNullable().defaultTo(0); - table.integer("bytesDownloaded").notNullable().defaultTo(0); - table.datetime("lastTimePlayed"); - table.float("fileSize").notNullable().defaultTo(0); - table.text("uri"); - table.boolean("isDeleted").notNullable().defaultTo(0); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - table - .integer("repackId") - .references("repack.id") - .unique("repack_repackId_unique_" + timestamp); - }); - } - }); - - await knex.schema.hasTable("user_preferences").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("user_preferences", (table) => { - table.increments("id").primary(); - table.text("downloadsPath"); - table.text("language").notNullable().defaultTo("en"); - table.text("realDebridApiToken"); - table - .boolean("downloadNotificationsEnabled") - .notNullable() - .defaultTo(0); - table - .boolean("repackUpdatesNotificationsEnabled") - .notNullable() - .defaultTo(0); - table.boolean("preferQuitInsteadOfHiding").notNullable().defaultTo(0); - table.boolean("runAtStartup").notNullable().defaultTo(0); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - }); - } - }); - - await knex.schema.hasTable("game_shop_cache").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("game_shop_cache", (table) => { - table.text("objectID").primary().notNullable(); - table.text("shop").notNullable(); - table.text("serializedData"); - table.text("howLongToBeatSerializedData"); - table.text("language"); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - }); - } - }); - - await knex.schema.hasTable("download_queue").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("download_queue", (table) => { - table.increments("id").primary(); - table - .integer("gameId") - .references("game.id") - .unique("download_queue_gameId_unique_" + timestamp); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - }); - } - }); - - await knex.schema.hasTable("user_auth").then(async (exists) => { - if (!exists) { - await knex.schema.createTable("user_auth", (table) => { - table.increments("id").primary(); - table.text("userId").notNullable().defaultTo(""); - table.text("displayName").notNullable().defaultTo(""); - table.text("profileImageUrl"); - table.text("accessToken").notNullable().defaultTo(""); - table.text("refreshToken").notNullable().defaultTo(""); - table.integer("tokenExpirationTimestamp").notNullable().defaultTo(0); - table.datetime("createdAt").notNullable().defaultTo(knex.fn.now()); - table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now()); - }); - } - }); - }, - - down: async (knex: Knex) => { - await knex.schema.dropTableIfExists("game"); - await knex.schema.dropTableIfExists("repack"); - await knex.schema.dropTableIfExists("download_queue"); - await knex.schema.dropTableIfExists("user_auth"); - await knex.schema.dropTableIfExists("game_shop_cache"); - await knex.schema.dropTableIfExists("user_preferences"); - await knex.schema.dropTableIfExists("download_source"); - }, -}; diff --git a/src/main/migrations/20240830143906_RepackUris.ts b/src/main/migrations/20240830143906_RepackUris.ts deleted file mode 100644 index 18bb9a59..00000000 --- a/src/main/migrations/20240830143906_RepackUris.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const RepackUris: HydraMigration = { - name: "RepackUris", - up: async (knex: Knex) => { - await knex.schema.alterTable("repack", (table) => { - table.text("uris").notNullable().defaultTo("[]"); - }); - }, - - down: async (knex: Knex) => { - await knex.schema.alterTable("repack", (table) => { - table.integer("page"); - table.dropColumn("uris"); - }); - }, -}; diff --git a/src/main/migrations/20240913213944_update_user_language.ts b/src/main/migrations/20240913213944_update_user_language.ts deleted file mode 100644 index 3297eb0d..00000000 --- a/src/main/migrations/20240913213944_update_user_language.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const UpdateUserLanguage: HydraMigration = { - name: "UpdateUserLanguage", - up: async (knex: Knex) => { - await knex("user_preferences") - .update("language", "pt-BR") - .where("language", "pt"); - }, - - down: async (_knex: Knex) => {}, -}; diff --git a/src/main/migrations/20240915035339_ensure_repack_uris.ts b/src/main/migrations/20240915035339_ensure_repack_uris.ts deleted file mode 100644 index 64fbcd2e..00000000 --- a/src/main/migrations/20240915035339_ensure_repack_uris.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const EnsureRepackUris: HydraMigration = { - name: "EnsureRepackUris", - up: async (knex: Knex) => { - await knex.schema.hasColumn("repack", "uris").then(async (exists) => { - if (!exists) { - await knex.schema.table("repack", (table) => { - table.text("uris").notNullable().defaultTo("[]"); - }); - } - }); - }, - - down: async (_knex: Knex) => {}, -}; diff --git a/src/main/migrations/20240918001920_FixMissingColumns.ts b/src/main/migrations/20240918001920_FixMissingColumns.ts deleted file mode 100644 index d23662ed..00000000 --- a/src/main/migrations/20240918001920_FixMissingColumns.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const FixMissingColumns: HydraMigration = { - name: "FixMissingColumns", - up: async (knex: Knex) => { - const timestamp = new Date().getTime(); - await knex.schema - .hasColumn("repack", "downloadSourceId") - .then(async (exists) => { - if (!exists) { - await knex.schema.table("repack", (table) => { - table - .integer("downloadSourceId") - .references("download_source.id") - .onDelete("CASCADE"); - }); - } - }); - - await knex.schema.hasColumn("game", "remoteId").then(async (exists) => { - if (!exists) { - await knex.schema.table("game", (table) => { - table - .text("remoteId") - .unique({ indexName: "game_remoteId_unique_" + timestamp }); - }); - } - }); - - await knex.schema.hasColumn("game", "uri").then(async (exists) => { - if (!exists) { - await knex.schema.table("game", (table) => { - table.text("uri"); - }); - } - }); - }, - - down: async (_knex: Knex) => {}, -}; diff --git a/src/main/migrations/20240919030940_create_game_achievement.ts b/src/main/migrations/20240919030940_create_game_achievement.ts deleted file mode 100644 index 791eeb29..00000000 --- a/src/main/migrations/20240919030940_create_game_achievement.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const CreateGameAchievement: HydraMigration = { - name: "CreateGameAchievement", - up: (knex: Knex) => { - return knex.schema.createTable("game_achievement", (table) => { - table.increments("id").primary(); - table.text("objectId").notNullable(); - table.text("shop").notNullable(); - table.text("achievements"); - table.text("unlockedAchievements"); - table.unique(["objectId", "shop"]); - }); - }, - - down: (knex: Knex) => { - return knex.schema.dropTable("game_achievement"); - }, -}; diff --git a/src/main/migrations/20241013012900_add_achievement_notification_preference.ts b/src/main/migrations/20241013012900_add_achievement_notification_preference.ts deleted file mode 100644 index a4f48265..00000000 --- a/src/main/migrations/20241013012900_add_achievement_notification_preference.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddAchievementNotificationPreference: HydraMigration = { - name: "AddAchievementNotificationPreference", - up: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.boolean("achievementNotificationsEnabled").defaultTo(true); - }); - }, - - down: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.dropColumn("achievementNotificationsEnabled"); - }); - }, -}; diff --git a/src/main/migrations/20241015235142_create_user_subscription.ts b/src/main/migrations/20241015235142_create_user_subscription.ts deleted file mode 100644 index 5f9ecab1..00000000 --- a/src/main/migrations/20241015235142_create_user_subscription.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const CreateUserSubscription: HydraMigration = { - name: "CreateUserSubscription", - up: async (knex: Knex) => { - return knex.schema.createTable("user_subscription", (table) => { - table.increments("id").primary(); - table.string("subscriptionId").defaultTo(""); - table - .text("userId") - .notNullable() - .references("user_auth.id") - .onDelete("CASCADE"); - table.string("status").defaultTo(""); - table.string("planId").defaultTo(""); - table.string("planName").defaultTo(""); - table.dateTime("expiresAt").nullable(); - table.dateTime("createdAt").defaultTo(knex.fn.now()); - table.dateTime("updatedAt").defaultTo(knex.fn.now()); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.dropTable("user_subscription"); - }, -}; diff --git a/src/main/migrations/20241016100249_add_background_image_url.ts b/src/main/migrations/20241016100249_add_background_image_url.ts deleted file mode 100644 index b377c650..00000000 --- a/src/main/migrations/20241016100249_add_background_image_url.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddBackgroundImageUrl: HydraMigration = { - name: "AddBackgroundImageUrl", - up: (knex: Knex) => { - return knex.schema.alterTable("user_auth", (table) => { - return table.text("backgroundImageUrl").nullable(); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("user_auth", (table) => { - return table.dropColumn("backgroundImageUrl"); - }); - }, -}; diff --git a/src/main/migrations/20241019081648_add_wine_prefix_to_game.ts b/src/main/migrations/20241019081648_add_wine_prefix_to_game.ts deleted file mode 100644 index 517f6fb5..00000000 --- a/src/main/migrations/20241019081648_add_wine_prefix_to_game.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddWinePrefixToGame: HydraMigration = { - name: "AddWinePrefixToGame", - up: (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.text("winePrefixPath").nullable(); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.dropColumn("winePrefixPath"); - }); - }, -}; diff --git a/src/main/migrations/20241030171454_add_start_minimized_column.ts b/src/main/migrations/20241030171454_add_start_minimized_column.ts deleted file mode 100644 index 69ede189..00000000 --- a/src/main/migrations/20241030171454_add_start_minimized_column.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddStartMinimizedColumn: HydraMigration = { - name: "AddStartMinimizedColumn", - up: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.boolean("startMinimized").notNullable().defaultTo(0); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.dropColumn("startMinimized"); - }); - }, -}; diff --git a/src/main/migrations/20241106053733_add_disable_nsfw_alert_column.ts b/src/main/migrations/20241106053733_add_disable_nsfw_alert_column.ts deleted file mode 100644 index a248dd2b..00000000 --- a/src/main/migrations/20241106053733_add_disable_nsfw_alert_column.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddDisableNsfwAlertColumn: HydraMigration = { - name: "AddDisableNsfwAlertColumn", - up: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.boolean("disableNsfwAlert").notNullable().defaultTo(0); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.dropColumn("disableNsfwAlert"); - }); - }, -}; diff --git a/src/main/migrations/20241108200154_add_should_seed_colum.ts b/src/main/migrations/20241108200154_add_should_seed_colum.ts deleted file mode 100644 index 7e90a3b1..00000000 --- a/src/main/migrations/20241108200154_add_should_seed_colum.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddShouldSeedColumn: HydraMigration = { - name: "AddShouldSeedColumn", - up: (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.boolean("shouldSeed").notNullable().defaultTo(true); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.dropColumn("shouldSeed"); - }); - }, -}; diff --git a/src/main/migrations/20241108201806_add_seed_after_download.ts b/src/main/migrations/20241108201806_add_seed_after_download.ts deleted file mode 100644 index 75b94577..00000000 --- a/src/main/migrations/20241108201806_add_seed_after_download.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddSeedAfterDownloadColumn: HydraMigration = { - name: "AddSeedAfterDownloadColumn", - up: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table - .boolean("seedAfterDownloadComplete") - .notNullable() - .defaultTo(true); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.dropColumn("seedAfterDownloadComplete"); - }); - }, -}; diff --git a/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts b/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts deleted file mode 100644 index 36771c43..00000000 --- a/src/main/migrations/20241216140633_add_hidden_achievement_description_column .ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddHiddenAchievementDescriptionColumn: HydraMigration = { - name: "AddHiddenAchievementDescriptionColumn", - up: (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table - .boolean("showHiddenAchievementsDescription") - .notNullable() - .defaultTo(0); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("user_preferences", (table) => { - return table.dropColumn("showHiddenAchievementsDescription"); - }); - }, -}; diff --git a/src/main/migrations/20241226044022_add_launch_options_column_to_game.ts b/src/main/migrations/20241226044022_add_launch_options_column_to_game.ts deleted file mode 100644 index 417eeb63..00000000 --- a/src/main/migrations/20241226044022_add_launch_options_column_to_game.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const AddLaunchOptionsColumnToGame: HydraMigration = { - name: "AddLaunchOptionsColumnToGame", - up: (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.string("launchOptions").nullable(); - }); - }, - - down: async (knex: Knex) => { - return knex.schema.alterTable("game", (table) => { - return table.dropColumn("launchOptions"); - }); - }, -}; diff --git a/src/main/migrations/migration.stub b/src/main/migrations/migration.stub deleted file mode 100644 index 299b3fc2..00000000 --- a/src/main/migrations/migration.stub +++ /dev/null @@ -1,11 +0,0 @@ -import type { HydraMigration } from "@main/knex-client"; -import type { Knex } from "knex"; - -export const MigrationName: HydraMigration = { - name: "MigrationName", - up: (knex: Knex) => { - return knex.schema.createTable("table_name", async (table) => {}); - }, - - down: async (knex: Knex) => {}, -}; diff --git a/src/main/repository.ts b/src/main/repository.ts index 5bbfaf9f..1a6975a2 100644 --- a/src/main/repository.ts +++ b/src/main/repository.ts @@ -1,9 +1,5 @@ import { dataSource } from "./data-source"; -import { DownloadQueue, Game, UserPreferences } from "@main/entity"; - -export const gameRepository = dataSource.getRepository(Game); +import { UserPreferences } from "@main/entity"; export const userPreferencesRepository = dataSource.getRepository(UserPreferences); - -export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 754847c9..d1111b0d 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -1,6 +1,4 @@ -import { gameRepository } from "@main/repository"; import { parseAchievementFile } from "./parse-achievement-file"; -import { Game } from "@main/entity"; import { mergeAchievements } from "./merge-achievements"; import fs, { readdirSync } from "node:fs"; import { @@ -9,10 +7,9 @@ import { findAllAchievementFiles, getAlternativeObjectIds, } from "./find-achivement-files"; -import type { AchievementFile, UnlockedAchievement } from "@types"; +import type { AchievementFile, Game, UnlockedAchievement } from "@types"; import { achievementsLogger } from "../logger"; import { Cracker } from "@shared"; -import { IsNull, Not } from "typeorm"; import { publishCombinedNewAchievementNotification } from "../notifications"; import { gamesSublevel } from "@main/level"; @@ -47,12 +44,12 @@ const watchAchievementsWindows = async () => { }; const watchAchievementsWithWine = async () => { - const games = await gameRepository.find({ - where: { - isDeleted: false, - winePrefixPath: Not(IsNull()), - }, - }); + const games = await gamesSublevel + .values() + .all() + .then((games) => + games.filter((game) => !game.isDeleted && game.winePrefixPath) + ); for (const game of games) { const gameAchievementFiles = findAchievementFiles(game); @@ -188,11 +185,10 @@ export class AchievementWatcherManager { }; private static preSearchAchievementsWindows = async () => { - const games = await gameRepository.find({ - where: { - isDeleted: false, - }, - }); + const games = await gamesSublevel + .values() + .all() + .then((games) => games.filter((game) => !game.isDeleted)); const gameAchievementFilesMap = findAllAchievementFiles(); @@ -200,7 +196,7 @@ export class AchievementWatcherManager { games.map((game) => { const gameAchievementFiles: AchievementFile[] = []; - for (const objectId of getAlternativeObjectIds(game.objectID)) { + for (const objectId of getAlternativeObjectIds(game.objectId)) { gameAchievementFiles.push( ...(gameAchievementFilesMap.get(objectId) || []) ); @@ -216,11 +212,10 @@ export class AchievementWatcherManager { }; private static preSearchAchievementsWithWine = async () => { - const games = await gameRepository.find({ - where: { - isDeleted: false, - }, - }); + const games = await gamesSublevel + .values() + .all() + .then((games) => games.filter((game) => !game.isDeleted)); return Promise.all( games.map((game) => { diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 0578065c..f9bd9a42 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -254,7 +254,7 @@ export const findAchievementFiles = (game: Game) => { for (const cracker of crackers) { for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) { - for (const objectId of getAlternativeObjectIds(game.objectID)) { + for (const objectId of getAlternativeObjectIds(game.objectId)) { const filePath = path.join( game.winePrefixPath ?? "", folderPath, diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index 0393477c..8832a475 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -4,8 +4,7 @@ import { } from "./find-achivement-files"; import { parseAchievementFile } from "./parse-achievement-file"; import { mergeAchievements } from "./merge-achievements"; -import type { UnlockedAchievement } from "@types"; -import { Game } from "@main/entity"; +import type { Game, UnlockedAchievement } from "@types"; export const updateLocalUnlockedAchivements = async (game: Game) => { const gameAchievementFiles = findAchievementFiles(game); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index f85b018e..9f726ad9 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -1,11 +1,6 @@ -import { Game } from "@main/entity"; import { Downloader } from "@shared"; import { WindowManager } from "../window-manager"; -import { - downloadQueueRepository, - gameRepository, - userPreferencesRepository, -} from "@main/repository"; +import { userPreferencesRepository } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; import type { Download, DownloadProgress } from "@types"; import { GofileApi, QiwiApi, DatanodesApi } from "../hosters"; @@ -23,7 +18,7 @@ import { logger } from "../logger"; import { downloadsSublevel, levelKeys } from "@main/level"; export class DownloadManager { - private static downloadingGameId: number | null = null; + private static downloadingGameId: string | null = null; public static async startRPC( download?: Download, @@ -34,13 +29,15 @@ export class DownloadManager { ? await this.getDownloadPayload(download).catch(() => undefined) : undefined, downloadsToSeed?.map((download) => ({ - game_id: game.id, - url: game.uri!, - save_path: game.downloadPath!, + game_id: `${download.shop}-${download.objectId}`, + url: download.uri!, + save_path: download.downloadPath!, })) ); - this.downloadingGameId = game?.id ?? null; + if (download) { + this.downloadingGameId = `${download.shop}-${download.objectId}`; + } } private static async getDownloadStatus() { diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index 8cacdcb7..bbd3efc5 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -1,9 +1,9 @@ export interface PauseDownloadPayload { - game_id: number; + game_id: string; } export interface CancelDownloadPayload { - game_id: number; + game_id: string; } export enum LibtorrentStatus { diff --git a/src/main/services/library-sync/merge-with-remote-games.ts b/src/main/services/library-sync/merge-with-remote-games.ts index d286ad6c..2b6eebb0 100644 --- a/src/main/services/library-sync/merge-with-remote-games.ts +++ b/src/main/services/library-sync/merge-with-remote-games.ts @@ -1,17 +1,15 @@ -import { gameRepository } from "@main/repository"; import { HydraApi } from "../hydra-api"; import { steamGamesWorker } from "@main/workers"; import { steamUrlBuilder } from "@shared"; +import { gamesSublevel, levelKeys } from "@main/level"; export const mergeWithRemoteGames = async () => { return HydraApi.get("/profile/games") .then(async (response) => { for (const game of response) { - const localGame = await gameRepository.findOne({ - where: { - objectID: game.objectId, - }, - }); + const localGame = await gamesSublevel.get( + levelKeys.game(game.shop, game.objectId) + ); if (localGame) { const updatedLastTimePlayed = @@ -26,17 +24,12 @@ export const mergeWithRemoteGames = async () => { ? game.playTimeInMilliseconds : localGame.playTimeInMilliseconds; - gameRepository.update( - { - objectID: game.objectId, - shop: "steam", - }, - { - remoteId: game.id, - lastTimePlayed: updatedLastTimePlayed, - playTimeInMilliseconds: updatedPlayTime, - } - ); + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + ...localGame, + remoteId: game.id, + lastTimePlayed: updatedLastTimePlayed, + playTimeInMilliseconds: updatedPlayTime, + }); } else { const steamGame = await steamGamesWorker.run(Number(game.objectId), { name: "getById", @@ -47,14 +40,15 @@ export const mergeWithRemoteGames = async () => { ? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon) : null; - gameRepository.insert({ - objectID: game.objectId, + gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { + objectId: game.objectId, title: steamGame?.name, remoteId: game.id, shop: game.shop, iconUrl, lastTimePlayed: game.lastTimePlayed, playTimeInMilliseconds: game.playTimeInMilliseconds, + isDeleted: false, }); } } diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index f3e2541b..23230589 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 { Game } from "@main/entity"; import { userPreferencesRepository } from "@main/repository"; import fs from "node:fs"; import axios from "axios"; @@ -11,6 +10,7 @@ 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"; async function downloadImage(url: string | null) { if (!url) return undefined; diff --git a/src/main/services/python-rpc.ts b/src/main/services/python-rpc.ts index 1384a1be..22e60461 100644 --- a/src/main/services/python-rpc.ts +++ b/src/main/services/python-rpc.ts @@ -10,7 +10,7 @@ import { Readable } from "node:stream"; import { app, dialog } from "electron"; interface GamePayload { - game_id: number; + game_id: string; url: string; save_path: string; } diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index a7cfcee2..e7a6db2b 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -13,10 +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 { gameRepository, userPreferencesRepository } from "@main/repository"; -import { IsNull, Not } from "typeorm"; +import { userPreferencesRepository } from "@main/repository"; import { HydraApi } from "./hydra-api"; import UserAgent from "user-agents"; +import { gamesSublevel } from "@main/level"; +import { slice, sortBy } from "lodash-es"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -207,17 +208,22 @@ export class WindowManager { } const updateSystemTray = async () => { - const games = await gameRepository.find({ - where: { - isDeleted: false, - executablePath: Not(IsNull()), - lastTimePlayed: Not(IsNull()), - }, - take: 5, - order: { - lastTimePlayed: "DESC", - }, - }); + const games = await gamesSublevel + .values() + .all() + .then((games) => + slice( + sortBy( + games.filter( + (game) => + !game.isDeleted && game.executablePath && game.lastTimePlayed + ), + "lastTimePlayed", + "DESC" + ), + 5 + ) + ); const recentlyPlayedGames: Array = games.map(({ title, executablePath }) => ({ diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 9242d9a6..3ee5f094 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -101,9 +101,9 @@ export function GameDetailsContextProvider({ const updateGame = useCallback(async () => { return window.electron - .getGameByObjectId(objectId!) + .getGameByObjectId(shop, objectId!) .then((result) => setGame(result)); - }, [setGame, objectId]); + }, [setGame, shop, objectId]); const isGameDownloading = lastPacket?.game.id === game?.id; diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index 4ea79b93..2a21dea2 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -9,7 +9,11 @@ import { setGameDeleting, removeGameFromDeleting, } from "@renderer/features"; -import type { DownloadProgress, StartGameDownloadPayload } from "@types"; +import type { + DownloadProgress, + GameShop, + StartGameDownloadPayload, +} from "@types"; import { useDate } from "./use-date"; import { formatBytes } from "@shared"; @@ -31,48 +35,48 @@ export function useDownload() { return game; }; - const pauseDownload = async (gameId: number) => { - await window.electron.pauseGameDownload(gameId); + const pauseDownload = async (shop: GameShop, objectId: string) => { + await window.electron.pauseGameDownload(shop, objectId); await updateLibrary(); dispatch(clearDownload()); }; - const resumeDownload = async (gameId: number) => { - await window.electron.resumeGameDownload(gameId); + const resumeDownload = async (shop: GameShop, objectId: string) => { + await window.electron.resumeGameDownload(shop, objectId); return updateLibrary(); }; - const removeGameInstaller = async (gameId: number) => { - dispatch(setGameDeleting(gameId)); + const removeGameInstaller = async (shop: GameShop, objectId: string) => { + dispatch(setGameDeleting(objectId)); try { - await window.electron.deleteGameFolder(gameId); + await window.electron.deleteGameFolder(shop, objectId); updateLibrary(); } finally { - dispatch(removeGameFromDeleting(gameId)); + dispatch(removeGameFromDeleting(objectId)); } }; - const cancelDownload = async (gameId: number) => { - await window.electron.cancelGameDownload(gameId); + const cancelDownload = async (shop: GameShop, objectId: string) => { + await window.electron.cancelGameDownload(shop, objectId); dispatch(clearDownload()); updateLibrary(); - removeGameInstaller(gameId); + removeGameInstaller(shop, objectId); }; - const removeGameFromLibrary = (gameId: number) => - window.electron.removeGameFromLibrary(gameId).then(() => { + const removeGameFromLibrary = (shop: GameShop, objectId: string) => + window.electron.removeGameFromLibrary(shop, objectId).then(() => { updateLibrary(); }); - const pauseSeeding = async (gameId: number) => { - await window.electron.pauseGameSeed(gameId); + const pauseSeeding = async (shop: GameShop, objectId: string) => { + await window.electron.pauseGameSeed(shop, objectId); await updateLibrary(); }; - const resumeSeeding = async (gameId: number) => { - await window.electron.resumeGameSeed(gameId); + const resumeSeeding = async (shop: GameShop, objectId: string) => { + await window.electron.resumeGameSeed(shop, objectId); await updateLibrary(); }; @@ -90,8 +94,8 @@ export function useDownload() { } }; - const isGameDeleting = (gameId: number) => { - return gamesWithDeletionInProgress.includes(gameId); + const isGameDeleting = (objectId: string) => { + return gamesWithDeletionInProgress.includes(objectId); }; return { diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 8820820b..8c71364d 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -33,7 +33,6 @@ export interface User { export interface Game { title: string; iconUrl: string | null; - status: GameStatus | null; playTimeInMilliseconds: number; lastTimePlayed: Date | null; objectId: string; @@ -58,6 +57,8 @@ export interface Download { lastTimePlayed: Date | null; fileSize: number; shouldSeed: boolean; + // TODO: Rename to DownloadStatus + status: GameStatus | null; timestamp: number; }