diff --git a/src/main/events/catalogue/get-catalogue.ts b/src/main/events/catalogue/get-catalogue.ts
index 24654bdc..b4ce4531 100644
--- a/src/main/events/catalogue/get-catalogue.ts
+++ b/src/main/events/catalogue/get-catalogue.ts
@@ -2,7 +2,9 @@ import { getSteamAppAsset } from "@main/helpers";
import type { CatalogueEntry, GameShop } from "@types";
import { registerEvent } from "../register-event";
-import { SearchEngine, requestSteam250 } from "@main/services";
+import { requestSteam250 } from "@main/services";
+import { repacksWorker } from "@main/workers";
+import { formatName } from "@shared";
const resultSize = 12;
@@ -17,7 +19,10 @@ const getCatalogue = async (_event: Electron.IpcMainInvokeEvent) => {
}
const { title, objectID } = trendingGames[i]!;
- const repacks = SearchEngine.searchRepacks(title);
+ const repacks = await repacksWorker.run(
+ { query: formatName(title) },
+ { name: "search" }
+ );
const catalogueEntry = {
objectID,
diff --git a/src/main/events/catalogue/get-games.ts b/src/main/events/catalogue/get-games.ts
index 21e7bc03..8a538178 100644
--- a/src/main/events/catalogue/get-games.ts
+++ b/src/main/events/catalogue/get-games.ts
@@ -1,22 +1,29 @@
import type { CatalogueEntry } from "@types";
import { registerEvent } from "../register-event";
+import { repacksWorker, steamGamesWorker } from "@main/workers";
import { convertSteamGameToCatalogueEntry } from "../helpers/search-games";
-import { steamGamesWorker } from "@main/workers";
const getGames = async (
_event: Electron.IpcMainInvokeEvent,
take = 12,
cursor = 0
): Promise<{ results: CatalogueEntry[]; cursor: number }> => {
- const results = await steamGamesWorker.run(
+ const steamGames = await steamGamesWorker.run(
{ limit: take, offset: cursor },
{ name: "list" }
);
+ const entries = await repacksWorker.run(
+ steamGames.map((game) => convertSteamGameToCatalogueEntry(game)),
+ {
+ name: "findRepacksForCatalogueEntries",
+ }
+ );
+
return {
- results: results.map((result) => convertSteamGameToCatalogueEntry(result)),
- cursor: cursor + results.length,
+ results: entries,
+ cursor: cursor + entries.length,
};
};
diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts
index 7a1a641b..77ac48a6 100644
--- a/src/main/events/catalogue/get-random-game.ts
+++ b/src/main/events/catalogue/get-random-game.ts
@@ -1,10 +1,12 @@
import { shuffle } from "lodash-es";
-import { SearchEngine, getSteam250List } from "@main/services";
+import { getSteam250List } from "@main/services";
import { registerEvent } from "../register-event";
import { searchSteamGames } from "../helpers/search-games";
import type { Steam250Game } from "@types";
+import { repacksWorker } from "@main/workers";
+import { formatName } from "@shared";
const state = { games: Array(), index: 0 };
@@ -15,7 +17,10 @@ const filterGames = async (games: Steam250Game[]) => {
const catalogue = await searchSteamGames({ query: game.title });
if (catalogue.length) {
- const repacks = SearchEngine.searchRepacks(catalogue[0].title);
+ const repacks = await repacksWorker.run(
+ { query: formatName(catalogue[0].title) },
+ { name: "search" }
+ );
if (repacks.length) {
results.push(game);
diff --git a/src/main/events/catalogue/search-game-repacks.ts b/src/main/events/catalogue/search-game-repacks.ts
index a03fe5f0..37bf74f1 100644
--- a/src/main/events/catalogue/search-game-repacks.ts
+++ b/src/main/events/catalogue/search-game-repacks.ts
@@ -1,9 +1,9 @@
-import { SearchEngine } from "@main/services";
import { registerEvent } from "../register-event";
+import { repacksWorker } from "@main/workers";
const searchGameRepacks = (
_event: Electron.IpcMainInvokeEvent,
query: string
-) => SearchEngine.searchRepacks(query);
+) => repacksWorker.run({ query }, { name: "search" });
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 a58f0cc6..cb311184 100644
--- a/src/main/events/download-sources/add-download-source.ts
+++ b/src/main/events/download-sources/add-download-source.ts
@@ -5,7 +5,8 @@ import { DownloadSource, Repack } from "@main/entity";
import axios from "axios";
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
import { downloadSourceSchema } from "../helpers/validators";
-import { SearchEngine } from "@main/services";
+import { repackRepository } from "@main/repository";
+import { repacksWorker } from "@main/workers";
const addDownloadSource = async (
_event: Electron.IpcMainInvokeEvent,
@@ -49,7 +50,15 @@ const addDownloadSource = async (
}
);
- await SearchEngine.updateRepacks();
+ repackRepository
+ .find({
+ order: {
+ createdAt: "DESC",
+ },
+ })
+ .then((repacks) => {
+ repacksWorker.run(repacks, { name: "setRepacks" });
+ });
return downloadSource;
};
diff --git a/src/main/events/download-sources/remove-download-source.ts b/src/main/events/download-sources/remove-download-source.ts
index 0f52c58e..a50c5ec9 100644
--- a/src/main/events/download-sources/remove-download-source.ts
+++ b/src/main/events/download-sources/remove-download-source.ts
@@ -1,13 +1,22 @@
-import { downloadSourceRepository } from "@main/repository";
+import { downloadSourceRepository, repackRepository } from "@main/repository";
import { registerEvent } from "../register-event";
-import { SearchEngine } from "@main/services";
+import { repacksWorker } from "@main/workers";
const removeDownloadSource = async (
_event: Electron.IpcMainInvokeEvent,
id: number
) => {
await downloadSourceRepository.delete(id);
- await SearchEngine.updateRepacks();
+
+ repackRepository
+ .find({
+ order: {
+ createdAt: "DESC",
+ },
+ })
+ .then((repacks) => {
+ repacksWorker.run(repacks, { name: "setRepacks" });
+ });
};
registerEvent("removeDownloadSource", removeDownloadSource);
diff --git a/src/main/events/download-sources/validate-download-source.ts b/src/main/events/download-sources/validate-download-source.ts
index 622e2748..20a3fca7 100644
--- a/src/main/events/download-sources/validate-download-source.ts
+++ b/src/main/events/download-sources/validate-download-source.ts
@@ -1,22 +1,9 @@
-import { z } from "zod";
import { registerEvent } from "../register-event";
import axios from "axios";
import { downloadSourceRepository } from "@main/repository";
-
-const downloadSourceSchema = z.object({
- name: z.string().max(255),
- downloads: z.array(
- z.object({
- title: z.string().max(255),
- objectId: z.string().max(255).nullable(),
- shop: z.enum(["steam"]).nullable(),
- downloaders: z.array(z.enum(["real_debrid", "torrent"])),
- uris: z.array(z.string()),
- uploadDate: z.string().max(255),
- fileSize: z.string().max(255),
- })
- ),
-});
+import { downloadSourceSchema } from "../helpers/validators";
+import { repacksWorker } from "@main/workers";
+import { GameRepack } from "@types";
const validateDownloadSource = async (
_event: Electron.IpcMainInvokeEvent,
@@ -27,13 +14,26 @@ const validateDownloadSource = async (
const source = downloadSourceSchema.parse(response.data);
const existingSource = await downloadSourceRepository.findOne({
- where: [{ url }, { name: source.name }],
+ where: { url },
});
- if (existingSource?.url === url)
+ if (existingSource)
throw new Error("Source with the same url already exists");
- return { name: source.name, downloadCount: source.downloads.length };
+ const repacks = (await repacksWorker.run(undefined, {
+ name: "list",
+ })) as GameRepack[];
+
+ console.log(repacks);
+
+ const existingUris = source.downloads
+ .flatMap((download) => download.uris)
+ .filter((uri) => repacks.some((repack) => repack.magnet === uri));
+
+ return {
+ name: source.name,
+ downloadCount: source.downloads.length - existingUris.length,
+ };
};
registerEvent("validateDownloadSource", validateDownloadSource);
diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts
index 4d075409..5434c08a 100644
--- a/src/main/events/helpers/search-games.ts
+++ b/src/main/events/helpers/search-games.ts
@@ -4,8 +4,7 @@ import flexSearch from "flexsearch";
import type { GameShop, CatalogueEntry, SteamGame } from "@types";
import { getSteamAppAsset } from "@main/helpers";
-import { SearchEngine } from "@main/services";
-import { steamGamesWorker } from "@main/workers";
+import { repacksWorker, steamGamesWorker } from "@main/workers";
export interface SearchGamesArgs {
query?: string;
@@ -14,24 +13,31 @@ export interface SearchGamesArgs {
}
export const convertSteamGameToCatalogueEntry = (
- result: SteamGame
-): CatalogueEntry => {
- return {
- objectID: String(result.id),
- title: result.name,
- shop: "steam" as GameShop,
- cover: getSteamAppAsset("library", String(result.id)),
- repacks: SearchEngine.searchRepacks(result.name),
- };
-};
+ game: SteamGame
+): CatalogueEntry => ({
+ objectID: String(game.id),
+ title: game.name,
+ shop: "steam" as GameShop,
+ cover: getSteamAppAsset("library", String(game.id)),
+ repacks: [],
+});
export const searchSteamGames = async (
options: flexSearch.SearchOptions
): Promise => {
- const steamGames = await steamGamesWorker.run(options, { name: "search" });
+ const steamGames = (await steamGamesWorker.run(options, {
+ name: "search",
+ })) as SteamGame[];
+
+ const result = await repacksWorker.run(
+ steamGames.map((game) => convertSteamGameToCatalogueEntry(game)),
+ {
+ name: "findRepacksForCatalogueEntries",
+ }
+ );
return orderBy(
- steamGames.map((result) => convertSteamGameToCatalogueEntry(result)),
+ result,
[({ repacks }) => repacks.length, "repacks"],
["desc"]
);
diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts
index 429ab4ea..b17807d5 100644
--- a/src/main/events/library/get-library.ts
+++ b/src/main/events/library/get-library.ts
@@ -7,7 +7,7 @@ const getLibrary = async () =>
isDeleted: false,
},
order: {
- createdAt: "desc",
+ updatedAt: "desc",
},
});
diff --git a/src/main/main.ts b/src/main/main.ts
index e12b5f8c..b81398cf 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -1,8 +1,13 @@
-import { DownloadManager, SearchEngine, startMainLoop } from "./services";
-import { gameRepository, userPreferencesRepository } from "./repository";
+import { DownloadManager, startMainLoop } from "./services";
+import {
+ gameRepository,
+ repackRepository,
+ userPreferencesRepository,
+} from "./repository";
import { UserPreferences } from "./entity";
import { RealDebridClient } from "./services/real-debrid";
import { Not } from "typeorm";
+import { repacksWorker } from "./workers";
startMainLoop();
@@ -21,7 +26,16 @@ const loadState = async (userPreferences: UserPreferences | null) => {
});
if (game) DownloadManager.startDownload(game);
- await SearchEngine.updateRepacks();
+
+ repackRepository
+ .find({
+ order: {
+ createdAt: "DESC",
+ },
+ })
+ .then((repacks) => {
+ repacksWorker.run(repacks, { name: "setRepacks" });
+ });
};
userPreferencesRepository
diff --git a/src/main/services/index.ts b/src/main/services/index.ts
index bada604e..93427179 100644
--- a/src/main/services/index.ts
+++ b/src/main/services/index.ts
@@ -7,4 +7,3 @@ export * from "./download-manager";
export * from "./how-long-to-beat";
export * from "./process-watcher";
export * from "./main-loop";
-export * from "./search-engine";
diff --git a/src/main/services/search-engine.ts b/src/main/services/search-engine.ts
deleted file mode 100644
index 87b268d3..00000000
--- a/src/main/services/search-engine.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import flexSearch from "flexsearch";
-
-import { repackRepository } from "@main/repository";
-import { formatName } from "@shared";
-import type { GameRepack } from "@types";
-
-export class SearchEngine {
- public static repacks: GameRepack[] = [];
-
- private static repacksIndex = new flexSearch.Index();
-
- public static searchRepacks(query: string): GameRepack[] {
- return this.repacksIndex
- .search(formatName(query))
- .map((index) => this.repacks[index]);
- }
-
- public static async updateRepacks() {
- this.repacks = [];
-
- const repacks = await repackRepository.find({
- order: {
- createdAt: "desc",
- },
- });
-
- for (let i = 0; i < repacks.length; i++) {
- const repack = repacks[i];
-
- const formattedTitle = formatName(repack.title);
-
- this.repacks = [...this.repacks, { ...repack, title: formattedTitle }];
- this.repacksIndex.add(i, formattedTitle);
- }
- }
-}
diff --git a/src/main/workers/index.ts b/src/main/workers/index.ts
index 00d51f91..fd17995e 100644
--- a/src/main/workers/index.ts
+++ b/src/main/workers/index.ts
@@ -1,5 +1,6 @@
import path from "node:path";
import steamGamesWorkerPath from "./steam-games.worker?modulePath";
+import repacksWorkerPath from "./repacks.worker?modulePath";
import Piscina from "piscina";
@@ -11,3 +12,7 @@ export const steamGamesWorker = new Piscina({
steamGamesPath: path.join(seedsPath, "steam-games.json"),
},
});
+
+export const repacksWorker = new Piscina({
+ filename: repacksWorkerPath,
+});
diff --git a/src/main/workers/repacks.worker.ts b/src/main/workers/repacks.worker.ts
new file mode 100644
index 00000000..b781367e
--- /dev/null
+++ b/src/main/workers/repacks.worker.ts
@@ -0,0 +1,31 @@
+import { formatName } from "@shared";
+import { CatalogueEntry, GameRepack } from "@types";
+import flexSearch from "flexsearch";
+
+const repacksIndex = new flexSearch.Index();
+
+const state: { repacks: GameRepack[] } = { repacks: [] };
+
+export const setRepacks = (repacks: GameRepack[]) => {
+ state.repacks = repacks;
+
+ for (let i = 0; i < repacks.length; i++) {
+ const repack = repacks[i];
+
+ const formattedTitle = formatName(repack.title);
+
+ repacksIndex.add(i, formattedTitle);
+ }
+};
+
+export const search = (options: flexSearch.SearchOptions) =>
+ repacksIndex.search(options).map((index) => state.repacks[index]);
+
+export const list = () => state.repacks;
+
+export const findRepacksForCatalogueEntries = (entries: CatalogueEntry[]) => {
+ return entries.map((entry) => {
+ const repacks = search({ query: formatName(entry.title) });
+ return { ...entry, repacks };
+ });
+};
diff --git a/src/main/workers/steam-games.worker.ts b/src/main/workers/steam-games.worker.ts
index 4a41e06e..8783c7a1 100644
--- a/src/main/workers/steam-games.worker.ts
+++ b/src/main/workers/steam-games.worker.ts
@@ -25,7 +25,7 @@ for (let i = 0; i < steamGames.length; i++) {
}
export const search = (options: flexSearch.SearchOptions) => {
- const results = steamGamesIndex.search(options.query ?? "", options);
+ const results = steamGamesIndex.search(options);
const games = results.map((index) => steamGames[index]);
return orderBy(games, ["name"], ["asc"]);
diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts
index 3a64455d..975940b9 100644
--- a/src/renderer/src/helpers.ts
+++ b/src/renderer/src/helpers.ts
@@ -40,7 +40,3 @@ export const buildGameDetailsPath = (
const searchParams = new URLSearchParams({ title: game.title, ...params });
return `/game/${game.shop}/${game.objectID}?${searchParams.toString()}`;
};
-
-export const numberFormatter = new Intl.NumberFormat("en-US", {
- maximumSignificantDigits: 3,
-});
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
index 708dbfed..937ca613 100644
--- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
+++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
@@ -224,47 +224,51 @@ export function HeroPanelActions() {
);
}
- return (
- <>
- {game?.progress === 1 ? (
- <>
- setShowBinaryNotFoundModal(false)}
- />
+ if (game) {
+ return (
+ <>
+ {game?.progress === 1 ? (
+ <>
+ setShowBinaryNotFoundModal(false)}
+ />
+
+ >
+ ) : (
+ toggleGameOnLibraryButton
+ )}
+
+ {isGameRunning ? (
+ ) : (
+
- >
- ) : (
- toggleGameOnLibraryButton
- )}
+ )}
+ >
+ );
+ }
- {isGameRunning ? (
-
- ) : (
-
- )}
- >
- );
+ return toggleGameOnLibraryButton;
}
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 ffe1421a..c7d9bbde 100644
--- a/src/renderer/src/pages/settings/add-download-source-modal.tsx
+++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx
@@ -1,10 +1,9 @@
import { Button, Modal, TextField } from "@renderer/components";
import { SPACING_UNIT } from "@renderer/theme.css";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import * as styles from "./settings-download-sources.css";
-import { numberFormatter } from "@renderer/helpers";
interface AddDownloadSourceModalProps {
visible: boolean;
@@ -25,6 +24,12 @@ export function AddDownloadSourceModal({
downloadCount: number;
} | null>(null);
+ useEffect(() => {
+ setValue("");
+ setIsLoading(false);
+ setValidationResult(null);
+ }, [visible]);
+
const { t } = useTranslation("settings");
const handleValidateDownloadSource = async () => {
@@ -32,8 +37,8 @@ export function AddDownloadSourceModal({
try {
const result = await window.electron.validateDownloadSource(value);
- setValidationResult(result);
console.log(result);
+ setValidationResult(result);
} finally {
setIsLoading(false);
}
@@ -96,7 +101,8 @@ export function AddDownloadSourceModal({
>
{validationResult?.name}
- Found {numberFormatter.format(validationResult?.downloadCount)}{" "}
+ Found{" "}
+ {validationResult?.downloadCount.toLocaleString(undefined)}{" "}
download options
diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx
index fbb766d4..330e92a6 100644
--- a/src/renderer/src/pages/settings/settings-download-sources.tsx
+++ b/src/renderer/src/pages/settings/settings-download-sources.tsx
@@ -8,7 +8,6 @@ import type { DownloadSource } from "@types";
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
import { AddDownloadSourceModal } from "./add-download-source-modal";
import { useToast } from "@renderer/hooks";
-import { numberFormatter } from "@renderer/helpers";
export function SettingsDownloadSources() {
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
@@ -53,6 +52,16 @@ export function SettingsDownloadSources() {
{t("download_sources_description")}
+
+
{downloadSources.map((downloadSource) => (
@@ -60,9 +69,7 @@ export function SettingsDownloadSources() {
{t("download_options", {
count: downloadSource.repackCount,
- countFormatted: numberFormatter.format(
- downloadSource.repackCount
- ),
+ countFormatted: downloadSource.repackCount.toLocaleString(),
})}
@@ -87,16 +94,6 @@ export function SettingsDownloadSources() {
))}
-
-
>
);
}