fix: fixing bug with sources

This commit is contained in:
Chubby Granny Chaser
2025-10-14 15:49:02 +01:00
parent aba206452f
commit 311555386e
4 changed files with 122 additions and 105 deletions

View File

@@ -1,10 +1,15 @@
import { registerEvent } from "../register-event";
import { downloadSourcesSublevel, repacksSublevel } from "@main/level";
import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers";
const deleteAllDownloadSources = async (
_event: Electron.IpcMainInvokeEvent
) => {
await Promise.all([repacksSublevel.clear(), downloadSourcesSublevel.clear()]);
// Invalidate caches after clearing all sources
invalidateDownloadSourcesCache();
invalidateIdCaches();
};
registerEvent("deleteAllDownloadSources", deleteAllDownloadSources);

View File

@@ -1,5 +1,6 @@
import { registerEvent } from "../register-event";
import { downloadSourcesSublevel, repacksSublevel } from "@main/level";
import { invalidateDownloadSourcesCache, invalidateIdCaches } from "./helpers";
const deleteDownloadSource = async (
_event: Electron.IpcMainInvokeEvent,
@@ -20,6 +21,10 @@ const deleteDownloadSource = async (
await batch.write();
await downloadSourcesSublevel.del(`${id}`);
// Invalidate caches after deletion
invalidateDownloadSourcesCache();
invalidateIdCaches();
};
registerEvent("deleteDownloadSource", deleteDownloadSource);

View File

@@ -163,19 +163,52 @@ const addNewDownloads = async (
for (const download of downloads) {
const formattedTitle = formatRepackName(download.title);
const [firstLetter] = formattedTitle;
const games = steamGames[firstLetter] || [];
let gamesInSteam: FormattedSteamGame[] = [];
const gamesInSteam = games.filter((game) =>
formattedTitle.startsWith(game.formattedName)
);
// Only try to match if we have a valid formatted title
if (formattedTitle && formattedTitle.length > 0) {
const [firstLetter] = formattedTitle;
const games = steamGames[firstLetter] || [];
if (gamesInSteam.length === 0) continue;
// Try exact prefix match first
gamesInSteam = games.filter((game) =>
formattedTitle.startsWith(game.formattedName)
);
// If no exact prefix match, try contains match (more lenient)
if (gamesInSteam.length === 0) {
gamesInSteam = games.filter(
(game) =>
formattedTitle.includes(game.formattedName) ||
game.formattedName.includes(formattedTitle)
);
}
// If still no match, try checking all letters (not just first letter)
// This helps with repacks that use abbreviations or alternate naming
if (gamesInSteam.length === 0) {
for (const letter of Object.keys(steamGames)) {
const letterGames = steamGames[letter] || [];
const matches = letterGames.filter(
(game) =>
formattedTitle.includes(game.formattedName) ||
game.formattedName.includes(formattedTitle)
);
if (matches.length > 0) {
gamesInSteam = matches;
break;
}
}
}
}
// Add matched game IDs to source tracking
for (const game of gamesInSteam) {
objectIdsOnSource.add(String(game.id));
}
// Create the repack even if no games matched
// This ensures all repacks from sources are imported
const repack = {
id: nextRepackId++,
objectIds: gamesInSteam.map((game) => String(game.id)),
@@ -254,3 +287,26 @@ export const importDownloadSourceToLocal = async (
objectIds,
};
};
export const updateDownloadSourcePreservingTimestamp = async (
existingSource: DownloadSource,
url: string
) => {
const response = await axios.get<z.infer<typeof downloadSourceSchema>>(url);
const updatedSource = {
...existingSource,
name: response.data.name,
etag: response.headers["etag"] || null,
status: DownloadSourceStatus.UpToDate,
downloadCount: response.data.downloads.length,
updatedAt: new Date(),
// Preserve the original createdAt timestamp
};
await downloadSourcesSublevel.put(`${existingSource.id}`, updatedSource);
invalidateDownloadSourcesCache();
return updatedSource;
};

View File

@@ -3,11 +3,7 @@ import axios, { AxiosError } from "axios";
import { z } from "zod";
import { downloadSourcesSublevel, repacksSublevel } from "@main/level";
import { DownloadSourceStatus } from "@shared";
import {
checkUrlExists,
invalidateDownloadSourcesCache,
invalidateIdCaches,
} from "./helpers";
import { invalidateDownloadSourcesCache } from "./helpers";
const downloadSourceSchema = z.object({
name: z.string().max(255),
@@ -157,60 +153,6 @@ const addNewDownloads = async (
}
};
const deleteDownloadSource = async (id: number) => {
const repacksToDelete: string[] = [];
for await (const [key, repack] of repacksSublevel.iterator()) {
if (repack.downloadSourceId === id) {
repacksToDelete.push(key);
}
}
const batch = repacksSublevel.batch();
for (const key of repacksToDelete) {
batch.del(key);
}
await batch.write();
await downloadSourcesSublevel.del(`${id}`);
invalidateDownloadSourcesCache();
invalidateIdCaches();
};
const importDownloadSource = async (url: string) => {
const urlExists = await checkUrlExists(url);
if (urlExists) {
return;
}
const response = await axios.get<z.infer<typeof downloadSourceSchema>>(url);
const steamGames = await getSteamGames();
const now = new Date();
const nextId = await getNextId(downloadSourcesSublevel);
const downloadSource = {
id: nextId,
url,
name: response.data.name,
etag: response.headers["etag"] || null,
status: DownloadSourceStatus.UpToDate,
downloadCount: response.data.downloads.length,
objectIds: [],
createdAt: now,
updatedAt: now,
};
await downloadSourcesSublevel.put(`${downloadSource.id}`, downloadSource);
invalidateDownloadSourcesCache();
await addNewDownloads(downloadSource, response.data.downloads, steamGames);
};
const syncDownloadSources = async (
_event: Electron.IpcMainInvokeEvent
): Promise<number> => {
@@ -249,57 +191,66 @@ const syncDownloadSources = async (
existingRepacks.push(repack);
}
if (downloadSources.some((source) => !source.fingerprint)) {
await Promise.all(
downloadSources.map(async (source) => {
await deleteDownloadSource(source.id);
await importDownloadSource(source.url);
})
);
} else {
for (const downloadSource of downloadSources) {
const headers: Record<string, string> = {};
// Handle sources with missing fingerprints individually, don't delete all sources
const sourcesWithFingerprints = downloadSources.filter(
(source) => source.fingerprint
);
const sourcesWithoutFingerprints = downloadSources.filter(
(source) => !source.fingerprint
);
if (downloadSource.etag) {
headers["If-None-Match"] = downloadSource.etag;
}
// For sources without fingerprints, just continue with normal sync
// They will get fingerprints updated later by updateMissingFingerprints
const allSourcesToSync = [
...sourcesWithFingerprints,
...sourcesWithoutFingerprints,
];
try {
const response = await axios.get(downloadSource.url, {
headers,
});
for (const downloadSource of allSourcesToSync) {
const headers: Record<string, string> = {};
const source = downloadSourceSchema.parse(response.data);
const steamGames = await getSteamGames();
if (downloadSource.etag) {
headers["If-None-Match"] = downloadSource.etag;
}
const repacks = source.downloads.filter(
(download) =>
!existingRepacks.some((repack) => repack.title === download.title)
);
try {
const response = await axios.get(downloadSource.url, {
headers,
});
await downloadSourcesSublevel.put(`${downloadSource.id}`, {
...downloadSource,
etag: response.headers["etag"] || null,
downloadCount: source.downloads.length,
status: DownloadSourceStatus.UpToDate,
});
const source = downloadSourceSchema.parse(response.data);
const steamGames = await getSteamGames();
await addNewDownloads(downloadSource, repacks, steamGames);
const repacks = source.downloads.filter(
(download) =>
!existingRepacks.some((repack) => repack.title === download.title)
);
newRepacksCount += repacks.length;
} catch (err: unknown) {
const isNotModified = (err as AxiosError).response?.status === 304;
await downloadSourcesSublevel.put(`${downloadSource.id}`, {
...downloadSource,
etag: response.headers["etag"] || null,
downloadCount: source.downloads.length,
status: DownloadSourceStatus.UpToDate,
});
await downloadSourcesSublevel.put(`${downloadSource.id}`, {
...downloadSource,
status: isNotModified
? DownloadSourceStatus.UpToDate
: DownloadSourceStatus.Errored,
});
}
await addNewDownloads(downloadSource, repacks, steamGames);
newRepacksCount += repacks.length;
} catch (err: unknown) {
const isNotModified = (err as AxiosError).response?.status === 304;
await downloadSourcesSublevel.put(`${downloadSource.id}`, {
...downloadSource,
status: isNotModified
? DownloadSourceStatus.UpToDate
: DownloadSourceStatus.Errored,
});
}
}
// Invalidate cache after all sync operations complete
invalidateDownloadSourcesCache();
return newRepacksCount;
} catch (err) {
return -1;