mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-27 02:04:39 +10:00
added download chunk endpoint
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user