feat: adding notification when all repacks are migrated

This commit is contained in:
Chubby Granny Chaser 2024-09-27 03:27:02 +01:00
parent d88e06e289
commit 17febcd88a
No known key found for this signature in database
31 changed files with 308 additions and 390 deletions

View file

@ -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<number>) => {
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(() => {

View file

@ -33,6 +33,7 @@ export function RepacksContextProvider({ children }: RepacksContextProps) {
const channel = new BroadcastChannel(`repacks:search:${channelId}`);
channel.onmessage = (event: MessageEvent<GameRepack[]>) => {
resolve(event.data);
channel.close();
};
return [];

View file

@ -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<GameRepack[]>;
getGameStats: (objectId: string, shop: GameShop) => Promise<GameStats>;
getTrendingGames: () => Promise<TrendingGame[]>;
/* Meant for Dexie migration */
getRepacks: () => Promise<GameRepack[]>;
/* Library */
addGameToLibrary: (
@ -105,10 +102,7 @@ declare global {
/* Download sources */
getDownloadSources: () => Promise<DownloadSource[]>;
validateDownloadSource: (
url: string
) => Promise<DownloadSourceValidationResult>;
syncDownloadSources: (downloadSources: DownloadSource[]) => Promise<void>;
deleteDownloadSource: (id: number) => Promise<void>;
/* Hardware */
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
@ -172,6 +166,9 @@ declare global {
action: FriendRequestAction
) => Promise<void>;
sendFriendRequest: (userId: string) => Promise<void>;
/* Notifications */
publishNewRepacksNotification: (newRepacksCount: number) => Promise<void>;
}
interface Window {

View file

@ -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<DownloadSourceValidationResult>
) => {
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({
<h4>{validationResult?.name}</h4>
<small>
{t("found_download_option", {
count: validationResult?.downloads.length,
count: validationResult?.downloadCount,
countFormatted:
validationResult?.downloads.length.toLocaleString(),
validationResult?.downloadCount.toLocaleString(),
})}
</small>
</div>

View file

@ -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 = {

View file

@ -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<Payload>) => {
const [type, data] = event.data;
if (type === "VALIDATE_DOWNLOAD_SOURCE") {
const response =
await axios.get<z.infer<typeof downloadSourceSchema>>(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<Payload>) => {
}
if (type === "IMPORT_DOWNLOAD_SOURCE") {
const result = data;
const response =
await axios.get<z.infer<typeof downloadSourceSchema>>(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<Payload>) => {
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);
}
}
};

View file

@ -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();

View file

@ -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<Payload>) => {
// 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");
// }
};