From dcb45da68b70c0ecc98d94f8bcf4585c25cfbedb Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sun, 1 Feb 2026 12:08:12 +1100 Subject: [PATCH] feat: tighter delta version support --- pages/admin/index.vue | 13 +- pages/store/[id]/index.vue | 15 +- .../v1/admin/depot/torrential/manifest.get.ts | 2 +- server/api/v1/admin/game/[id]/index.get.ts | 9 +- server/api/v1/admin/home/index.get.ts | 8 - .../api/v1/admin/import/version/index.post.ts | 7 +- server/api/v1/auth/signin/simple.post.ts | 16 +- server/api/v1/auth/signup/simple.post.ts | 6 +- .../[id]/version/[versionid]/index.get.ts | 6 +- .../api/v1/client/game/[id]/versions.get.ts | 18 +- server/api/v1/client/game/manifest.get.ts | 1 + server/api/v1/games/[id]/index.get.ts | 4 +- server/internal/gamesize/index.ts | 294 ++++++------------ server/internal/library/index.ts | 55 +--- server/internal/library/manifest/index.ts | 44 ++- .../{manifest.ts => manifest/utils.ts} | 0 16 files changed, 186 insertions(+), 312 deletions(-) rename server/internal/library/{manifest.ts => manifest/utils.ts} (100%) diff --git a/pages/admin/index.vue b/pages/admin/index.vue index 8d28871..ea5e755 100644 --- a/pages/admin/index.vue +++ b/pages/admin/index.vue @@ -173,7 +173,7 @@ :title="t('home.admin.biggestGamesToDownload')" :subtitle="t('home.admin.latestVersionOnly')" > - +
@@ -181,7 +181,7 @@ :title="t('home.admin.biggestGamesOnServer')" :subtitle="t('home.admin.allVersionsCombined')" > - +
@@ -196,7 +196,6 @@ import DropLogo from "~/components/DropLogo.vue"; import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline"; import { getPercentage } from "~/utils/utils"; import { getBarColor } from "~/utils/colors"; -import type { GameSize } from "~/server/internal/gamesize"; import type { RankItem } from "~/components/RankingList.vue"; definePageMeta({ @@ -216,16 +215,8 @@ const { gameCount, sources, userStats, - biggestGamesLatest, - biggestGamesCombined, } = await $dropFetch("/api/v1/admin/home"); -const gameToRankItem = (game: GameSize, rank: number): RankItem => ({ - rank: rank + 1, - name: game.gameName, - value: formatBytes(game.size), -}); - const pieChartData = [ { label: t("home.admin.inactiveUsers"), diff --git a/pages/store/[id]/index.vue b/pages/store/[id]/index.vue index 88af108..2ee589f 100644 --- a/pages/store/[id]/index.vue +++ b/pages/store/[id]/index.vue @@ -93,10 +93,21 @@ {{ $t("store.size") }} - {{ formatBytes(size) }} + , >(gameId: string, version: T) { - const size = await libraryManager.getGameVersionSize( - gameId, - version.versionId, - ); - return { ...version, size }; + const clientSize = await gameSizeManager.getVersionSize(version.versionId); + const diskSize = await gameSizeManager.getVersionDiskSize(version.versionId); + return { ...version, diskSize, clientSize }; } export type AdminFetchGameType = Prisma.GameGetPayload<{ diff --git a/server/api/v1/admin/home/index.get.ts b/server/api/v1/admin/home/index.get.ts index 4cdcd02..c9ce280 100644 --- a/server/api/v1/admin/home/index.get.ts +++ b/server/api/v1/admin/home/index.get.ts @@ -10,18 +10,10 @@ export default defineEventHandler(async (h3) => { const sources = await libraryManager.fetchLibraries(); const userStats = await userStatsManager.getUserStats(); - - const biggestGamesCombined = - await libraryManager.getBiggestGamesCombinedVersions(5); - const biggestGamesLatest = - await libraryManager.getBiggestGamesLatestVersions(5); - return { gameCount: await prisma.game.count(), version: systemConfig.getDropVersion(), userStats, sources, - biggestGamesLatest, - biggestGamesCombined, }; }); diff --git a/server/api/v1/admin/import/version/index.post.ts b/server/api/v1/admin/import/version/index.post.ts index a42531c..8cf5490 100644 --- a/server/api/v1/admin/import/version/index.post.ts +++ b/server/api/v1/admin/import/version/index.post.ts @@ -50,7 +50,12 @@ export default defineEventHandler(async (h3) => { where: { gameId: body.id, delta: false, - launches: { some: { platform: platformObject.platform } }, + OR: [ + { launches: { some: { platform: platformObject.platform } } }, + { + setups: { some: { platform: platformObject.platform } }, + }, + ], }, }); if (validOverlayVersions == 0) diff --git a/server/api/v1/auth/signin/simple.post.ts b/server/api/v1/auth/signin/simple.post.ts index 8d40085..211ab52 100644 --- a/server/api/v1/auth/signin/simple.post.ts +++ b/server/api/v1/auth/signin/simple.post.ts @@ -23,7 +23,7 @@ export default defineEventHandler<{ if (!authManager.getAuthProviders().Simple) throw createError({ statusCode: 403, - statusMessage: t("errors.auth.method.signinDisabled"), + message: t("errors.auth.method.signinDisabled"), }); const body = signinValidator(await readBody(h3)); @@ -33,7 +33,7 @@ export default defineEventHandler<{ throw createError({ statusCode: 400, - statusMessage: body.summary, + message: body.summary, }); } @@ -57,13 +57,13 @@ export default defineEventHandler<{ if (!authMek) throw createError({ statusCode: 401, - statusMessage: t("errors.auth.invalidUserOrPass"), + message: t("errors.auth.invalidUserOrPass"), }); if (!authMek.user.enabled) throw createError({ statusCode: 403, - statusMessage: t("errors.auth.disabled"), + message: t("errors.auth.disabled"), }); // LEGACY bcrypt @@ -74,13 +74,13 @@ export default defineEventHandler<{ if (!hash) throw createError({ statusCode: 500, - statusMessage: t("errors.auth.invalidPassState"), + message: t("errors.auth.invalidPassState"), }); if (!(await checkHashBcrypt(body.password, hash))) throw createError({ statusCode: 401, - statusMessage: t("errors.auth.invalidUserOrPass"), + message: t("errors.auth.invalidUserOrPass"), }); // TODO: send user to forgot password screen or something to force them to change their password to new system @@ -101,13 +101,13 @@ export default defineEventHandler<{ if (!hash || typeof hash !== "string") throw createError({ statusCode: 500, - statusMessage: t("errors.auth.invalidPassState"), + message: t("errors.auth.invalidPassState"), }); if (!(await checkHashArgon2(body.password, hash))) throw createError({ statusCode: 401, - statusMessage: t("errors.auth.invalidUserOrPass"), + message: t("errors.auth.invalidUserOrPass"), }); const result = await sessionHandler.signin(h3, authMek.userId, { diff --git a/server/api/v1/auth/signup/simple.post.ts b/server/api/v1/auth/signup/simple.post.ts index 5f3fdb2..cd8086a 100644 --- a/server/api/v1/auth/signup/simple.post.ts +++ b/server/api/v1/auth/signup/simple.post.ts @@ -27,7 +27,7 @@ export default defineEventHandler<{ if (!authManager.getAuthProviders().Simple) throw createError({ statusCode: 403, - statusMessage: t("errors.auth.method.signinDisabled"), + message: t("errors.auth.method.signinDisabled"), }); const user = await readValidatedBody(h3, CreateUserValidator); @@ -38,7 +38,7 @@ export default defineEventHandler<{ if (!invitation) throw createError({ statusCode: 401, - statusMessage: t("errors.auth.invalidInvite"), + message: t("errors.auth.invalidInvite"), }); // reuse items from invite @@ -51,7 +51,7 @@ export default defineEventHandler<{ if (existing > 0) throw createError({ statusCode: 400, - statusMessage: t("errors.auth.usernameTaken"), + message: t("errors.auth.usernameTaken"), }); const userId = randomUUID(); diff --git a/server/api/v1/client/game/[id]/version/[versionid]/index.get.ts b/server/api/v1/client/game/[id]/version/[versionid]/index.get.ts index 1f3e7a2..c78a960 100644 --- a/server/api/v1/client/game/[id]/version/[versionid]/index.get.ts +++ b/server/api/v1/client/game/[id]/version/[versionid]/index.get.ts @@ -1,6 +1,5 @@ import { defineClientEventHandler } from "~/server/internal/clients/event-handler"; import prisma from "~/server/internal/db/database"; -import libraryManager from "~/server/internal/library"; export default defineClientEventHandler(async (h3) => { const id = getRouterParam(h3, "id"); @@ -57,8 +56,5 @@ export default defineClientEventHandler(async (h3) => { })), }; - return { - ...gameVersionMapped, - size: libraryManager.getGameVersionSize(id, version), - }; + return gameVersionMapped; }); diff --git a/server/api/v1/client/game/[id]/versions.get.ts b/server/api/v1/client/game/[id]/versions.get.ts index 2c543e7..9125e1f 100644 --- a/server/api/v1/client/game/[id]/versions.get.ts +++ b/server/api/v1/client/game/[id]/versions.get.ts @@ -1,6 +1,7 @@ import type { Platform } from "~/prisma/client/enums"; import { defineClientEventHandler } from "~/server/internal/clients/event-handler"; import prisma from "~/server/internal/db/database"; +import type { GameVersionSize } from "~/server/internal/gamesize"; import gameSizeManager from "~/server/internal/gamesize"; type VersionDownloadOption = { @@ -8,14 +9,14 @@ type VersionDownloadOption = { displayName?: string | undefined; versionPath?: string | undefined; platform: Platform; - size: number; + size: GameVersionSize; requiredContent: Array<{ gameId: string; versionId: string; name: string; iconObjectId: string; shortDescription: string; - size: number; + size: GameVersionSize; }>; }; @@ -86,19 +87,14 @@ export default defineClientEventHandler(async (h3) => { iconObjectId: launch.executor.gameVersion.game.mIconObjectId, shortDescription: launch.executor.gameVersion.game.mShortDescription, - size: - (await gameSizeManager.getGameVersionSize( - launch.executor.gameVersion.game.id, - launch.executor.gameVersion.versionId, - )) ?? 0, + size: (await gameSizeManager.getVersionSize( + launch.executor.gameVersion.versionId, + ))!, }); } } - const size = await gameSizeManager.getGameVersionSize( - v.gameId, - v.versionId, - ); + const size = await gameSizeManager.getVersionSize(v.versionId); return platformOptions .entries() diff --git a/server/api/v1/client/game/manifest.get.ts b/server/api/v1/client/game/manifest.get.ts index 44208a4..4edd8d2 100644 --- a/server/api/v1/client/game/manifest.get.ts +++ b/server/api/v1/client/game/manifest.get.ts @@ -11,5 +11,6 @@ export default defineClientEventHandler(async (h3) => { }); const result = await createDownloadManifestDetails(version); + console.log(result); return result; }); diff --git a/server/api/v1/games/[id]/index.get.ts b/server/api/v1/games/[id]/index.get.ts index 85b6950..1d2442a 100644 --- a/server/api/v1/games/[id]/index.get.ts +++ b/server/api/v1/games/[id]/index.get.ts @@ -1,6 +1,6 @@ import aclManager from "~/server/internal/acls"; import prisma from "~/server/internal/db/database"; -import libraryManager from "~/server/internal/library"; +import gameSizeManager from "~/server/internal/gamesize"; export default defineEventHandler(async (h3) => { const userId = await aclManager.getUserIdACL(h3, ["store:read"]); @@ -57,7 +57,7 @@ export default defineEventHandler(async (h3) => { }, }); - const size = await libraryManager.getGameVersionSize(game.id); + const size = (await gameSizeManager.getGameBreakdown(gameId))!; return { game, rating, size }; }); diff --git a/server/internal/gamesize/index.ts b/server/internal/gamesize/index.ts index a919ad1..ce4bd6e 100644 --- a/server/internal/gamesize/index.ts +++ b/server/internal/gamesize/index.ts @@ -1,228 +1,116 @@ import cacheHandler from "../cache"; import prisma from "../db/database"; import { sum } from "../../../utils/array"; -import type { Game, GameVersion } from "~/prisma/client/client"; -import { castManifest } from "../library/manifest"; +import { createDownloadManifestDetails } from "../library/manifest"; +import { castManifest } from "../library/manifest/utils"; -export type GameSize = { - gameName: string; - size: number; - gameId: string; +export type GameVersionSize = { + versionId: string; + installSize: number; + downloadSize: number; }; -export type VersionSize = GameSize & { - latest: boolean; -}; - -type VersionsSizes = { - [versionName: string]: VersionSize; -}; - -type GameVersionsSize = { - [gameId: string]: VersionsSizes; +export type GameSizeBreakdown = { + diskSize: number; + versions: Array; }; class GameSizeManager { private gameVersionsSizesCache = - cacheHandler.createCache("gameVersionsSizes"); - // All versions sizes combined - private gameSizesCache = cacheHandler.createCache("gameSizes"); + cacheHandler.createCache("versionSizes"); + private gameBreakdownCache = + cacheHandler.createCache("gameBreakdown"); - private async clearGameVersionsSizesCache() { - (await this.gameVersionsSizesCache.getKeys()).map((key) => - this.gameVersionsSizesCache.remove(key), - ); - } - - private async clearGameSizesCache() { - (await this.gameSizesCache.getKeys()).map((key) => - this.gameSizesCache.remove(key), - ); - } - - // All versions of a game combined - async getCombinedGameSize(gameId: string) { - const versions = await prisma.gameVersion.findMany({ - where: { gameId }, - }); - const sizes = await Promise.all( - versions.map((version) => castManifest(version.dropletManifest).size), - ); - return sum(sizes); - } - - async getGameVersionSize( - gameId: string, - versionId?: string, - ): Promise { - if (!versionId) { - const version = await prisma.gameVersion.findFirst({ - where: { gameId }, - orderBy: { - versionIndex: "desc", - }, - }); - if (!version) { - return null; - } - versionId = version.versionId; + /*** + * Gets the size of the game to the user: + * - installSize: size on disk after install + * - downloadSize: how many bytes are downloaded (but not necessarily stored) + */ + async getVersionSize(versionId: string): Promise { + if (await this.gameVersionsSizesCache.has(versionId)) + return await this.gameVersionsSizesCache.get(versionId); + try { + const { downloadSize, installSize } = + await createDownloadManifestDetails(versionId); + const result = { + downloadSize, + installSize, + versionId, + } satisfies GameVersionSize; + await this.gameVersionsSizesCache.set(versionId, result); + return result; + } catch { + return null; } - - const { dropletManifest } = (await prisma.gameVersion.findUnique({ - where: { versionId }, - }))!; - - return castManifest(dropletManifest).size; } - private async isLatestVersion( - gameVersions: GameVersion[], - version: GameVersion, - ): Promise { - return gameVersions.length > 0 - ? gameVersions[0].versionId === version.versionId - : false; - } - - async getBiggestGamesLatestVersion(top: number): Promise { - const gameIds = await this.gameVersionsSizesCache.getKeys(); - const latestGames = await Promise.all( - gameIds.map(async (gameId) => { - const versionsSizes = await this.gameVersionsSizesCache.get(gameId); - if (!versionsSizes) { - return null; - } - const latestVersionName = Object.keys(versionsSizes).find( - (versionName) => versionsSizes[versionName].latest, - ); - if (!latestVersionName) { - return null; - } - return versionsSizes[latestVersionName] || null; - }), - ); - return latestGames - .filter((game) => game !== null) - .sort((gameA, gameB) => gameB.size - gameA.size) - .slice(0, top); - } - - async isGameVersionsSizesCacheEmpty() { - return (await this.gameVersionsSizesCache.getKeys()).length === 0; - } - - async isGameSizesCacheEmpty() { - return (await this.gameSizesCache.getKeys()).length === 0; - } - - async cacheAllCombinedGames() { - await this.clearGameSizesCache(); - const games = await prisma.game.findMany({ include: { versions: true } }); - - await Promise.all(games.map((game) => this.cacheCombinedGame(game))); - } - - async cacheCombinedGame(game: Game) { - const size = await this.getCombinedGameSize(game.id); - if (!size) { - this.gameSizesCache.remove(game.id); - return; - } - const gameSize = { - size, - gameName: game.mName, - gameId: game.id, - }; - await this.gameSizesCache.set(game.id, gameSize); - } - - async cacheAllGameVersions() { - await this.clearGameVersionsSizesCache(); - const games = await prisma.game.findMany({ - include: { - versions: { - orderBy: { - versionIndex: "desc", - }, - take: 1, - }, + /*** + * Get the size of the game on disk + */ + async getVersionDiskSize(versionId: string): Promise { + const version = await prisma.gameVersion.findUnique({ + where: { + versionId, + }, + select: { + dropletManifest: true, }, }); - - await Promise.all(games.map((game) => this.cacheGameVersion(game))); + if (!version) return null; + return castManifest(version.dropletManifest).size; } - async cacheGameVersion( - game: Game & { versions: GameVersion[] }, - versionId?: string, - ) { - const cacheVersion = async (version: GameVersion) => { - const size = await this.getGameVersionSize(game.id, version.versionId); - if (!version.versionId || !size) { - return; - } - - const versionsSizes = { - [version.versionId]: { - size, - gameName: game.mName, - gameId: game.id, - latest: await this.isLatestVersion(game.versions, version), - }, - }; - const allVersionsSizes = - (await this.gameVersionsSizesCache.get(game.id)) || {}; - await this.gameVersionsSizesCache.set(game.id, { - ...allVersionsSizes, - ...versionsSizes, - }); - }; - - if (versionId) { - const version = await prisma.gameVersion.findFirst({ - where: { gameId: game.id, versionId }, - }); - if (!version) { - return; - } - cacheVersion(version); - return; - } - if ("versions" in game) { - await Promise.all(game.versions.map(cacheVersion)); - } - } - - async getBiggestGamesAllVersions(top: number): Promise { - const gameIds = await this.gameSizesCache.getKeys(); - const allGames = await Promise.all( - gameIds.map(async (gameId) => await this.gameSizesCache.get(gameId)), + /** + * Calculate the total disk usage of a game + * @param gameId Game ID to calculate + * @returns Total **disk** size of the game + */ + async getGameDiskSize(gameId: string): Promise { + const versions = await prisma.gameVersion.findMany({ + where: { gameId }, + select: { + versionId: true, + }, + }); + const sizes = await Promise.all( + versions.map((version) => this.getVersionDiskSize(version.versionId)), ); - return allGames - .filter((game) => game !== null) - .sort((gameA, gameB) => gameB.size - gameA.size) - .slice(0, top); + return sum(sizes.filter((v) => v !== null)); } - async deleteGameVersion(gameId: string, version: string) { - const game = await prisma.game.findFirst({ where: { id: gameId } }); - if (game) { - await this.cacheCombinedGame(game); - } - const versionsSizes = await this.gameVersionsSizesCache.get(gameId); - if (!versionsSizes) { - return; - } - // Remove the version from the VersionsSizes object - const { [version]: _, ...updatedVersionsSizes } = versionsSizes; - await this.gameVersionsSizesCache.set(gameId, updatedVersionsSizes); - } + async getGameBreakdown(gameId: string): Promise { + const versions = await prisma.gameVersion.findMany({ + where: { gameId }, + orderBy: { versionIndex: "desc" }, + select: { versionId: true, displayName: true, versionPath: true }, + }); + if (!versions) return null; - async deleteGame(gameId: string) { - this.gameSizesCache.remove(gameId); - this.gameVersionsSizesCache.remove(gameId); + const breakdownKey = `${gameId} ${versions.map((v) => v.versionId).join(" ")}`; + + if (await this.gameBreakdownCache.has(breakdownKey)) + return (await this.gameBreakdownCache.get(breakdownKey))!; + + let diskSize = 0; + const versionInformation = []; + for (const version of versions) { + const size = (await this.getVersionSize(version.versionId))!; + const vDiskSize = (await this.getVersionDiskSize(version.versionId))!; + diskSize += vDiskSize; + versionInformation.push({ + ...size, + diskSize: vDiskSize, + name: (version.displayName ?? version.versionPath)!, + }); + } + const result = { + diskSize, + versions: versionInformation, + }; + await this.gameBreakdownCache.set(breakdownKey, result); + return result; } } -export const manager = new GameSizeManager(); -export default manager; +export const gameSizeManager = new GameSizeManager(); +export default gameSizeManager; diff --git a/server/internal/library/index.ts b/server/internal/library/index.ts index 84513a5..56421e8 100644 --- a/server/internal/library/index.ts +++ b/server/internal/library/index.ts @@ -19,7 +19,7 @@ import gameSizeManager from "~/server/internal/gamesize"; import { TORRENTIAL_SERVICE } from "../services/services/torrential"; import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post"; import { GameType, type Platform } from "~/prisma/client/enums"; -import { castManifest } from "./manifest"; +import { castManifest } from "./manifest/utils"; export function createGameImportTaskId(libraryId: string, libraryPath: string) { return createHash("md5") @@ -500,14 +500,18 @@ class LibraryManager { acls: ["system:import:version:read"], }); - await libraryManager.cacheCombinedGameSize(gameId); - await libraryManager.cacheGameVersionSize(gameId, newVersion.versionId); - await TORRENTIAL_SERVICE.utils().invalidate( gameId, newVersion.versionId, ); + // Ensure cache is filled (also pre-caches the manifest) + try { + await gameSizeManager.getVersionSize(newVersion.versionId); + } catch (e) { + logger.warn(`Failed to pre-cache game size and manifest: ${e}`); + } + if (version.type === "depot") { // SAFETY: we can only reach this if the type is depot and identifier is valid // eslint-disable-next-line drop/no-prisma-delete @@ -552,8 +556,6 @@ class LibraryManager { versionId: version, }, }); - - await gameSizeManager.deleteGameVersion(gameId, version); } async deleteGame(gameId: string) { @@ -562,7 +564,6 @@ class LibraryManager { id: gameId, }, }); - await gameSizeManager.deleteGame(gameId); // Delete all game versions that depended on this game await prisma.gameVersion.deleteMany({ where: { @@ -578,46 +579,6 @@ class LibraryManager { }, }); } - - async getGameVersionSize( - gameId: string, - versionName?: string, - ): Promise { - return gameSizeManager.getGameVersionSize(gameId, versionName); - } - - async getBiggestGamesCombinedVersions(top: number) { - if (await gameSizeManager.isGameSizesCacheEmpty()) { - await gameSizeManager.cacheAllCombinedGames(); - } - return gameSizeManager.getBiggestGamesAllVersions(top); - } - - async getBiggestGamesLatestVersions(top: number) { - if (await gameSizeManager.isGameVersionsSizesCacheEmpty()) { - await gameSizeManager.cacheAllGameVersions(); - } - return gameSizeManager.getBiggestGamesLatestVersion(top); - } - - async cacheCombinedGameSize(gameId: string) { - const game = await prisma.game.findFirst({ where: { id: gameId } }); - if (!game) { - return; - } - await gameSizeManager.cacheCombinedGame(game); - } - - async cacheGameVersionSize(gameId: string, versionId: string) { - const game = await prisma.game.findFirst({ - where: { id: gameId }, - include: { versions: true }, - }); - if (!game) { - return; - } - await gameSizeManager.cacheGameVersion(game, versionId); - } } export const libraryManager = new LibraryManager(); diff --git a/server/internal/library/manifest/index.ts b/server/internal/library/manifest/index.ts index 3c4f50b..e5279e0 100644 --- a/server/internal/library/manifest/index.ts +++ b/server/internal/library/manifest/index.ts @@ -1,14 +1,27 @@ +import cacheHandler from "../../cache"; import prisma from "../../db/database"; -import { castManifest, type DropletManifest } from "../manifest"; +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(map: Map): { [key: string]: T } { return Object.fromEntries(map.entries().toArray()); } +const manifestCache = + cacheHandler.createCache("manifestCache"); /** * @@ -17,7 +30,9 @@ function convertMap(map: Map): { [key: string]: T } { */ export async function createDownloadManifestDetails( versionId: string, + refresh = false, ): Promise { + if(await manifestCache.has(versionId) && !refresh) return (await manifestCache.get(versionId))!; const mainVersion = await prisma.gameVersion.findUnique({ where: { versionId }, select: { @@ -75,6 +90,9 @@ export async function createDownloadManifestDetails( } } + let installSize = 0; + let downloadSize = 0; + // Now that we have our file list, filter the manifests const manifests = new Map(); for (const version of versionOrder) { @@ -86,9 +104,17 @@ export async function createDownloadManifestDetails( const fileNames = Object.fromEntries(files); const manifest = castManifest(version.dropletManifest); const filteredChunks = Object.fromEntries( - Object.entries(manifest.chunks).filter(([, chunkData]) => - chunkData.files.some((fileEntry) => !!fileNames[fileEntry.filename]), - ), + Object.entries(manifest.chunks).filter(([, chunkData]) => { + let flag = false; + chunkData.files.forEach((fileEntry) => { + if (fileNames[fileEntry.filename]) { + flag = true; + installSize += fileEntry.length; + } + downloadSize += fileEntry.length; + }); + return flag; + }), ); manifests.set(version.versionId, { ...manifest, @@ -96,5 +122,13 @@ export async function createDownloadManifestDetails( }); } - return { fileList: convertMap(fileList), manifests: convertMap(manifests) }; + const result = { + fileList: convertMap(fileList), + manifests: convertMap(manifests), + installSize, + downloadSize, + }; + await manifestCache.set(versionId, result); + + return result; } diff --git a/server/internal/library/manifest.ts b/server/internal/library/manifest/utils.ts similarity index 100% rename from server/internal/library/manifest.ts rename to server/internal/library/manifest/utils.ts