added download chunk endpoint

This commit is contained in:
DecDuck
2024-10-23 12:03:31 +11:00
parent 6e2dc89670
commit 3dd6062af4
3 changed files with 70 additions and 14 deletions

View File

@ -0,0 +1,51 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import fs from "fs";
import path from "path";
import libraryManager from "~/server/internal/library";
const chunkSize = 1024 * 1024 * 64;
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
const gameId = query.id?.toString();
const versionName = query.versionName?.toString();
const filename = query.name?.toString();
const chunkIndex = parseInt(query.chunk?.toString() ?? "?");
if (!gameId || !versionName || !filename || !Number.isNaN(chunkIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk arguments",
});
const game = await prisma.game.findUnique({
where: {
id: gameId,
},
select: {
libraryBasePath: true,
},
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
const versionDir = path.join(libraryManager.fetchLibraryPath(), versionName);
if (!fs.existsSync(versionDir))
throw createError({
statusCode: 400,
statusMessage: "Invalid version name",
});
const gameFile = path.join(versionDir, filename);
if (!fs.existsSync(versionDir))
throw createError({ statusCode: 400, statusMessage: "Invalid game file" });
const gameFileStats = fs.statSync(gameFile);
const start = chunkIndex * chunkSize;
const end = Math.min((chunkIndex + 1) * chunkSize, gameFileStats.size);
const gameReadStream = fs.createReadStream(gameFile, { start, end });
return sendStream(h3, gameReadStream);
});

View File

@ -5,7 +5,7 @@ import prisma from "../db/database";
export type EventHandlerFunction<T> = (
h3: H3Event<EventHandlerRequest>,
utils: ClientUtils
utils: ClientUtils,
) => Promise<T> | T;
type ClientUtils = {
@ -18,7 +18,7 @@ const NONCE_LENIENCE = 30_000;
export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
return defineEventHandler(async (h3) => {
const header = await getHeader(h3, "Authorization");
const header = getHeader(h3, "Authorization");
if (!header) throw createError({ statusCode: 403 });
const [method, ...parts] = header.split(" ");
@ -49,6 +49,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
const ca = h3.context.ca;
const certBundle = await ca.fetchClientCertificate(clientId);
// This does the blacklist check already
if (!certBundle)
throw createError({
statusCode: 403,
@ -80,7 +81,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
});
if (!client)
throw new Error(
"client util fetch client broke - this should NOT happen"
"client util fetch client broke - this should NOT happen",
);
return client;
}
@ -95,7 +96,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
if (!client)
throw new Error(
"client util fetch client broke - this should NOT happen"
"client util fetch client broke - this should NOT happen",
);
return client.user;

View File

@ -22,6 +22,10 @@ class LibraryManager {
this.basePath = process.env.LIBRARY ?? "./.data/library";
}
fetchLibraryPath() {
return this.basePath;
}
async fetchAllUnimportedGames() {
const dirs = fs.readdirSync(this.basePath).filter((e) => {
const fullDir = path.join(this.basePath, e);
@ -45,13 +49,13 @@ class LibraryManager {
async fetchUnimportedGameVersions(
libraryBasePath: string,
versions: Array<GameVersion>
versions: Array<GameVersion>,
) {
const gameDir = path.join(this.basePath, libraryBasePath);
const versionsDirs = fs.readdirSync(gameDir);
const importedVersionDirs = versions.map((e) => e.versionName);
const unimportedVersions = versionsDirs.filter(
(e) => !importedVersionDirs.includes(e)
(e) => !importedVersionDirs.includes(e),
);
return unimportedVersions;
@ -79,10 +83,10 @@ class LibraryManager {
noVersions: e.versions.length == 0,
unimportedVersions: await this.fetchUnimportedGameVersions(
e.libraryBasePath,
e.versions
e.versions,
),
},
}))
})),
);
}
@ -103,13 +107,13 @@ class LibraryManager {
const targetDir = path.join(this.basePath, game.libraryBasePath);
if (!fs.existsSync(targetDir))
throw new Error(
"Game in database, but no physical directory? Something is very very wrong..."
"Game in database, but no physical directory? Something is very very wrong...",
);
const versions = fs.readdirSync(targetDir);
const currentVersions = game.versions.map((e) => e.versionName);
const unimportedVersions = versions.filter(
(e) => !currentVersions.includes(e)
(e) => !currentVersions.includes(e),
);
return unimportedVersions;
}
@ -123,7 +127,7 @@ class LibraryManager {
const targetDir = path.join(
this.basePath,
game.libraryBasePath,
versionName
versionName,
);
if (!fs.existsSync(targetDir)) return undefined;
@ -171,7 +175,7 @@ class LibraryManager {
const finalChoice = sortedOptions[0];
const finalChoiceRelativePath = path.relative(
targetDir,
finalChoice.filename
finalChoice.filename,
);
startupGuess = finalChoiceRelativePath;
platformGuess = finalChoice.platform;
@ -196,7 +200,7 @@ class LibraryManager {
gameId: string,
versionName: string,
metadata: { platform: string; setup: string; startup: string },
delta = false
delta = false,
) {
const taskId = `import:${gameId}:${versionName}`;
@ -233,7 +237,7 @@ class LibraryManager {
(err, manifest) => {
if (err) return reject(err);
resolve(manifest);
}
},
);
});