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"