mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-25 11:51:02 +00:00
feat: sync download sources
This commit is contained in:
@@ -19,6 +19,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
|
"no-async-promise-executor": "off",
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
13
src/main/events/download-sources/create-download-source.ts
Normal file
13
src/main/events/download-sources/create-download-source.ts
Normal file
@@ -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);
|
||||||
8
src/main/events/download-sources/get-download-sources.ts
Normal file
8
src/main/events/download-sources/get-download-sources.ts
Normal file
@@ -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);
|
||||||
18
src/main/events/download-sources/remove-download-source.ts
Normal file
18
src/main/events/download-sources/remove-download-source.ts
Normal file
@@ -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);
|
||||||
@@ -90,6 +90,9 @@ import "./themes/get-custom-theme-by-id";
|
|||||||
import "./themes/get-active-custom-theme";
|
import "./themes/get-active-custom-theme";
|
||||||
import "./themes/close-editor-window";
|
import "./themes/close-editor-window";
|
||||||
import "./themes/toggle-custom-theme";
|
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";
|
import { isPortableVersion } from "@main/helpers";
|
||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
/* Download sources */
|
/* Download sources */
|
||||||
putDownloadSource: (objectIds: string[]) =>
|
putDownloadSource: (objectIds: string[]) =>
|
||||||
ipcRenderer.invoke("putDownloadSource", objectIds),
|
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 */
|
/* Library */
|
||||||
toggleAutomaticCloudSync: (
|
toggleAutomaticCloudSync: (
|
||||||
|
|||||||
@@ -136,6 +136,61 @@ export function App() {
|
|||||||
});
|
});
|
||||||
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
}, [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<number>) => {
|
||||||
|
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(() => {
|
const onSignIn = useCallback(() => {
|
||||||
fetchUserDetails().then((response) => {
|
fetchUserDetails().then((response) => {
|
||||||
if (response) {
|
if (response) {
|
||||||
@@ -144,7 +199,15 @@ export function App() {
|
|||||||
showSuccessToast(t("successfully_signed_in"));
|
showSuccessToast(t("successfully_signed_in"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [fetchUserDetails, t, showSuccessToast, updateUserDetails]);
|
|
||||||
|
syncDownloadSources();
|
||||||
|
}, [
|
||||||
|
fetchUserDetails,
|
||||||
|
t,
|
||||||
|
showSuccessToast,
|
||||||
|
updateUserDetails,
|
||||||
|
syncDownloadSources,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = window.electron.onSyncFriendRequests((result) => {
|
const unsubscribe = window.electron.onSyncFriendRequests((result) => {
|
||||||
@@ -212,31 +275,8 @@ export function App() {
|
|||||||
}, [dispatch, draggingDisabled]);
|
}, [dispatch, draggingDisabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateRepacks();
|
syncDownloadSources();
|
||||||
|
}, [syncDownloadSources]);
|
||||||
const id = crypto.randomUUID();
|
|
||||||
const channel = new BroadcastChannel(`download_sources:sync:${id}`);
|
|
||||||
|
|
||||||
channel.onmessage = async (event: MessageEvent<number>) => {
|
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadAndApplyTheme = async () => {
|
const loadAndApplyTheme = async () => {
|
||||||
|
|||||||
5
src/renderer/src/declaration.d.ts
vendored
5
src/renderer/src/declaration.d.ts
vendored
@@ -165,6 +165,11 @@ declare global {
|
|||||||
putDownloadSource: (
|
putDownloadSource: (
|
||||||
objectIds: string[]
|
objectIds: string[]
|
||||||
) => Promise<{ fingerprint: string }>;
|
) => Promise<{ fingerprint: string }>;
|
||||||
|
createDownloadSource: (url: string) => Promise<void>;
|
||||||
|
removeDownloadSource: (url: string, removeAll?: boolean) => Promise<void>;
|
||||||
|
getDownloadSources: () => Promise<
|
||||||
|
Pick<DownloadSource, "url" | "createdAt" | "updatedAt">[]
|
||||||
|
>;
|
||||||
|
|
||||||
/* Hardware */
|
/* Hardware */
|
||||||
getDiskFreeSpace: (path: string) => Promise<disk.DiskUsage>;
|
getDiskFreeSpace: (path: string) => Promise<disk.DiskUsage>;
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ export function AddDownloadSourceModal({
|
|||||||
|
|
||||||
downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]);
|
downloadSourcesWorker.postMessage(["IMPORT_DOWNLOAD_SOURCE", url]);
|
||||||
|
|
||||||
channel.onmessage = async () => {
|
channel.onmessage = () => {
|
||||||
|
window.electron.createDownloadSource(url);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
putDownloadSource();
|
putDownloadSource();
|
||||||
|
|||||||
@@ -70,14 +70,20 @@ export function SettingsDownloadSources() {
|
|||||||
if (sourceUrl) setShowAddDownloadSourceModal(true);
|
if (sourceUrl) setShowAddDownloadSourceModal(true);
|
||||||
}, [sourceUrl]);
|
}, [sourceUrl]);
|
||||||
|
|
||||||
const handleRemoveSource = (id: number) => {
|
const handleRemoveSource = (downloadSource: DownloadSource) => {
|
||||||
setIsRemovingDownloadSource(true);
|
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 = () => {
|
channel.onmessage = () => {
|
||||||
showSuccessToast(t("removed_download_source"));
|
showSuccessToast(t("removed_download_source"));
|
||||||
|
window.electron.removeDownloadSource(downloadSource.url);
|
||||||
|
|
||||||
getDownloadSources();
|
getDownloadSources();
|
||||||
setIsRemovingDownloadSource(false);
|
setIsRemovingDownloadSource(false);
|
||||||
@@ -96,7 +102,7 @@ export function SettingsDownloadSources() {
|
|||||||
|
|
||||||
channel.onmessage = () => {
|
channel.onmessage = () => {
|
||||||
showSuccessToast(t("removed_download_sources"));
|
showSuccessToast(t("removed_download_sources"));
|
||||||
|
window.electron.removeDownloadSource("", true);
|
||||||
getDownloadSources();
|
getDownloadSources();
|
||||||
setIsRemovingDownloadSource(false);
|
setIsRemovingDownloadSource(false);
|
||||||
setShowConfirmationDeleteAllSourcesModal(false);
|
setShowConfirmationDeleteAllSourcesModal(false);
|
||||||
@@ -253,7 +259,7 @@ export function SettingsDownloadSources() {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
onClick={() => handleRemoveSource(downloadSource.id)}
|
onClick={() => handleRemoveSource(downloadSource)}
|
||||||
disabled={isRemovingDownloadSource}
|
disabled={isRemovingDownloadSource}
|
||||||
>
|
>
|
||||||
<NoEntryIcon />
|
<NoEntryIcon />
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ export interface GameRepack {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DownloadSource {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
repackCount: number;
|
||||||
|
status: DownloadSourceStatus;
|
||||||
|
objectIds: string[];
|
||||||
|
downloadCount: number;
|
||||||
|
fingerprint: string;
|
||||||
|
etag: string | null;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export type ShopDetails = SteamAppDetails & {
|
export type ShopDetails = SteamAppDetails & {
|
||||||
objectId: string;
|
objectId: string;
|
||||||
};
|
};
|
||||||
@@ -197,20 +211,6 @@ export interface DownloadSourceValidationResult {
|
|||||||
downloadCount: number;
|
downloadCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadSource {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
repackCount: number;
|
|
||||||
status: DownloadSourceStatus;
|
|
||||||
objectIds: string[];
|
|
||||||
downloadCount: number;
|
|
||||||
fingerprint: string;
|
|
||||||
etag: string | null;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GameStats {
|
export interface GameStats {
|
||||||
downloadCount: number;
|
downloadCount: number;
|
||||||
playerCount: number;
|
playerCount: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user