Files
drop/server/internal/library/manifest/index.ts
T
DecDuck dbe34684d8 Paginated admin library & upgrade manifests (#351)
* feat: new page layout + endpoint

* feat: non-parallel mass import

* feat: paginated admin library

* feat: lint and performance improvement

* feat: library filter util

* feat: link frontend features to backend

* fix: lint

* fix: small fixes

* feat: bump torrential

* fix: lint
2026-02-25 02:17:33 +11:00

153 lines
4.3 KiB
TypeScript

import cacheHandler from "../../cache";
import prisma from "../../db/database";
import { castManifest, type DropletManifest } from "./utils";
export type DownloadManifestDetails = {
/***
* Version ID to manifest
*/
manifests: { [key: string]: DropletManifest };
/***
* File name to version ID
*/
fileList: { [key: string]: string };
/// Size on disk after download
installSize: number;
/// Size of download
downloadSize: number;
};
function convertMap<T>(map: Map<string, T>): { [key: string]: T } {
return Object.fromEntries(map.entries().toArray());
}
const manifestCache =
cacheHandler.createCache<DownloadManifestDetails>("manifestCache");
/**
*
* @param gameId Game ID
* @param versionId Version ID
*/
export async function createDownloadManifestDetails(
versionId: string,
previous?: string,
refresh = false,
): Promise<DownloadManifestDetails> {
const manifestKey = `${versionId}${previous ? `-from-${previous}` : ""}`;
if ((await manifestCache.has(manifestKey)) && !refresh)
return (await manifestCache.get(manifestKey))!;
const mainVersion = await prisma.gameVersion.findUnique({
where: { versionId },
select: {
versionId: true,
delta: true,
versionIndex: true,
fileList: true,
negativeFileList: true,
gameId: true,
dropletManifest: true,
},
});
if (!mainVersion)
throw createError({ statusCode: 404, message: "Version not found" });
const collectedVersions = [];
let versionIndex = mainVersion.versionIndex;
while (mainVersion.delta) {
const nextVersion = await prisma.gameVersion.findFirst({
where: { gameId: mainVersion.gameId, versionIndex: { lt: versionIndex } },
orderBy: {
versionIndex: "desc",
},
select: {
versionId: true,
versionIndex: true,
delta: true,
fileList: true,
negativeFileList: true,
dropletManifest: true,
},
});
if (!nextVersion)
throw createError({
statusCode: 500,
message: "Delta version without version underneath it.",
});
versionIndex = nextVersion.versionIndex;
collectedVersions.push(nextVersion);
if (!nextVersion.delta) break;
}
collectedVersions.reverse();
// Apply fileList in lowest priority to newest priority
const versionOrder = [...collectedVersions, mainVersion];
const fileList = new Map<string, string>();
for (const version of versionOrder) {
for (const file of version.fileList) {
fileList.set(file, version.versionId);
}
for (const negFile of version.negativeFileList) {
fileList.delete(negFile);
}
}
let installSize = 0;
let downloadSize = 0;
const existingChunks = previous
? await createDownloadManifestDetails(previous)
: undefined;
// Now that we have our file list, filter the manifests
const manifests = new Map<string, DropletManifest>();
for (const version of versionOrder) {
const files = fileList
.entries()
.filter(([, versionId]) => version.versionId === versionId)
.toArray();
if (files.length == 0) continue;
const fileNames = Object.fromEntries(files);
const manifest = castManifest(version.dropletManifest);
const filteredChunks = Object.fromEntries(
Object.entries(manifest.chunks).filter(([_, chunkData]) => {
//if(existingChunks && existingChunks.manifests[version.versionId]?.chunks?.[chunkId]) return false;
let flag = false;
chunkData.files.forEach((fileEntry) => {
if (
existingChunks &&
existingChunks.fileList[fileEntry.filename] == version.versionId
)
return;
if (fileNames[fileEntry.filename]) {
flag = true;
installSize += fileEntry.length;
}
});
// If we have to download this chunk, add it's length
if (flag) {
downloadSize += chunkData.files
.map((v) => v.length)
.reduce((a, b) => a + b, 0);
}
return flag;
}),
);
manifests.set(version.versionId, {
...manifest,
chunks: filteredChunks,
});
}
const result = {
fileList: convertMap(fileList),
manifests: convertMap(manifests),
installSize,
downloadSize,
};
await manifestCache.set(manifestKey, result);
return result;
}