From 17febcd88a604860294bed10a61b75ffa0e439f8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 27 Sep 2024 03:27:02 +0100 Subject: [PATCH 1/2] feat: adding notification when all repacks are migrated --- electron-builder.yml | 2 +- src/main/events/catalogue/get-random-game.ts | 19 +-- src/main/events/catalogue/get-repacks.ts | 7 - src/main/events/catalogue/search-games.ts | 6 +- .../delete-download-source.ts | 9 ++ .../download-sources/sync-download-sources.ts | 13 -- .../validate-download-source.ts | 12 -- src/main/events/helpers/search-games.ts | 7 +- src/main/events/helpers/validators.ts | 13 -- src/main/events/index.ts | 5 +- .../events/library/add-game-to-library.ts | 21 +-- .../publish-new-repacks-notification.ts | 29 ++++ .../events/torrenting/start-game-download.ts | 31 ++-- src/main/helpers/index.ts | 10 -- src/main/index.ts | 44 +++++- src/main/main.ts | 16 --- src/main/services/index.ts | 1 - src/main/services/notifications.ts | 18 --- src/main/services/repacks-manager.ts | 63 -------- src/main/workers/download-sources.worker.ts | 59 -------- src/main/workers/index.ts | 5 - src/preload/index.ts | 13 +- src/renderer/src/app.tsx | 60 +++++--- .../src/context/repacks/repacks.context.tsx | 1 + src/renderer/src/declaration.d.ts | 11 +- .../settings/add-download-source-modal.tsx | 27 ++-- .../settings/settings-download-sources.tsx | 21 +-- .../src/workers/download-sources.worker.ts | 136 +++++++++++++++--- src/renderer/src/workers/index.ts | 2 - src/renderer/src/workers/migration.worker.ts | 35 ----- src/types/index.ts | 2 +- 31 files changed, 308 insertions(+), 390 deletions(-) delete mode 100644 src/main/events/catalogue/get-repacks.ts create mode 100644 src/main/events/download-sources/delete-download-source.ts delete mode 100644 src/main/events/download-sources/sync-download-sources.ts delete mode 100644 src/main/events/download-sources/validate-download-source.ts delete mode 100644 src/main/events/helpers/validators.ts create mode 100644 src/main/events/notifications/publish-new-repacks-notification.ts delete mode 100644 src/main/services/repacks-manager.ts delete mode 100644 src/main/workers/download-sources.worker.ts delete mode 100644 src/renderer/src/workers/migration.worker.ts diff --git a/electron-builder.yml b/electron-builder.yml index a085b1e9..06473566 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -1,4 +1,4 @@ -appId: site.hydralauncher.hydra +appId: gg.hydralauncher.hydra productName: Hydra directories: buildResources: build diff --git a/src/main/events/catalogue/get-random-game.ts b/src/main/events/catalogue/get-random-game.ts index 57acfc30..0a3797a9 100644 --- a/src/main/events/catalogue/get-random-game.ts +++ b/src/main/events/catalogue/get-random-game.ts @@ -3,32 +3,15 @@ import { shuffle } from "lodash-es"; import { getSteam250List } from "@main/services"; import { registerEvent } from "../register-event"; -// import { getSteamGameById } from "../helpers/search-games"; import type { Steam250Game } from "@types"; const state = { games: Array(), index: 0 }; -const filterGames = async (_games: Steam250Game[]) => { - const results: Steam250Game[] = []; - - // for (const game of games) { - // const steamGame = await getSteamGameById(game.objectID); - - // if (steamGame?.repacks.length) { - // results.push(game); - // } - // } - - return results; -}; - const getRandomGame = async (_event: Electron.IpcMainInvokeEvent) => { if (state.games.length == 0) { const steam250List = await getSteam250List(); - const filteredSteam250List = await filterGames(steam250List); - - state.games = shuffle(filteredSteam250List); + state.games = shuffle(steam250List); } if (state.games.length == 0) { diff --git a/src/main/events/catalogue/get-repacks.ts b/src/main/events/catalogue/get-repacks.ts deleted file mode 100644 index db39fc7e..00000000 --- a/src/main/events/catalogue/get-repacks.ts +++ /dev/null @@ -1,7 +0,0 @@ -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-games.ts b/src/main/events/catalogue/search-games.ts index ebe601f2..8f81d40e 100644 --- a/src/main/events/catalogue/search-games.ts +++ b/src/main/events/catalogue/search-games.ts @@ -1,7 +1,7 @@ import { registerEvent } from "../register-event"; import { convertSteamGameToCatalogueEntry } from "../helpers/search-games"; import { CatalogueEntry } from "@types"; -import { HydraApi, RepacksManager } from "@main/services"; +import { HydraApi } from "@main/services"; const searchGamesEvent = async ( _event: Electron.IpcMainInvokeEvent, @@ -11,15 +11,13 @@ const searchGamesEvent = async ( { objectId: string; title: string; shop: string }[] >("/games/search", { title: query, take: 12, skip: 0 }, { needsAuth: false }); - const steamGames = games.map((game) => { + return games.map((game) => { return convertSteamGameToCatalogueEntry({ id: Number(game.objectId), name: game.title, clientIcon: null, }); }); - - return RepacksManager.findRepacksForCatalogueEntries(steamGames); }; registerEvent("searchGames", searchGamesEvent); diff --git a/src/main/events/download-sources/delete-download-source.ts b/src/main/events/download-sources/delete-download-source.ts new file mode 100644 index 00000000..abfbf661 --- /dev/null +++ b/src/main/events/download-sources/delete-download-source.ts @@ -0,0 +1,9 @@ +import { registerEvent } from "../register-event"; +import { knexClient } from "@main/knex-client"; + +const deleteDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + id: number +) => knexClient("download_source").where({ id }).delete(); + +registerEvent("deleteDownloadSource", deleteDownloadSource); diff --git a/src/main/events/download-sources/sync-download-sources.ts b/src/main/events/download-sources/sync-download-sources.ts deleted file mode 100644 index 49380f30..00000000 --- a/src/main/events/download-sources/sync-download-sources.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { downloadSourcesWorker } from "@main/workers"; -import { registerEvent } from "../register-event"; -import type { DownloadSource } from "@types"; - -const syncDownloadSources = async ( - _event: Electron.IpcMainInvokeEvent, - downloadSources: DownloadSource[] -) => - downloadSourcesWorker.run(downloadSources, { - name: "getUpdatedRepacks", - }); - -registerEvent("syncDownloadSources", syncDownloadSources); diff --git a/src/main/events/download-sources/validate-download-source.ts b/src/main/events/download-sources/validate-download-source.ts deleted file mode 100644 index 4f43ca08..00000000 --- a/src/main/events/download-sources/validate-download-source.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { registerEvent } from "../register-event"; -import { downloadSourcesWorker } from "@main/workers"; - -const validateDownloadSource = async ( - _event: Electron.IpcMainInvokeEvent, - url: string -) => - downloadSourcesWorker.run(url, { - name: "validateDownloadSource", - }); - -registerEvent("validateDownloadSource", validateDownloadSource); diff --git a/src/main/events/helpers/search-games.ts b/src/main/events/helpers/search-games.ts index 58e9bc92..1f1fc756 100644 --- a/src/main/events/helpers/search-games.ts +++ b/src/main/events/helpers/search-games.ts @@ -1,7 +1,6 @@ import type { GameShop, CatalogueEntry, SteamGame } from "@types"; import { steamGamesWorker } from "@main/workers"; -import { RepacksManager } from "@main/services"; import { steamUrlBuilder } from "@shared"; export interface SearchGamesArgs { @@ -28,9 +27,5 @@ export const getSteamGameById = async ( if (!steamGame) return null; - const catalogueEntry = convertSteamGameToCatalogueEntry(steamGame); - - const result = RepacksManager.findRepacksForCatalogueEntry(catalogueEntry); - - return result; + return convertSteamGameToCatalogueEntry(steamGame); }; diff --git a/src/main/events/helpers/validators.ts b/src/main/events/helpers/validators.ts deleted file mode 100644 index ee36bb85..00000000 --- a/src/main/events/helpers/validators.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { z } from "zod"; - -export const downloadSourceSchema = z.object({ - name: z.string().max(255), - downloads: z.array( - z.object({ - title: z.string().max(255), - uris: z.array(z.string()), - uploadDate: z.string().max(255), - fileSize: z.string().max(255), - }) - ), -}); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 73bf38f4..f2853cbf 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -9,7 +9,6 @@ import "./catalogue/get-random-game"; import "./catalogue/search-games"; 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"; @@ -37,9 +36,8 @@ import "./user-preferences/auto-launch"; import "./autoupdater/check-for-updates"; import "./autoupdater/restart-and-install-update"; import "./user-preferences/authenticate-real-debrid"; +import "./download-sources/delete-download-source"; import "./download-sources/get-download-sources"; -import "./download-sources/validate-download-source"; -import "./download-sources/sync-download-sources"; import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; @@ -58,6 +56,7 @@ import "./profile/update-profile"; import "./profile/process-profile-image"; import "./profile/send-friend-request"; import "./profile/sync-friend-requests"; +import "./notifications/publish-new-repacks-notification"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index 13a7e5e0..b5c9a5d0 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -3,7 +3,6 @@ import { gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import type { GameShop } from "@types"; -import { getFileBase64 } from "@main/helpers"; import { steamGamesWorker } from "@main/workers"; import { createGame } from "@main/services/library-sync"; @@ -36,20 +35,12 @@ const addGameToLibrary = async ( ? steamUrlBuilder.icon(objectID, steamGame.clientIcon) : null; - await gameRepository - .insert({ - title, - iconUrl, - objectID, - shop, - }) - .then(() => { - if (iconUrl) { - getFileBase64(iconUrl).then((base64) => - gameRepository.update({ objectID }, { iconUrl: base64 }) - ); - } - }); + await gameRepository.insert({ + title, + iconUrl, + objectID, + shop, + }); } const game = await gameRepository.findOne({ where: { objectID } }); diff --git a/src/main/events/notifications/publish-new-repacks-notification.ts b/src/main/events/notifications/publish-new-repacks-notification.ts new file mode 100644 index 00000000..5230c209 --- /dev/null +++ b/src/main/events/notifications/publish-new-repacks-notification.ts @@ -0,0 +1,29 @@ +import { Notification } from "electron"; +import { registerEvent } from "../register-event"; +import { userPreferencesRepository } from "@main/repository"; +import { t } from "i18next"; + +const publishNewRepacksNotification = async ( + _event: Electron.IpcMainInvokeEvent, + newRepacksCount: number +) => { + if (newRepacksCount < 1) return; + + const userPreferences = await userPreferencesRepository.findOne({ + where: { id: 1 }, + }); + + if (userPreferences?.repackUpdatesNotificationsEnabled) { + new Notification({ + title: t("repack_list_updated", { + ns: "notifications", + }), + body: t("repack_count", { + ns: "notifications", + count: newRepacksCount, + }), + }).show(); + } +}; + +registerEvent("publishNewRepacksNotification", publishNewRepacksNotification); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 491083cb..a2c51a01 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -1,7 +1,6 @@ import { registerEvent } from "../register-event"; import type { StartGameDownloadPayload } from "@types"; -import { getFileBase64 } from "@main/helpers"; import { DownloadManager, HydraApi, logger } from "@main/services"; import { Not } from "typeorm"; @@ -60,26 +59,16 @@ const startGameDownload = async ( ? steamUrlBuilder.icon(objectID, steamGame.clientIcon) : null; - await gameRepository - .insert({ - title, - iconUrl, - objectID, - downloader, - shop, - status: "active", - downloadPath, - uri, - }) - .then((result) => { - if (iconUrl) { - getFileBase64(iconUrl).then((base64) => - gameRepository.update({ objectID }, { iconUrl: base64 }) - ); - } - - return result; - }); + await gameRepository.insert({ + title, + iconUrl, + objectID, + downloader, + shop, + status: "active", + downloadPath, + uri, + }); } const updatedGame = await gameRepository.findOne({ diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index a9dcae6c..bf29762a 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -7,16 +7,6 @@ export const getFileBuffer = async (url: string) => response.arrayBuffer().then((buffer) => Buffer.from(buffer)) ); -export const getFileBase64 = async (url: string) => - fetch(url, { method: "GET" }).then((response) => - response.arrayBuffer().then((buffer) => { - const base64 = Buffer.from(buffer).toString("base64"); - const contentType = response.headers.get("content-type"); - - return `data:${contentType};base64,${base64}`; - }) - ); - export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/main/index.ts b/src/main/index.ts index 594220c5..c9e36b2c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, net, protocol } from "electron"; +import { app, BrowserWindow, net, protocol, session } from "electron"; import { init } from "@sentry/electron/main"; import updater from "electron-updater"; import i18n from "i18next"; @@ -74,7 +74,7 @@ const runMigrations = async () => { // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(async () => { - electronApp.setAppUserModelId("site.hydralauncher.hydra"); + electronApp.setAppUserModelId("gg.hydralauncher.hydra"); protocol.handle("local", (request) => { const filePath = request.url.slice("local:".length); @@ -103,6 +103,46 @@ app.whenReady().then(async () => { WindowManager.createMainWindow(); WindowManager.createSystemTray(userPreferences?.language || "en"); + + session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { + callback({ + requestHeaders: { + ...details.requestHeaders, + "user-agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + }, + }); + }); + + session.defaultSession.webRequest.onHeadersReceived((details, callback) => { + const headers = { + "access-control-allow-origin": ["*"], + "access-control-allow-methods": ["GET, POST, PUT, DELETE, OPTIONS"], + "access-control-expose-headers": ["ETag"], + "access-control-allow-headers": [ + "Content-Type, Authorization, X-Requested-With, If-None-Match", + ], + "access-control-allow-credentials": ["true"], + }; + + if (details.method === "OPTIONS") { + callback({ + cancel: false, + responseHeaders: { + ...details.responseHeaders, + ...headers, + }, + statusLine: "HTTP/1.1 200 OK", + }); + } else { + callback({ + responseHeaders: { + ...details.responseHeaders, + ...headers, + }, + }); + } + }); }); app.on("browser-window-created", (_, window) => { diff --git a/src/main/main.ts b/src/main/main.ts index b71bab8c..7f3d6370 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,14 +1,10 @@ import { DownloadManager, PythonInstance, startMainLoop } from "./services"; import { downloadQueueRepository, - // repackRepository, userPreferencesRepository, } from "./repository"; import { UserPreferences } from "./entity"; import { RealDebridClient } from "./services/real-debrid"; -// import { fetchDownloadSourcesAndUpdate } from "./helpers"; -// import { publishNewRepacksNotifications } from "./services/notifications"; -// import { MoreThan } from "typeorm"; import { HydraApi } from "./services/hydra-api"; import { uploadGamesBatch } from "./services/library-sync"; @@ -39,18 +35,6 @@ const loadState = async (userPreferences: UserPreferences | null) => { } startMainLoop(); - - // const now = new Date(); - - // fetchDownloadSourcesAndUpdate().then(async () => { - // const newRepacksCount = await repackRepository.count({ - // where: { - // createdAt: MoreThan(now), - // }, - // }); - - // if (newRepacksCount > 0) publishNewRepacksNotifications(newRepacksCount); - // }); }; userPreferencesRepository diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 255b3871..8664062f 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -7,5 +7,4 @@ export * from "./download"; export * from "./how-long-to-beat"; export * from "./process-watcher"; export * from "./main-loop"; -export * from "./repacks-manager"; export * from "./hydra-api"; diff --git a/src/main/services/notifications.ts b/src/main/services/notifications.ts index aa43571d..81d9e582 100644 --- a/src/main/services/notifications.ts +++ b/src/main/services/notifications.ts @@ -49,24 +49,6 @@ export const publishDownloadCompleteNotification = async (game: Game) => { } }; -export const publishNewRepacksNotifications = async (count: number) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); - - if (userPreferences?.repackUpdatesNotificationsEnabled) { - new Notification({ - title: t("repack_list_updated", { - ns: "notifications", - }), - body: t("repack_count", { - ns: "notifications", - count: count, - }), - }).show(); - } -}; - export const publishNotificationUpdateReadyToInstall = async ( version: string ) => { diff --git a/src/main/services/repacks-manager.ts b/src/main/services/repacks-manager.ts deleted file mode 100644 index 933d7431..00000000 --- a/src/main/services/repacks-manager.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { repackRepository } from "@main/repository"; -import { formatName } from "@shared"; -import { CatalogueEntry, GameRepack } from "@types"; -import flexSearch from "flexsearch"; - -export class RepacksManager { - public static repacks: GameRepack[] = []; - private static repacksIndex = new flexSearch.Index(); - - public static async updateRepacks() { - this.repacks = await repackRepository - .find({ - order: { - createdAt: "DESC", - }, - }) - .then((repacks) => - repacks.map((repack) => { - const uris: string[] = []; - const magnet = repack?.magnet; - - if (magnet) uris.push(magnet); - - return { - ...repack, - uris: [...uris, ...JSON.parse(repack.uris)], - }; - }) - ); - - for (let i = 0; i < this.repacks.length; i++) { - this.repacksIndex.remove(i); - } - - this.repacksIndex = new flexSearch.Index(); - - for (let i = 0; i < this.repacks.length; i++) { - const repack = this.repacks[i]; - - const formattedTitle = formatName(repack.title); - - this.repacksIndex.add(i, formattedTitle); - } - } - - public static search(options: flexSearch.SearchOptions) { - return this.repacksIndex - .search({ ...options, query: formatName(options.query ?? "") }) - .map((index) => this.repacks[index]); - } - - public static findRepacksForCatalogueEntry(entry: CatalogueEntry) { - const repacks = this.search({ query: formatName(entry.title) }); - return { ...entry, repacks }; - } - - public static findRepacksForCatalogueEntries(entries: CatalogueEntry[]) { - return entries.map((entry) => { - const repacks = this.search({ query: formatName(entry.title) }); - return { ...entry, repacks }; - }); - } -} diff --git a/src/main/workers/download-sources.worker.ts b/src/main/workers/download-sources.worker.ts deleted file mode 100644 index c660ad00..00000000 --- a/src/main/workers/download-sources.worker.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { downloadSourceSchema } from "@main/events/helpers/validators"; -import { DownloadSourceStatus } from "@shared"; -import type { DownloadSource } from "@types"; -import axios, { AxiosError, AxiosHeaders } from "axios"; -import { z } from "zod"; - -export type DownloadSourceResponse = z.infer & { - etag: string | null; - status: DownloadSourceStatus; -}; - -export const getUpdatedRepacks = async (downloadSources: DownloadSource[]) => { - const results: DownloadSourceResponse[] = []; - - for (const downloadSource of downloadSources) { - const headers = new AxiosHeaders(); - - if (downloadSource.etag) { - headers.set("If-None-Match", downloadSource.etag); - } - - try { - const response = await axios.get(downloadSource.url, { - headers, - }); - - const source = downloadSourceSchema.parse(response.data); - - results.push({ - ...downloadSource, - downloads: source.downloads, - etag: response.headers["etag"], - status: DownloadSourceStatus.UpToDate, - }); - } catch (err: unknown) { - const isNotModified = (err as AxiosError).response?.status === 304; - - results.push({ - ...downloadSource, - downloads: [], - etag: null, - status: isNotModified - ? DownloadSourceStatus.UpToDate - : DownloadSourceStatus.Errored, - }); - } - } - - return results; -}; - -export const validateDownloadSource = async (url: string) => { - const response = await axios.get(url); - - return { - ...downloadSourceSchema.parse(response.data), - etag: response.headers["etag"], - }; -}; diff --git a/src/main/workers/index.ts b/src/main/workers/index.ts index 799ed2ef..eded03a3 100644 --- a/src/main/workers/index.ts +++ b/src/main/workers/index.ts @@ -1,6 +1,5 @@ import path from "node:path"; import steamGamesWorkerPath from "./steam-games.worker?modulePath"; -import downloadSourcesWorkerPath from "./download-sources.worker?modulePath"; import Piscina from "piscina"; @@ -13,7 +12,3 @@ export const steamGamesWorker = new Piscina({ }, maxThreads: 1, }); - -export const downloadSourcesWorker = new Piscina({ - filename: downloadSourcesWorkerPath, -}); diff --git a/src/preload/index.ts b/src/preload/index.ts index 4d7b7183..5b35958b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -11,7 +11,6 @@ import type { GameRunning, FriendRequestAction, UpdateProfileRequest, - DownloadSource, } from "@types"; import type { CatalogueCategory } from "@shared"; @@ -50,8 +49,6 @@ 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"), @@ -63,10 +60,8 @@ contextBridge.exposeInMainWorld("electron", { /* Download sources */ getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), - validateDownloadSource: (url: string) => - ipcRenderer.invoke("validateDownloadSource", url), - syncDownloadSources: (downloadSources: DownloadSource[]) => - ipcRenderer.invoke("syncDownloadSources", downloadSources), + deleteDownloadSource: (id: number) => + ipcRenderer.invoke("deleteDownloadSource", id), /* Library */ addGameToLibrary: (objectID: string, title: string, shop: GameShop) => @@ -182,4 +177,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.on("on-signout", listener); return () => ipcRenderer.removeListener("on-signout", listener); }, + + /* Notifications */ + publishNewRepacksNotification: (newRepacksCount: number) => + ipcRenderer.invoke("publishNewRepacksNotification", newRepacksCount), }); diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index f7a24a46..37e63154 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -26,7 +26,7 @@ import { } from "@renderer/features"; import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; -// import { migrationWorker } from "./workers"; +import { downloadSourcesWorker } from "./workers"; import { repacksContext } from "./context"; export interface AppProps { @@ -39,6 +39,8 @@ export function App() { const { t } = useTranslation("app"); + const downloadSourceMigrationLock = useRef(false); + const { clearDownload, setLastPacket } = useDownload(); const { indexRepacks } = useContext(repacksContext); @@ -211,22 +213,46 @@ export function App() { }, [dispatch, draggingDisabled]); useEffect(() => { - // window.electron.getRepacks().then((repacks) => { - // migrationWorker.postMessage(["MIGRATE_REPACKS", repacks]); - // }); - // window.electron.getDownloadSources().then((downloadSources) => { - // migrationWorker.postMessage([ - // "MIGRATE_DOWNLOAD_SOURCES", - // downloadSources, - // ]); - // }); - // migrationWorker.onmessage = ( - // event: MessageEvent<"MIGRATE_REPACKS_COMPLETE"> - // ) => { - // if (event.data === "MIGRATE_REPACKS_COMPLETE") { - // indexRepacks(); - // } - // }; + if (downloadSourceMigrationLock.current) return; + + downloadSourceMigrationLock.current = true; + + window.electron.getDownloadSources().then(async (downloadSources) => { + if (!downloadSources.length) { + const id = crypto.randomUUID(); + const channel = new BroadcastChannel(`download_sources:sync:${id}`); + + channel.onmessage = (event: MessageEvent) => { + const newRepacksCount = event.data; + window.electron.publishNewRepacksNotification(newRepacksCount); + }; + + downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); + } + + for (const downloadSource of downloadSources) { + const channel = new BroadcastChannel( + `download_sources:import:${downloadSource.url}` + ); + await new Promise((resolve) => { + downloadSourcesWorker.postMessage([ + "IMPORT_DOWNLOAD_SOURCE", + downloadSource.url, + ]); + + channel.onmessage = () => { + window.electron.deleteDownloadSource(downloadSource.id).then(() => { + resolve(true); + }); + + indexRepacks(); + channel.close(); + }; + }).catch(() => channel.close()); + } + + downloadSourceMigrationLock.current = false; + }); }, [indexRepacks]); const handleToastClose = useCallback(() => { diff --git a/src/renderer/src/context/repacks/repacks.context.tsx b/src/renderer/src/context/repacks/repacks.context.tsx index c59d5792..cddbb209 100644 --- a/src/renderer/src/context/repacks/repacks.context.tsx +++ b/src/renderer/src/context/repacks/repacks.context.tsx @@ -33,6 +33,7 @@ export function RepacksContextProvider({ children }: RepacksContextProps) { const channel = new BroadcastChannel(`repacks:search:${channelId}`); channel.onmessage = (event: MessageEvent) => { resolve(event.data); + channel.close(); }; return []; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 28c5caf7..2d1d8663 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -25,7 +25,6 @@ import type { UserStats, UserDetails, FriendRequestSync, - DownloadSourceValidationResult, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -66,8 +65,6 @@ declare global { searchGameRepacks: (query: string) => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; getTrendingGames: () => Promise; - /* Meant for Dexie migration */ - getRepacks: () => Promise; /* Library */ addGameToLibrary: ( @@ -105,10 +102,7 @@ declare global { /* Download sources */ getDownloadSources: () => Promise; - validateDownloadSource: ( - url: string - ) => Promise; - syncDownloadSources: (downloadSources: DownloadSource[]) => Promise; + deleteDownloadSource: (id: number) => Promise; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; @@ -172,6 +166,9 @@ declare global { action: FriendRequestAction ) => Promise; sendFriendRequest: (userId: string) => Promise; + + /* Notifications */ + publishNewRepacksNotification: (newRepacksCount: number) => Promise; } interface Window { 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 8e34cbe2..5ec22827 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -67,8 +67,21 @@ export function AddDownloadSourceModal({ return; } - const result = await window.electron.validateDownloadSource(values.url); - setValidationResult(result); + downloadSourcesWorker.postMessage([ + "VALIDATE_DOWNLOAD_SOURCE", + values.url, + ]); + + const channel = new BroadcastChannel( + `download_sources:validate:${values.url}` + ); + + channel.onmessage = ( + event: MessageEvent + ) => { + setValidationResult(event.data); + channel.close(); + }; setUrl(values.url); }, @@ -93,16 +106,14 @@ export function AddDownloadSourceModal({ if (validationResult) { const channel = new BroadcastChannel(`download_sources:import:${url}`); - downloadSourcesWorker.postMessage([ - "IMPORT_DOWNLOAD_SOURCE", - { ...validationResult, url }, - ]); + downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]); channel.onmessage = () => { setIsLoading(false); onClose(); onAddDownloadSource(); + channel.close(); }; } }; @@ -159,9 +170,9 @@ export function AddDownloadSourceModal({

