63ac2b8ffc
* feat: nginx + torrential basics & services system * fix: lint + i18n * fix: update torrential to remove openssl * feat: add torrential to Docker build * feat: move to self hosted runner * fix: move off self-hosted runner * fix: update nginx.conf * feat: torrential cache invalidation * fix: update torrential for cache invalidation * feat: integrity check task * fix: lint * feat: move to version ids * fix: client fixes and client-side checks * feat: new depot apis and version id fixes * feat: update torrential * feat: droplet bump and remove unsafe update functions * fix: lint * feat: v4 featureset: emulators, multi-launch commands * fix: lint * fix: mobile ui for game editor * feat: launch options * fix: lint * fix: remove axios, use $fetch * feat: metadata and task api improvements * feat: task actions * fix: slight styling issue * feat: fix style and lints * feat: totp backend routes * feat: oidc groups * fix: update drop-base * feat: creation of passkeys & totp * feat: totp signin * feat: webauthn mfa/signin * feat: launch selecting ui * fix: manually running tasks * feat: update add company game modal to use new SelectorGame * feat: executor selector * fix(docker): update rust to rust nightly for torrential build (#305) * feat: new version ui * feat: move package lookup to build time to allow for deno dev * fix: lint * feat: localisation cleanup * feat: apply localisation cleanup * feat: potential i18n refactor logic * feat: remove args from commands * fix: lint * fix: lockfile --------- Co-authored-by: Aden Lindsay <140392385+AdenMGB@users.noreply.github.com>
229 lines
6.2 KiB
TypeScript
229 lines
6.2 KiB
TypeScript
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";
|
|
|
|
export type GameSize = {
|
|
gameName: string;
|
|
size: number;
|
|
gameId: string;
|
|
};
|
|
|
|
export type VersionSize = GameSize & {
|
|
latest: boolean;
|
|
};
|
|
|
|
type VersionsSizes = {
|
|
[versionName: string]: VersionSize;
|
|
};
|
|
|
|
type GameVersionsSize = {
|
|
[gameId: string]: VersionsSizes;
|
|
};
|
|
|
|
class GameSizeManager {
|
|
private gameVersionsSizesCache =
|
|
cacheHandler.createCache<GameVersionsSize>("gameVersionsSizes");
|
|
// All versions sizes combined
|
|
private gameSizesCache = cacheHandler.createCache<GameSize>("gameSizes");
|
|
|
|
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<number | null> {
|
|
if (!versionId) {
|
|
const version = await prisma.gameVersion.findFirst({
|
|
where: { gameId },
|
|
orderBy: {
|
|
versionIndex: "desc",
|
|
},
|
|
});
|
|
if (!version) {
|
|
return null;
|
|
}
|
|
versionId = version.versionId;
|
|
}
|
|
|
|
const { dropletManifest } = (await prisma.gameVersion.findUnique({
|
|
where: { gameId_versionId: { versionId, gameId } },
|
|
}))!;
|
|
|
|
return castManifest(dropletManifest).size;
|
|
}
|
|
|
|
private async isLatestVersion(
|
|
gameVersions: GameVersion[],
|
|
version: GameVersion,
|
|
): Promise<boolean> {
|
|
return gameVersions.length > 0
|
|
? gameVersions[0].versionId === version.versionId
|
|
: false;
|
|
}
|
|
|
|
async getBiggestGamesLatestVersion(top: number): Promise<VersionSize[]> {
|
|
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,
|
|
},
|
|
},
|
|
});
|
|
|
|
await Promise.all(games.map((game) => this.cacheGameVersion(game)));
|
|
}
|
|
|
|
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<GameSize[]> {
|
|
const gameIds = await this.gameSizesCache.getKeys();
|
|
const allGames = await Promise.all(
|
|
gameIds.map(async (gameId) => await this.gameSizesCache.get(gameId)),
|
|
);
|
|
return allGames
|
|
.filter((game) => game !== null)
|
|
.sort((gameA, gameB) => gameB.size - gameA.size)
|
|
.slice(0, top);
|
|
}
|
|
|
|
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 deleteGame(gameId: string) {
|
|
this.gameSizesCache.remove(gameId);
|
|
this.gameVersionsSizesCache.remove(gameId);
|
|
}
|
|
}
|
|
|
|
export const manager = new GameSizeManager();
|
|
export default manager;
|