diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6da066af..f76a27a1 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { }, ], "@typescript-eslint/no-explicit-any": "warn", + "no-async-promise-executor": "off", "prettier/prettier": [ "error", { diff --git a/src/main/events/download-sources/create-download-source.ts b/src/main/events/download-sources/create-download-source.ts new file mode 100644 index 00000000..82b5dd4d --- /dev/null +++ b/src/main/events/download-sources/create-download-source.ts @@ -0,0 +1,13 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const createDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + url: string +) => { + return HydraApi.post("/profile/download-sources", { + url, + }); +}; + +registerEvent("createDownloadSource", createDownloadSource); diff --git a/src/main/events/download-sources/get-download-sources.ts b/src/main/events/download-sources/get-download-sources.ts new file mode 100644 index 00000000..bbebd06c --- /dev/null +++ b/src/main/events/download-sources/get-download-sources.ts @@ -0,0 +1,8 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const getDownloadSources = async (_event: Electron.IpcMainInvokeEvent) => { + return HydraApi.get("/profile/download-sources"); +}; + +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 new file mode 100644 index 00000000..bcc66998 --- /dev/null +++ b/src/main/events/download-sources/remove-download-source.ts @@ -0,0 +1,18 @@ +import { HydraApi } from "@main/services"; +import { registerEvent } from "../register-event"; + +const removeDownloadSource = async ( + _event: Electron.IpcMainInvokeEvent, + url?: string, + removeAll = false +) => { + const params = new URLSearchParams({ + all: removeAll.toString(), + }); + + if (url) params.set("url", url); + + return HydraApi.delete(`/profile/download-sources?${params.toString()}`); +}; + +registerEvent("removeDownloadSource", removeDownloadSource); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index bcbbb43d..d49ddc71 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -90,6 +90,9 @@ import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; import "./themes/close-editor-window"; import "./themes/toggle-custom-theme"; +import "./download-sources/create-download-source"; +import "./download-sources/remove-download-source"; +import "./download-sources/get-download-sources"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/preload/index.ts b/src/preload/index.ts index b49595a7..3ce2902c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -100,6 +100,11 @@ contextBridge.exposeInMainWorld("electron", { /* Download sources */ putDownloadSource: (objectIds: string[]) => ipcRenderer.invoke("putDownloadSource", objectIds), + createDownloadSource: (url: string) => + ipcRenderer.invoke("createDownloadSource", url), + removeDownloadSource: (url: string, removeAll?: boolean) => + ipcRenderer.invoke("removeDownloadSource", url, removeAll), + getDownloadSources: () => ipcRenderer.invoke("getDownloadSources"), /* Library */ toggleAutomaticCloudSync: ( diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index f9bd645e..9d0b137e 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -136,6 +136,61 @@ export function App() { }); }, [fetchUserDetails, updateUserDetails, dispatch]); + const syncDownloadSources = useCallback(async () => { + const downloadSources = await window.electron.getDownloadSources(); + + await Promise.allSettled( + downloadSources.map(async (source) => { + return new Promise(async (resolve) => { + const existingDownloadSource = await downloadSourcesTable + .where({ url: source.url }) + .first(); + + if (!existingDownloadSource) { + const channel = new BroadcastChannel( + `download_sources:import:${source.url}` + ); + + downloadSourcesWorker.postMessage([ + "IMPORT_DOWNLOAD_SOURCE", + source.url, + ]); + + channel.onmessage = () => { + resolve(true); + channel.close(); + }; + } + }); + }) + ); + + updateRepacks(); + + const id = crypto.randomUUID(); + const channel = new BroadcastChannel(`download_sources:sync:${id}`); + + channel.onmessage = async (event: MessageEvent) => { + const newRepacksCount = event.data; + window.electron.publishNewRepacksNotification(newRepacksCount); + updateRepacks(); + + const downloadSources = await downloadSourcesTable.toArray(); + + downloadSources + .filter((source) => !source.fingerprint) + .forEach(async (downloadSource) => { + const { fingerprint } = await window.electron.putDownloadSource( + downloadSource.objectIds + ); + + downloadSourcesTable.update(downloadSource.id, { fingerprint }); + }); + }; + + downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); + }, [updateRepacks]); + const onSignIn = useCallback(() => { fetchUserDetails().then((response) => { if (response) { @@ -144,7 +199,15 @@ export function App() { showSuccessToast(t("successfully_signed_in")); } }); - }, [fetchUserDetails, t, showSuccessToast, updateUserDetails]); + + syncDownloadSources(); + }, [ + fetchUserDetails, + t, + showSuccessToast, + updateUserDetails, + syncDownloadSources, + ]); useEffect(() => { const unsubscribe = window.electron.onSyncFriendRequests((result) => { @@ -212,31 +275,8 @@ export function App() { }, [dispatch, draggingDisabled]); useEffect(() => { - updateRepacks(); - - const id = crypto.randomUUID(); - const channel = new BroadcastChannel(`download_sources:sync:${id}`); - - channel.onmessage = async (event: MessageEvent) => { - const newRepacksCount = event.data; - window.electron.publishNewRepacksNotification(newRepacksCount); - updateRepacks(); - - const downloadSources = await downloadSourcesTable.toArray(); - - downloadSources - .filter((source) => !source.fingerprint) - .forEach(async (downloadSource) => { - const { fingerprint } = await window.electron.putDownloadSource( - downloadSource.objectIds - ); - - downloadSourcesTable.update(downloadSource.id, { fingerprint }); - }); - }; - - downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); - }, [updateRepacks]); + syncDownloadSources(); + }, [syncDownloadSources]); useEffect(() => { const loadAndApplyTheme = async () => { diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 03f309cb..7ebebfaa 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -165,6 +165,11 @@ declare global { putDownloadSource: ( objectIds: string[] ) => Promise<{ fingerprint: string }>; + createDownloadSource: (url: string) => Promise; + removeDownloadSource: (url: string, removeAll?: boolean) => Promise; + getDownloadSources: () => Promise< + Pick[] + >; /* Hardware */ getDiskFreeSpace: (path: string) => Promise; 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 948dc721..075203a4 100644 --- a/src/renderer/src/pages/settings/add-download-source-modal.tsx +++ b/src/renderer/src/pages/settings/add-download-source-modal.tsx @@ -119,7 +119,8 @@ export function AddDownloadSourceModal({ downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]); - channel.onmessage = async () => { + channel.onmessage = () => { + window.electron.createDownloadSource(url); setIsLoading(false); putDownloadSource(); diff --git a/src/renderer/src/pages/settings/settings-download-sources.tsx b/src/renderer/src/pages/settings/settings-download-sources.tsx index cee71ad0..fa30dfa1 100644 --- a/src/renderer/src/pages/settings/settings-download-sources.tsx +++ b/src/renderer/src/pages/settings/settings-download-sources.tsx @@ -70,14 +70,20 @@ export function SettingsDownloadSources() { if (sourceUrl) setShowAddDownloadSourceModal(true); }, [sourceUrl]); - const handleRemoveSource = (id: number) => { + const handleRemoveSource = (downloadSource: DownloadSource) => { setIsRemovingDownloadSource(true); - const channel = new BroadcastChannel(`download_sources:delete:${id}`); + const channel = new BroadcastChannel( + `download_sources:delete:${downloadSource.id}` + ); - downloadSourcesWorker.postMessage(["DELETE_DOWNLOAD_SOURCE", id]); + downloadSourcesWorker.postMessage([ + "DELETE_DOWNLOAD_SOURCE", + downloadSource.id, + ]); channel.onmessage = () => { showSuccessToast(t("removed_download_source")); + window.electron.removeDownloadSource(downloadSource.url); getDownloadSources(); setIsRemovingDownloadSource(false); @@ -96,7 +102,7 @@ export function SettingsDownloadSources() { channel.onmessage = () => { showSuccessToast(t("removed_download_sources")); - + window.electron.removeDownloadSource("", true); getDownloadSources(); setIsRemovingDownloadSource(false); setShowConfirmationDeleteAllSourcesModal(false); @@ -253,7 +259,7 @@ export function SettingsDownloadSources() {