Files
drop/server/internal/downloads/manifest.ts
DecDuck 251ddb8ff8 Rearchitecture for v0.4.0 (#197)
* feat: database redist support

* feat: rearchitecture of database schemas, migration reset, and #180

* feat: import redists

* fix: giantbomb logging bug

* feat: partial user platform support + statusMessage -> message

* feat: add user platform filters to store view

* fix: sanitize svg uploads

... copilot suggested this

I feel dirty.

* feat: beginnings of platform & redist management

* feat: add server side redist patching

* fix: update drop-base commit

* feat: import of custom platforms & file extensions

* fix: redelete platform

* fix: remove platform

* feat: uninstall commands, new R UI

* checkpoint: before migrating to nuxt v4

* update to nuxt 4

* fix: fixes for Nuxt v4 update

* fix: remaining type issues

* feat: initial feedback to import other kinds of versions

* working commit

* fix: lint

* feat: redist import
2025-11-10 10:36:13 +11:00

140 lines
3.6 KiB
TypeScript

import prisma from "../db/database";
import { sum } from "~/utils/array";
export type DropChunk = {
permissions: number;
ids: string[];
checksums: string[];
lengths: number[];
};
export type DropManifest = {
[key: string]: DropChunk;
};
export type DropManifestMetadata = {
manifest: DropManifest;
versionId: string;
};
export type DropGeneratedManifest = DropManifest & {
[key: string]: { versionId: string };
};
class ManifestGenerator {
private static generateManifestFromMetadata(
rootManifest: DropManifestMetadata,
...overlays: DropManifestMetadata[]
): DropGeneratedManifest {
if (overlays.length == 0) {
return Object.fromEntries(
Object.entries(rootManifest.manifest).map(([key, value]) => {
return [
key,
Object.assign({}, value, { versionId: rootManifest.versionId }),
];
}),
);
}
// Recurse in verse order through versions, skipping files that already exist.
const versions = [...overlays.reverse(), rootManifest];
const manifest: DropGeneratedManifest = {};
for (const version of versions) {
for (const [filename, chunk] of Object.entries(version.manifest)) {
if (manifest[filename]) continue;
manifest[filename] = Object.assign({}, chunk, {
versionId: version.versionId,
});
}
}
return manifest;
}
// Local function because eventual caching
async generateManifest(versionId: string) {
const versions = [];
const baseVersion = await prisma.gameVersion.findUnique({
where: {
versionId,
version: {
gameId: {
not: null,
},
},
},
include: {
platform: true,
version: {
select: {
gameId: true,
dropletManifest: true,
versionIndex: true,
},
},
},
});
if (!baseVersion) return undefined;
versions.push(baseVersion);
// Collect other versions if this is a delta
if (baseVersion.delta) {
// Start at the same index minus one, and keep grabbing them
// until we run out or we hit something that isn't a delta
// eslint-disable-next-line no-constant-condition
for (let i = baseVersion.version.versionIndex - 1; true; i--) {
const currentVersion = await prisma.gameVersion.findFirst({
where: {
version: {
gameId: baseVersion.version.gameId!,
versionIndex: i,
},
platform: {
id: baseVersion.platform.id,
},
},
include: {
version: {
select: {
dropletManifest: true,
},
},
},
});
if (!currentVersion) return undefined;
versions.push(currentVersion);
if (!currentVersion?.delta) break;
}
}
versions.reverse();
const metadata: DropManifestMetadata[] = versions.map((gameVersion) => {
return {
manifest: JSON.parse(
gameVersion.version.dropletManifest?.toString() ?? "{}",
) as DropManifest,
versionId: gameVersion.versionId,
};
});
const manifest = ManifestGenerator.generateManifestFromMetadata(
metadata[0]!,
...metadata.slice(1),
);
return manifest;
}
calculateManifestSize(manifest: DropManifest) {
return sum(
Object.values(manifest)
.map((chunk) => chunk.lengths)
.flat(),
);
}
}
export const manifestGenerator = new ManifestGenerator();
export default manifestGenerator;