diff --git a/package.json b/package.json index c9f3885f..c00d3d1b 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "color.js": "^1.2.0", "create-desktop-shortcuts": "^1.11.0", "date-fns": "^3.6.0", + "dexie": "^4.0.8", "electron-log": "^5.1.4", "electron-updater": "^6.1.8", "fetch-cookie": "^3.0.1", diff --git a/src/main/events/catalogue/get-catalogue.ts b/src/main/events/catalogue/get-catalogue.ts index 8d6183a5..4fdb95bd 100644 --- a/src/main/events/catalogue/get-catalogue.ts +++ b/src/main/events/catalogue/get-catalogue.ts @@ -1,8 +1,8 @@ import type { GameShop } from "@types"; import { registerEvent } from "../register-event"; -import { HydraApi, RepacksManager } from "@main/services"; -import { CatalogueCategory, formatName, steamUrlBuilder } from "@shared"; +import { HydraApi } from "@main/services"; +import { CatalogueCategory, steamUrlBuilder } from "@shared"; import { steamGamesWorker } from "@main/workers"; const getCatalogue = async ( @@ -26,14 +26,9 @@ const getCatalogue = async ( name: "getById", }); - const repacks = RepacksManager.search({ - query: formatName(steamGame.name), - }); - return { title: steamGame.name, shop: game.shop, - repacks, cover: steamUrlBuilder.library(game.objectId), objectID: game.objectId, }; diff --git a/src/main/events/catalogue/get-game-shop-details.ts b/src/main/events/catalogue/get-game-shop-details.ts index 0b4535f6..3a435013 100644 --- a/src/main/events/catalogue/get-game-shop-details.ts +++ b/src/main/events/catalogue/get-game-shop-details.ts @@ -45,15 +45,17 @@ const getGameShopDetails = async ( const appDetails = getLocalizedSteamAppDetails(objectID, language).then( (result) => { - gameShopCacheRepository.upsert( - { - objectID, - shop: "steam", - language, - serializedData: JSON.stringify(result), - }, - ["objectID"] - ); + if (result) { + gameShopCacheRepository.upsert( + { + objectID, + shop: "steam", + language, + serializedData: JSON.stringify(result), + }, + ["objectID"] + ); + } return result; } diff --git a/src/main/events/catalogue/get-games.ts b/src/main/events/catalogue/get-games.ts index c34451eb..859a0de5 100644 --- a/src/main/events/catalogue/get-games.ts +++ b/src/main/events/catalogue/get-games.ts @@ -2,8 +2,6 @@ import type { CatalogueEntry } from "@types"; import { registerEvent } from "../register-event"; import { steamGamesWorker } from "@main/workers"; -import { convertSteamGameToCatalogueEntry } from "../helpers/search-games"; -import { RepacksManager } from "@main/services"; const getGames = async ( _event: Electron.IpcMainInvokeEvent, @@ -15,13 +13,9 @@ const getGames = async ( { name: "list" } ); - const entries = RepacksManager.findRepacksForCatalogueEntries( - steamGames.map((game) => convertSteamGameToCatalogueEntry(game)) - ); - return { - results: entries, - cursor: cursor + entries.length, + results: steamGames, + cursor: cursor + steamGames.length, }; }; diff --git a/src/main/events/catalogue/get-repacks.ts b/src/main/events/catalogue/get-repacks.ts new file mode 100644 index 00000000..db39fc7e --- /dev/null +++ b/src/main/events/catalogue/get-repacks.ts @@ -0,0 +1,7 @@ +import { registerEvent } from "../register-event"; +import { knexClient } from "@main/knex-client"; + +const getRepacks = (_event: Electron.IpcMainInvokeEvent) => + knexClient.select("*").from("repack"); + +registerEvent("getRepacks", getRepacks); diff --git a/src/main/events/catalogue/search-game-repacks.ts b/src/main/events/catalogue/search-game-repacks.ts deleted file mode 100644 index e3b9c2b5..00000000 --- a/src/main/events/catalogue/search-game-repacks.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { RepacksManager } from "@main/services"; -import { registerEvent } from "../register-event"; - -const searchGameRepacks = ( - _event: Electron.IpcMainInvokeEvent, - query: string -) => RepacksManager.search({ query }); - -registerEvent("searchGameRepacks", searchGameRepacks); diff --git a/src/main/events/download-sources/add-download-source.ts b/src/main/events/download-sources/add-download-source.ts index b0c0e470..b762c95d 100644 --- a/src/main/events/download-sources/add-download-source.ts +++ b/src/main/events/download-sources/add-download-source.ts @@ -4,7 +4,6 @@ import { DownloadSource } from "@main/entity"; import axios from "axios"; import { downloadSourceSchema } from "../helpers/validators"; import { insertDownloadsFromSource } from "@main/helpers"; -import { RepacksManager } from "@main/services"; const addDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, @@ -34,8 +33,6 @@ const addDownloadSource = async ( } ); - await RepacksManager.updateRepacks(); - return downloadSource; }; diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts index b8565645..97f8a6d8 100644 --- a/src/main/events/download-sources/get-download-sources.ts +++ b/src/main/events/download-sources/get-download-sources.ts @@ -1,11 +1,7 @@ -import { downloadSourceRepository } from "@main/repository"; import { registerEvent } from "../register-event"; +import { knexClient } from "@main/knex-client"; const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => - downloadSourceRepository.find({ - order: { - createdAt: "DESC", - }, - }); + knexClient.select("*").from("download_source"); registerEvent("getDownloadSources", getDownloadSources); diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts index 73f2ffbe..8d67df13 100644 --- a/src/main/events/download-sources/remove-download-source.ts +++ b/src/main/events/download-sources/remove-download-source.ts @@ -5,9 +5,6 @@ import { RepacksManager } from "@main/services"; const removeDownloadSource = async ( _event: Electron.IpcMainInvokeEvent, id: number -) => { - await downloadSourceRepository.delete(id); - await RepacksManager.updateRepacks(); -}; +) => downloadSourceRepository.delete(id); registerEvent("removeDownloadSource", removeDownloadSource); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 5fb5098e..58e9bc92 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -17,7 +17,6 @@ export const convertSteamGameToCatalogueEntry = ( title: game.name, shop: "steam" as GameShop, cover: steamUrlBuilder.library(String(game.id)), - repacks: [], }); export const getSteamGameById = async ( diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 54e63a3b..0638f900 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -7,9 +7,9 @@ import "./catalogue/get-games"; import "./catalogue/get-how-long-to-beat"; import "./catalogue/get-random-game"; import "./catalogue/search-games"; -import "./catalogue/search-game-repacks"; import "./catalogue/get-game-stats"; import "./catalogue/get-trending-games"; +import "./catalogue/get-repacks"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; import "./library/create-game-shortcut"; diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 253ab159..491083cb 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -9,36 +9,25 @@ 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, Repack } from "@main/entity"; +import { DownloadQueue, Game } from "@main/entity"; const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, payload: StartGameDownloadPayload ) => { - const { repackId, objectID, title, shop, downloadPath, downloader, uri } = - payload; + const { objectID, title, shop, downloadPath, downloader, uri } = payload; return dataSource.transaction(async (transactionalEntityManager) => { const gameRepository = transactionalEntityManager.getRepository(Game); - const repackRepository = transactionalEntityManager.getRepository(Repack); const downloadQueueRepository = transactionalEntityManager.getRepository(DownloadQueue); - const [game, repack] = await Promise.all([ - gameRepository.findOne({ - where: { - objectID, - shop, - }, - }), - repackRepository.findOne({ - where: { - id: repackId, - }, - }), - ]); - - if (!repack) return; + const game = await gameRepository.findOne({ + where: { + objectID, + shop, + }, + }); await DownloadManager.pauseDownload(); diff --git a/src/main/index.ts b/src/main/index.ts index 00311b46..594220c5 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -68,7 +68,6 @@ const runMigrations = async () => { }); await knexClient.migrate.latest(migrationConfig); - await knexClient.destroy(); }; // This method will be called when Electron has finished diff --git a/src/main/main.ts b/src/main/main.ts index af594e20..690282f6 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,9 +1,4 @@ -import { - DownloadManager, - RepacksManager, - PythonInstance, - startMainLoop, -} from "./services"; +import { DownloadManager, PythonInstance, startMainLoop } from "./services"; import { downloadQueueRepository, repackRepository, @@ -18,8 +13,6 @@ import { HydraApi } from "./services/hydra-api"; import { uploadGamesBatch } from "./services/library-sync"; const loadState = async (userPreferences: UserPreferences | null) => { - RepacksManager.updateRepacks(); - import("./events"); if (userPreferences?.realDebridApiToken) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 0f135b99..38df190d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -49,6 +49,8 @@ contextBridge.exposeInMainWorld("electron", { getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), + /* Meant for Dexie migration */ + getRepacks: () => ipcRenderer.invoke("getRepacks"), /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 5b9e44ca..7b1a2c03 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -26,6 +26,10 @@ import { } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; +import { RepacksContextProvider } from "./context"; +import { downloadSourcesWorker } from "./workers"; + +downloadSourcesWorker.postMessage("OK"); export interface AppProps { children: React.ReactNode; @@ -197,7 +201,7 @@ export function App() { useEffect(() => { new MutationObserver(() => { - const modal = document.body.querySelector("[role=modal]"); + const modal = document.body.querySelector("[role=dialog]"); dispatch(toggleDraggingDisabled(Boolean(modal))); }).observe(document.body, { @@ -211,46 +215,48 @@ export function App() { }, [dispatch]); return ( - <> - {window.electron.platform === "win32" && ( -
-

Hydra

-
- )} + + <> + {window.electron.platform === "win32" && ( +
+

Hydra

+
+ )} - - - {userDetails && ( - - )} -
- - -
-
+ )} -
- -
-
-
+
+ - - +
+
+ +
+ +
+
+
+ + + +
); } diff --git a/src/renderer/src/components/game-card/game-card.tsx b/src/renderer/src/components/game-card/game-card.tsx index 7181e9b3..9d54bad8 100644 --- a/src/renderer/src/components/game-card/game-card.tsx +++ b/src/renderer/src/components/game-card/game-card.tsx @@ -1,13 +1,14 @@ import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; -import type { CatalogueEntry, GameStats } from "@types"; +import type { CatalogueEntry, GameRepack, GameStats } from "@types"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import * as styles from "./game-card.css"; import { useTranslation } from "react-i18next"; import { Badge } from "../badge/badge"; -import { useCallback, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import { useFormat } from "@renderer/hooks"; +import { repacksContext } from "@renderer/context"; export interface GameCardProps extends React.DetailedHTMLProps< @@ -25,9 +26,20 @@ export function GameCard({ game, ...props }: GameCardProps) { const { t } = useTranslation("game_card"); const [stats, setStats] = useState(null); + const [repacks, setRepacks] = useState([]); + + const { searchRepacks, isIndexingRepacks } = useContext(repacksContext); + + useEffect(() => { + if (!isIndexingRepacks) { + searchRepacks(game.title).then((repacks) => { + setRepacks(repacks); + }); + } + }, [game, isIndexingRepacks, searchRepacks]); const uniqueRepackers = Array.from( - new Set(game.repacks.map(({ repacker }) => repacker)) + new Set(repacks.map(({ repacker }) => repacker)) ); const handleHover = useCallback(() => { 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 e723779f..120728b1 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -1,4 +1,10 @@ -import { createContext, useCallback, useEffect, useState } from "react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from "react"; import { useParams, useSearchParams } from "react-router-dom"; import { setHeaderTitle } from "@renderer/features"; @@ -16,6 +22,7 @@ import type { import { useTranslation } from "react-i18next"; import { GameDetailsContext } from "./game-details.context.types"; import { SteamContentDescriptor } from "@shared"; +import { repacksContext } from "../repacks/repacks.context"; export const gameDetailsContext = createContext({ game: null, @@ -52,7 +59,6 @@ export function GameDetailsContextProvider({ const { objectID, shop } = useParams(); const [shopDetails, setShopDetails] = useState(null); - const [repacks, setRepacks] = useState([]); const [game, setGame] = useState(null); const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false); @@ -64,10 +70,22 @@ export function GameDetailsContextProvider({ const [showRepacksModal, setShowRepacksModal] = useState(false); const [showGameOptionsModal, setShowGameOptionsModal] = useState(false); + const [repacks, setRepacks] = useState([]); + const [searchParams] = useSearchParams(); const gameTitle = searchParams.get("title")!; + const { searchRepacks, isIndexingRepacks } = useContext(repacksContext); + + useEffect(() => { + if (!isIndexingRepacks) { + searchRepacks(gameTitle).then((repacks) => { + setRepacks(repacks); + }); + } + }, [game, gameTitle, isIndexingRepacks, searchRepacks]); + const { i18n } = useTranslation("game_details"); const dispatch = useAppDispatch(); @@ -91,37 +109,31 @@ export function GameDetailsContextProvider({ }, [updateGame, isGameDownloading, lastPacket?.game.status]); useEffect(() => { - Promise.allSettled([ - window.electron.getGameShopDetails( + window.electron + .getGameShopDetails( objectID!, shop as GameShop, getSteamLanguage(i18n.language) - ), - window.electron.searchGameRepacks(gameTitle), - window.electron.getGameStats(objectID!, shop as GameShop), - ]) - .then(([appDetailsResult, repacksResult, statsResult]) => { - if (appDetailsResult.status === "fulfilled") { - setShopDetails(appDetailsResult.value); + ) + .then((result) => { + setShopDetails(result); - if ( - appDetailsResult.value?.content_descriptors.ids.includes( - SteamContentDescriptor.AdultOnlySexualContent - ) - ) { - setHasNSFWContentBlocked(true); - } + if ( + result?.content_descriptors.ids.includes( + SteamContentDescriptor.AdultOnlySexualContent + ) + ) { + setHasNSFWContentBlocked(true); } - - if (repacksResult.status === "fulfilled") - setRepacks(repacksResult.value); - - if (statsResult.status === "fulfilled") setStats(statsResult.value); }) .finally(() => { setIsLoading(false); }); + window.electron.getGameStats(objectID!, shop as GameShop).then((result) => { + setStats(result); + }); + updateGame(); }, [updateGame, dispatch, gameTitle, objectID, shop, i18n.language]); diff --git a/src/renderer/src/context/index.ts b/src/renderer/src/context/index.ts index d9c1c7e4..8d8b9223 100644 --- a/src/renderer/src/context/index.ts +++ b/src/renderer/src/context/index.ts @@ -1,3 +1,4 @@ export * from "./game-details/game-details.context"; export * from "./settings/settings.context"; export * from "./user-profile/user-profile.context"; +export * from "./repacks/repacks.context"; diff --git a/src/renderer/src/context/repacks/repacks.context.tsx b/src/renderer/src/context/repacks/repacks.context.tsx new file mode 100644 index 00000000..a2e4101b --- /dev/null +++ b/src/renderer/src/context/repacks/repacks.context.tsx @@ -0,0 +1,58 @@ +import type { GameRepack } from "@types"; +import { createContext, useCallback, useEffect, useState } from "react"; + +import { repacksWorker } from "@renderer/workers"; + +export interface RepacksContext { + searchRepacks: (query: string) => Promise; + isIndexingRepacks: boolean; +} + +export const repacksContext = createContext({ + searchRepacks: async () => [] as GameRepack[], + isIndexingRepacks: false, +}); + +const { Provider } = repacksContext; +export const { Consumer: RepacksContextConsumer } = repacksContext; + +export interface RepacksContextProps { + children: React.ReactNode; +} + +export function RepacksContextProvider({ children }: RepacksContextProps) { + const [isIndexingRepacks, setIsIndexingRepacks] = useState(true); + + const searchRepacks = useCallback(async (query: string) => { + return new Promise((resolve) => { + const channelId = crypto.randomUUID(); + repacksWorker.postMessage([channelId, query]); + + const channel = new BroadcastChannel(`repacks:search:${channelId}`); + channel.onmessage = (event: MessageEvent) => { + resolve(event.data); + }; + + return []; + }); + }, []); + + useEffect(() => { + repacksWorker.postMessage("INDEX_REPACKS"); + + repacksWorker.onmessage = () => { + setIsIndexingRepacks(false); + }; + }, []); + + return ( + + {children} + + ); +} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 70b77eec..3673ec08 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -65,6 +65,8 @@ declare global { searchGameRepacks: (query: string) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; + /* Meant for Dexie migration */ + getRepacks: () => Promise; /* Library */ addGameToLibrary: ( diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts new file mode 100644 index 00000000..2b9f0aa6 --- /dev/null +++ b/src/renderer/src/dexie.ts @@ -0,0 +1,13 @@ +import { Dexie } from "dexie"; + +export const db = new Dexie("Hydra"); + +db.version(1).stores({ + repacks: `++id, title, uri, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`, + downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`, +}); + +export const downloadSourcesTable = db.table("downloadSources"); +export const repacksTable = db.table("repacks"); + +db.open(); diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index b98d5ed9..5d9b2197 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -29,6 +29,8 @@ import { store } from "./store"; import resources from "@locales"; +import "./workers"; + Sentry.init({}); i18n diff --git a/src/renderer/src/pages/settings/add-download-source-modal.tsx b/src/renderer/src/pages/settings/add-download-source-modal.tsx index 015ee0dc..fba890d1 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -8,6 +8,7 @@ import { useForm } from "react-hook-form"; import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; +import { downloadSourcesTable } from "@renderer/dexie"; interface AddDownloadSourceModalProps { visible: boolean; @@ -91,6 +92,9 @@ export function AddDownloadSourceModal({ }, [visible, clearErrors, handleSubmit, onSubmit, setValue, sourceUrl]); const handleAddDownloadSource = async () => { + await downloadSourcesTable.add({ + url, + }); await window.electron.addDownloadSource(url); onClose(); onAddDownloadSource(); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 1646af22..53c14348 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -11,6 +11,7 @@ import { useToast } from "@renderer/hooks"; import { DownloadSourceStatus } from "@shared"; import { SPACING_UNIT } from "@renderer/theme.css"; import { settingsContext } from "@renderer/context"; +import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; export function SettingsDownloadSources() { const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] = @@ -25,7 +26,7 @@ export function SettingsDownloadSources() { const { showSuccessToast } = useToast(); const getDownloadSources = async () => { - return window.electron.getDownloadSources().then((sources) => { + downloadSourcesTable.toArray().then((sources) => { setDownloadSources(sources); }); }; @@ -39,7 +40,11 @@ export function SettingsDownloadSources() { }, [sourceUrl]); const handleRemoveSource = async (id: number) => { - await window.electron.removeDownloadSource(id); + await db.transaction("rw", downloadSourcesTable, repacksTable, async () => { + await downloadSourcesTable.where({ id }).delete(); + await repacksTable.where({ downloadSourceId: id }).delete(); + }); + showSuccessToast(t("removed_download_source")); getDownloadSources(); diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts new file mode 100644 index 00000000..609b6bcf --- /dev/null +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -0,0 +1,8 @@ +import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; + +self.onmessage = () => { + db.transaction("rw", repacksTable, downloadSourcesTable, async () => { + await repacksTable.where({ downloadSourceId: 10 }).delete(); + await downloadSourcesTable.where({ id: 10 }).delete(); + }); +}; diff --git a/src/renderer/src/workers/index.ts b/src/renderer/src/workers/index.ts new file mode 100644 index 00000000..9a5ab920 --- /dev/null +++ b/src/renderer/src/workers/index.ts @@ -0,0 +1,24 @@ +import MigrationWorker from "./migration.worker?worker"; +import RepacksWorker from "./repacks.worker?worker"; +import DownloadSourcesWorker from "./download-sources.worker?worker"; + +// const migrationWorker = new MigrationWorker(); +export const repacksWorker = new RepacksWorker(); +export const downloadSourcesWorker = new DownloadSourcesWorker(); + +// window.electron.getRepacks().then((repacks) => { +// console.log(repacks); +// migrationWorker.postMessage(["MIGRATE_REPACKS", repacks]); +// }); + +// window.electron.getDownloadSources().then((downloadSources) => { +// migrationWorker.postMessage(["MIGRATE_DOWNLOAD_SOURCES", downloadSources]); +// }); + +// migrationWorker.onmessage = (event) => { +// console.log(event.data); +// }; + +// setTimeout(() => { +// repacksWorker.postMessage("god"); +// }, 500); diff --git a/src/renderer/src/workers/migration.worker.ts b/src/renderer/src/workers/migration.worker.ts new file mode 100644 index 00000000..848dd052 --- /dev/null +++ b/src/renderer/src/workers/migration.worker.ts @@ -0,0 +1,32 @@ +import { downloadSourcesTable, repacksTable } from "@renderer/dexie"; +import { DownloadSource, GameRepack } from "@types"; + +export type Payload = + | ["MIGRATE_REPACKS", GameRepack[]] + | ["MIGRATE_DOWNLOAD_SOURCES", DownloadSource[]]; + +self.onmessage = async (event: MessageEvent) => { + const [type, data] = event.data; + + if (type === "MIGRATE_DOWNLOAD_SOURCES") { + const dexieDownloadSources = await downloadSourcesTable.count(); + + if (data.length !== dexieDownloadSources) { + await downloadSourcesTable.clear(); + await downloadSourcesTable.bulkAdd(data); + } + + self.postMessage("MIGRATE_DOWNLOAD_SOURCES_COMPLETE"); + } + + if (type === "MIGRATE_REPACKS") { + const dexieRepacks = await repacksTable.count(); + + if (data.length !== dexieRepacks) { + await repacksTable.clear(); + await repacksTable.bulkAdd(data); + } + + self.postMessage("MIGRATE_REPACKS_COMPLETE"); + } +}; diff --git a/src/renderer/src/workers/repacks.worker.ts b/src/renderer/src/workers/repacks.worker.ts new file mode 100644 index 00000000..0e3a9ce7 --- /dev/null +++ b/src/renderer/src/workers/repacks.worker.ts @@ -0,0 +1,52 @@ +import { repacksTable } from "@renderer/dexie"; +import { formatName } from "@shared"; +import { GameRepack } from "@types"; +import flexSearch from "flexsearch"; + +const index = new flexSearch.Index(); + +const state = { + repacks: [] as any[], +}; + +interface SerializedGameRepack extends Omit { + uris: string; +} + +self.onmessage = async ( + event: MessageEvent<[string, string] | "INDEX_REPACKS"> +) => { + if (event.data === "INDEX_REPACKS") { + repacksTable + .toCollection() + .sortBy("uploadDate") + .then((results) => { + state.repacks = results.reverse(); + + for (let i = 0; i < state.repacks.length; i++) { + const repack = state.repacks[i]; + const formattedTitle = formatName(repack.title); + index.add(i, formattedTitle); + } + + self.postMessage("INDEXING_COMPLETE"); + }); + } else { + const [requestId, query] = event.data; + + const results = index.search(formatName(query)).map((index) => { + const repack = state.repacks.at(index as number) as SerializedGameRepack; + + const uris = JSON.parse(repack.uris); + + return { + ...repack, + uris: [...uris, repack.magnet].filter(Boolean), + }; + }); + + const channel = new BroadcastChannel(`repacks:search:${requestId}`); + + channel.postMessage(results); + } +}; diff --git a/src/types/index.ts b/src/types/index.ts index 5b961dd6..a8fb3771 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -44,7 +44,6 @@ export interface CatalogueEntry { title: string; /* Epic Games covers cannot be guessed with objectID */ cover: string; - repacks: GameRepack[]; } export interface UserGame { @@ -71,7 +70,6 @@ export interface Game { status: GameStatus | null; folderName: string; downloadPath: string | null; - repacks: GameRepack[]; progress: number; bytesDownloaded: number; playTimeInMilliseconds: number; diff --git a/yarn.lock b/yarn.lock index 9aa73bd4..14651b4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3638,6 +3638,11 @@ detect-node@^2.0.4: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +dexie@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/dexie/-/dexie-4.0.8.tgz#21fca70686bdaa1d86fad45b6b19316f6a084a1d" + integrity sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"