added download chunk endpoint

This commit is contained in:
DecDuck
2024-10-23 12:03:31 +11:00
parent 9c9109b86a
commit eb4a9ae65f
3 changed files with 70 additions and 14 deletions
+51
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);
});
@@ -5,7 +5,7 @@ import prisma from "../db/database";
export type EventHandlerFunction<T> = ( export type EventHandlerFunction<T> = (
h3: H3Event<EventHandlerRequest>, h3: H3Event<EventHandlerRequest>,
utils: ClientUtils utils: ClientUtils,
) => Promise<T> | T; ) => Promise<T> | T;
type ClientUtils = { type ClientUtils = {
@@ -18,7 +18,7 @@ const NONCE_LENIENCE = 30_000;
export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) { export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
return defineEventHandler(async (h3) => { return defineEventHandler(async (h3) => {
const header = await getHeader(h3, "Authorization"); const header = getHeader(h3, "Authorization");
if (!header) throw createError({ statusCode: 403 }); if (!header) throw createError({ statusCode: 403 });
const [method, ...parts] = header.split(" "); const [method, ...parts] = header.split(" ");
@@ -49,6 +49,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
const ca = h3.context.ca; const ca = h3.context.ca;
const certBundle = await ca.fetchClientCertificate(clientId); const certBundle = await ca.fetchClientCertificate(clientId);
// This does the blacklist check already
if (!certBundle) if (!certBundle)
throw createError({ throw createError({
statusCode: 403, statusCode: 403,
@@ -80,7 +81,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
}); });
if (!client) if (!client)
throw new Error( throw new Error(
"client util fetch client broke - this should NOT happen" "client util fetch client broke - this should NOT happen",
); );
return client; return client;
} }
@@ -95,7 +96,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
if (!client) if (!client)
throw new Error( throw new Error(
"client util fetch client broke - this should NOT happen" "client util fetch client broke - this should NOT happen",
); );
return client.user; return client.user;
+14 -10
View File
@@ -22,6 +22,10 @@ class LibraryManager {
this.basePath = process.env.LIBRARY ?? "./.data/library"; this.basePath = process.env.LIBRARY ?? "./.data/library";
} }
fetchLibraryPath() {
return this.basePath;
}
async fetchAllUnimportedGames() { async fetchAllUnimportedGames() {
const dirs = fs.readdirSync(this.basePath).filter((e) => { const dirs = fs.readdirSync(this.basePath).filter((e) => {
const fullDir = path.join(this.basePath, e); const fullDir = path.join(this.basePath, e);
@@ -45,13 +49,13 @@ class LibraryManager {
async fetchUnimportedGameVersions( async fetchUnimportedGameVersions(
libraryBasePath: string, libraryBasePath: string,
versions: Array<GameVersion> versions: Array<GameVersion>,
) { ) {
const gameDir = path.join(this.basePath, libraryBasePath); const gameDir = path.join(this.basePath, libraryBasePath);
const versionsDirs = fs.readdirSync(gameDir); const versionsDirs = fs.readdirSync(gameDir);
const importedVersionDirs = versions.map((e) => e.versionName); const importedVersionDirs = versions.map((e) => e.versionName);
const unimportedVersions = versionsDirs.filter( const unimportedVersions = versionsDirs.filter(
(e) => !importedVersionDirs.includes(e) (e) => !importedVersionDirs.includes(e),
); );
return unimportedVersions; return unimportedVersions;
@@ -79,10 +83,10 @@ class LibraryManager {
noVersions: e.versions.length == 0, noVersions: e.versions.length == 0,
unimportedVersions: await this.fetchUnimportedGameVersions( unimportedVersions: await this.fetchUnimportedGameVersions(
e.libraryBasePath, e.libraryBasePath,
e.versions e.versions,
), ),
}, },
})) })),
); );
} }
@@ -103,13 +107,13 @@ class LibraryManager {
const targetDir = path.join(this.basePath, game.libraryBasePath); const targetDir = path.join(this.basePath, game.libraryBasePath);
if (!fs.existsSync(targetDir)) if (!fs.existsSync(targetDir))
throw new Error( 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 versions = fs.readdirSync(targetDir);
const currentVersions = game.versions.map((e) => e.versionName); const currentVersions = game.versions.map((e) => e.versionName);
const unimportedVersions = versions.filter( const unimportedVersions = versions.filter(
(e) => !currentVersions.includes(e) (e) => !currentVersions.includes(e),
); );
return unimportedVersions; return unimportedVersions;
} }
@@ -123,7 +127,7 @@ class LibraryManager {
const targetDir = path.join( const targetDir = path.join(
this.basePath, this.basePath,
game.libraryBasePath, game.libraryBasePath,
versionName versionName,
); );
if (!fs.existsSync(targetDir)) return undefined; if (!fs.existsSync(targetDir)) return undefined;
@@ -171,7 +175,7 @@ class LibraryManager {
const finalChoice = sortedOptions[0]; const finalChoice = sortedOptions[0];
const finalChoiceRelativePath = path.relative( const finalChoiceRelativePath = path.relative(
targetDir, targetDir,
finalChoice.filename finalChoice.filename,
); );
startupGuess = finalChoiceRelativePath; startupGuess = finalChoiceRelativePath;
platformGuess = finalChoice.platform; platformGuess = finalChoice.platform;
@@ -196,7 +200,7 @@ class LibraryManager {
gameId: string, gameId: string,
versionName: string, versionName: string,
metadata: { platform: string; setup: string; startup: string }, metadata: { platform: string; setup: string; startup: string },
delta = false delta = false,
) { ) {
const taskId = `import:${gameId}:${versionName}`; const taskId = `import:${gameId}:${versionName}`;
@@ -233,7 +237,7 @@ class LibraryManager {
(err, manifest) => { (err, manifest) => {
if (err) return reject(err); if (err) return reject(err);
resolve(manifest); resolve(manifest);
} },
); );
}); });