{validationResult?.name}

{t("found_download_option", { - count: validationResult?.downloads.length, + count: validationResult?.downloadCount, countFormatted: - validationResult?.downloads.length.toLocaleString(), + validationResult?.downloadCount.toLocaleString(), })} diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index 4f70ff6b..d2f45329 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -59,6 +59,7 @@ export function SettingsDownloadSources() { getDownloadSources(); indexRepacks(); setIsRemovingDownloadSource(false); + channel.close(); }; }; @@ -71,15 +72,17 @@ export function SettingsDownloadSources() { const syncDownloadSources = async () => { setIsSyncingDownloadSources(true); - window.electron - .syncDownloadSources(downloadSources) - .then(() => { - showSuccessToast(t("download_sources_synced")); - getDownloadSources(); - }) - .finally(() => { - setIsSyncingDownloadSources(false); - }); + const id = crypto.randomUUID(); + const channel = new BroadcastChannel(`download_sources:sync:${id}`); + + downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); + + channel.onmessage = () => { + showSuccessToast(t("download_sources_synced")); + getDownloadSources(); + setIsSyncingDownloadSources(false); + channel.close(); + }; }; const statusTitle = { diff --git a/src/renderer/src/workers/download-sources.worker.ts b/src/renderer/src/workers/download-sources.worker.ts index 8ad63a04..29ab7d87 100644 --- a/src/renderer/src/workers/download-sources.worker.ts +++ b/src/renderer/src/workers/download-sources.worker.ts @@ -1,16 +1,45 @@ import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; + +import { z } from "zod"; +import axios, { AxiosError, AxiosHeaders } from "axios"; import { DownloadSourceStatus } from "@shared"; -import type { DownloadSourceValidationResult } from "@types"; + +export const downloadSourceSchema = z.object({ + name: z.string().max(255), + downloads: z.array( + z.object({ + title: z.string().max(255), + uris: z.array(z.string()), + uploadDate: z.string().max(255), + fileSize: z.string().max(255), + }) + ), +}); type Payload = - | ["IMPORT_DOWNLOAD_SOURCE", DownloadSourceValidationResult & { url: string }] - | ["DELETE_DOWNLOAD_SOURCE", number]; - -db.open(); + | ["IMPORT_DOWNLOAD_SOURCE", string] + | ["DELETE_DOWNLOAD_SOURCE", number] + | ["VALIDATE_DOWNLOAD_SOURCE", string] + | ["SYNC_DOWNLOAD_SOURCES", string]; self.onmessage = async (event: MessageEvent) => { const [type, data] = event.data; + if (type === "VALIDATE_DOWNLOAD_SOURCE") { + const response = + await axios.get>(data); + + const { name } = downloadSourceSchema.parse(response.data); + + const channel = new BroadcastChannel(`download_sources:validate:${data}`); + + channel.postMessage({ + name, + etag: response.headers["etag"], + downloadCount: response.data.downloads.length, + }); + } + if (type === "DELETE_DOWNLOAD_SOURCE") { await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { await repacksTable.where({ downloadSourceId: data }).delete(); @@ -23,28 +52,29 @@ self.onmessage = async (event: MessageEvent) => { } if (type === "IMPORT_DOWNLOAD_SOURCE") { - const result = data; + const response = + await axios.get>(data); - await db.transaction("rw", downloadSourcesTable, repacksTable, async () => { + await db.transaction("rw", repacksTable, downloadSourcesTable, async () => { const now = new Date(); const id = await downloadSourcesTable.add({ - url: result.url, - name: result.name, - etag: result.etag, + url: data, + name: response.data.name, + etag: response.headers["etag"], status: DownloadSourceStatus.UpToDate, - downloadCount: result.downloads.length, + downloadCount: response.data.downloads.length, createdAt: now, updatedAt: now, }); const downloadSource = await downloadSourcesTable.get(id); - const repacks = result.downloads.map((download) => ({ + const repacks = response.data.downloads.map((download) => ({ title: download.title, uris: download.uris, fileSize: download.fileSize, - repacker: result.name, + repacker: response.data.name, uploadDate: download.uploadDate, downloadSourceId: downloadSource!.id, createdAt: now, @@ -54,10 +84,82 @@ self.onmessage = async (event: MessageEvent) => { await repacksTable.bulkAdd(repacks); }); - const channel = new BroadcastChannel( - `download_sources:import:${result.url}` - ); - + const channel = new BroadcastChannel(`download_sources:import:${data}`); channel.postMessage(true); } + + if (type === "SYNC_DOWNLOAD_SOURCES") { + const channel = new BroadcastChannel(`download_sources:sync:${data}`); + let newRepacksCount = 0; + + try { + const downloadSources = await downloadSourcesTable.toArray(); + const existingRepacks = await repacksTable.toArray(); + + for (const downloadSource of downloadSources) { + const headers = new AxiosHeaders(); + + if (downloadSource.etag) { + headers.set("If-None-Match", downloadSource.etag); + } + + try { + const response = await axios.get(downloadSource.url, { + headers, + }); + + const source = downloadSourceSchema.parse(response.data); + + await db.transaction( + "rw", + repacksTable, + downloadSourcesTable, + async () => { + await downloadSourcesTable.update(downloadSource.id, { + etag: response.headers["etag"], + downloadCount: source.downloads.length, + status: DownloadSourceStatus.UpToDate, + }); + + const now = new Date(); + + const repacks = source.downloads + .filter( + (download) => + !existingRepacks.some( + (repack) => repack.title === download.title + ) + ) + .map((download) => ({ + title: download.title, + uris: download.uris, + fileSize: download.fileSize, + repacker: source.name, + uploadDate: download.uploadDate, + downloadSourceId: downloadSource.id, + createdAt: now, + updatedAt: now, + })); + + newRepacksCount += repacks.length; + + await repacksTable.bulkAdd(repacks); + } + ); + } catch (err: unknown) { + const isNotModified = (err as AxiosError).response?.status === 304; + + await downloadSourcesTable.update(downloadSource.id, { + status: isNotModified + ? DownloadSourceStatus.UpToDate + : DownloadSourceStatus.Errored, + }); + } + } + + channel.postMessage(newRepacksCount); + } catch (err) { + channel.postMessage(-1); + } + } }; diff --git a/src/renderer/src/workers/index.ts b/src/renderer/src/workers/index.ts index 6cd430c6..b8141a8f 100644 --- a/src/renderer/src/workers/index.ts +++ b/src/renderer/src/workers/index.ts @@ -1,7 +1,5 @@ -import MigrationWorker from "./migration.worker?worker"; import RepacksWorker from "./repacks.worker?worker"; import DownloadSourcesWorker from "./download-sources.worker?worker"; -export const migrationWorker = new MigrationWorker(); export const repacksWorker = new RepacksWorker(); export const downloadSourcesWorker = new DownloadSourcesWorker(); diff --git a/src/renderer/src/workers/migration.worker.ts b/src/renderer/src/workers/migration.worker.ts deleted file mode 100644 index e6a66b2a..00000000 --- a/src/renderer/src/workers/migration.worker.ts +++ /dev/null @@ -1,35 +0,0 @@ -// import { db, downloadSourcesTable, repacksTable } from "@renderer/dexie"; -import { DownloadSource, GameRepack } from "@types"; - -export type Payload = [DownloadSource[], GameRepack[]]; - -self.onmessage = async (_event: MessageEvent) => { - // const [downloadSources, gameRepacks] = event.data; - // const downloadSourcesCount = await downloadSourcesTable.count(); - // if (downloadSources.length > downloadSourcesCount) { - // await db.transaction( - // "rw", - // downloadSourcesTable, - // repacksTable, - // async () => {} - // ); - // } - // 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.map((repack) => ({ ...repack, uris: JSON.stringify(repack.uris) })) - // ); - // } - // self.postMessage("MIGRATE_REPACKS_COMPLETE"); - // } -}; diff --git a/src/types/index.ts b/src/types/index.ts index 9e6f7def..762c611a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -233,8 +233,8 @@ export interface DownloadSourceDownload { export interface DownloadSourceValidationResult { name: string; - downloads: DownloadSourceDownload[]; etag: string; + downloadCount: number; } export interface DownloadSource { From 43e9919b6b437f8ed412c9a56e0e17c1fbf9a4a7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Fri, 27 Sep 2024 03:39:42 +0100 Subject: [PATCH 2/2] fix: adding direct comparison last downloaded option --- .../game-details/modals/repacks-modal.tsx | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 0d1b9c1d..9d8a1a11 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -1,6 +1,5 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import parseTorrent from "parse-torrent"; import { Badge, Button, Modal, TextField } from "@renderer/components"; import type { GameRepack } from "@types"; @@ -33,8 +32,6 @@ export function RepacksModal({ const [repack, setRepack] = useState(null); const [showSelectFolderModal, setShowSelectFolderModal] = useState(false); - const [infoHash, setInfoHash] = useState(null); - const { repacks, game } = useContext(gameDetailsContext); const { t } = useTranslation("game_details"); @@ -43,18 +40,9 @@ export function RepacksModal({ return orderBy(repacks, (repack) => repack.uploadDate, "desc"); }, [repacks]); - const getInfoHash = useCallback(async () => { - if (game?.uri?.startsWith("magnet:")) { - const torrent = await parseTorrent(game?.uri ?? ""); - if (torrent.infoHash) setInfoHash(torrent.infoHash); - } - }, [game]); - useEffect(() => { setFilteredRepacks(sortedRepacks); - - if (game?.uri) getInfoHash(); - }, [sortedRepacks, visible, game, getInfoHash]); + }, [sortedRepacks, visible, game]); const handleRepackClick = (repack: GameRepack) => { setRepack(repack); @@ -77,9 +65,6 @@ export function RepacksModal({ }; const checkIfLastDownloadedOption = (repack: GameRepack) => { - if (infoHash) return repack.uris.some((uri) => uri.includes(infoHash)); - if (!game?.uri) return false; - return repack.uris.some((uri) => uri.includes(game?.uri ?? "")); };