mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-19 17:23:57 +00:00
fix: fixing bug with sources
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user