* feat: start of library backends * feat: update backend routes and create initializer * feat: add legacy library creation * fix: resolve frontend type errors * fix: runtime errors * fix: lint
This commit is contained in:
@@ -33,11 +33,12 @@ export default defineEventHandler(async (h3) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!game)
|
||||
if (!game || !game.libraryId)
|
||||
throw createError({ statusCode: 404, statusMessage: "Game ID not found" });
|
||||
|
||||
const unimportedVersions = await libraryManager.fetchUnimportedVersions(
|
||||
game.id,
|
||||
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
|
||||
game.libraryId,
|
||||
game.libraryPath,
|
||||
);
|
||||
|
||||
return { game, unimportedVersions };
|
||||
|
||||
@@ -6,5 +6,10 @@ export default defineEventHandler(async (h3) => {
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const unimportedGames = await libraryManager.fetchAllUnimportedGames();
|
||||
return { unimportedGames };
|
||||
const iterableUnimportedGames = Object.entries(unimportedGames)
|
||||
.map(([libraryId, gameArray]) =>
|
||||
gameArray.map((e) => ({ game: e, library: libraryId })),
|
||||
)
|
||||
.flat();
|
||||
return { unimportedGames: iterableUnimportedGames };
|
||||
});
|
||||
|
||||
@@ -1,37 +1,46 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
import metadataHandler from "~/server/internal/metadata";
|
||||
import type {
|
||||
GameMetadataSearchResult,
|
||||
GameMetadataSource,
|
||||
} from "~/server/internal/metadata/types";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:game:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
const ImportGameBody = type({
|
||||
library: "string",
|
||||
path: "string",
|
||||
["metadata?"]: {
|
||||
id: "string",
|
||||
sourceId: "string",
|
||||
name: "string",
|
||||
},
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
export default defineEventHandler<{ body: typeof ImportGameBody.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:game:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const path = body.path;
|
||||
const metadata = body.metadata as GameMetadataSearchResult &
|
||||
GameMetadataSource;
|
||||
if (!path)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Path missing from body",
|
||||
});
|
||||
const { library, path, metadata } = await readValidatedBody(
|
||||
h3,
|
||||
ImportGameBody,
|
||||
);
|
||||
|
||||
const validPath = await libraryManager.checkUnimportedGamePath(path);
|
||||
if (!validPath)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid unimported game path",
|
||||
});
|
||||
if (!path)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Path missing from body",
|
||||
});
|
||||
|
||||
if (!metadata || !metadata.id || !metadata.sourceId) {
|
||||
console.log(metadata);
|
||||
return await metadataHandler.createGameWithoutMetadata(path);
|
||||
} else {
|
||||
return await metadataHandler.createGame(metadata, path);
|
||||
}
|
||||
});
|
||||
const valid = await libraryManager.checkUnimportedGamePath(library, path);
|
||||
if (!valid)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid library or game.",
|
||||
});
|
||||
|
||||
if (!metadata) {
|
||||
return await metadataHandler.createGameWithoutMetadata(library, path);
|
||||
} else {
|
||||
return await metadataHandler.createGame(metadata, library, path);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
@@ -13,8 +14,17 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Missing id in request params",
|
||||
});
|
||||
|
||||
const unimportedVersions =
|
||||
await libraryManager.fetchUnimportedVersions(gameId);
|
||||
const game = await prisma.game.findUnique({
|
||||
where: { id: gameId },
|
||||
select: { libraryId: true, libraryPath: true },
|
||||
});
|
||||
if (!game || !game.libraryId)
|
||||
throw createError({ statusCode: 404, statusMessage: "Game not found" });
|
||||
|
||||
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
|
||||
game.libraryId,
|
||||
game.libraryPath,
|
||||
);
|
||||
if (!unimportedVersions)
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
|
||||
|
||||
|
||||
@@ -9,31 +9,31 @@ const ImportVersion = type({
|
||||
version: "string",
|
||||
|
||||
platform: "string",
|
||||
launch: "string?",
|
||||
launchArgs: "string?",
|
||||
setup: "string?",
|
||||
setupArgs: "string?",
|
||||
onlySetup: "boolean?",
|
||||
delta: "boolean?",
|
||||
umuId: "string?",
|
||||
launch: "string = ''",
|
||||
launchArgs: "string = ''",
|
||||
setup: "string = ''",
|
||||
setupArgs: "string = ''",
|
||||
onlySetup: "boolean = false",
|
||||
delta: "boolean = false",
|
||||
umuId: "string = ''",
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const body = await readValidatedBody(h3, ImportVersion);
|
||||
const gameId = body.id;
|
||||
const versionName = body.version;
|
||||
|
||||
const platform = body.platform;
|
||||
const launch = body.launch ?? "";
|
||||
const launchArgs = body.launchArgs ?? "";
|
||||
const setup = body.setup ?? "";
|
||||
const setupArgs = body.setupArgs ?? "";
|
||||
const onlySetup = body.onlySetup ?? false;
|
||||
const delta = body.delta ?? false;
|
||||
const umuId = body.umuId ?? "";
|
||||
const {
|
||||
id,
|
||||
version,
|
||||
platform,
|
||||
launch,
|
||||
launchArgs,
|
||||
setup,
|
||||
setupArgs,
|
||||
onlySetup,
|
||||
delta,
|
||||
umuId,
|
||||
} = await readValidatedBody(h3, ImportVersion);
|
||||
|
||||
const platformParsed = parsePlatform(platform);
|
||||
if (!platformParsed)
|
||||
@@ -41,7 +41,7 @@ export default defineEventHandler(async (h3) => {
|
||||
|
||||
if (delta) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: { gameId: gameId, platform: platformParsed, delta: false },
|
||||
where: { gameId: id, platform: platformParsed, delta: false },
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
throw createError({
|
||||
@@ -66,7 +66,7 @@ export default defineEventHandler(async (h3) => {
|
||||
}
|
||||
|
||||
// startup & delta require more complex checking logic
|
||||
const taskId = await libraryManager.importVersion(gameId, versionName, {
|
||||
const taskId = await libraryManager.importVersion(id, version, {
|
||||
platform,
|
||||
onlySetup,
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import cacheHandler from "~/server/internal/cache";
|
||||
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;
|
||||
|
||||
const gameLookupCache = cacheHandler.createCache<{
|
||||
libraryId: string | null;
|
||||
libraryPath: string;
|
||||
}>("downloadGameLookupCache");
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const gameId = query.id?.toString();
|
||||
@@ -18,36 +22,40 @@ export default defineEventHandler(async (h3) => {
|
||||
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" });
|
||||
let game = await gameLookupCache.getItem(gameId);
|
||||
if (!game) {
|
||||
game = await prisma.game.findUnique({
|
||||
where: {
|
||||
id: gameId,
|
||||
},
|
||||
select: {
|
||||
libraryId: true,
|
||||
libraryPath: true,
|
||||
},
|
||||
});
|
||||
if (!game || !game.libraryId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
|
||||
|
||||
const versionDir = path.join(
|
||||
libraryManager.fetchLibraryPath(),
|
||||
game.libraryBasePath,
|
||||
versionName,
|
||||
);
|
||||
if (!fs.existsSync(versionDir))
|
||||
await gameLookupCache.setItem(gameId, game);
|
||||
}
|
||||
|
||||
if (!game.libraryId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid version name",
|
||||
statusCode: 500,
|
||||
statusMessage: "Somehow, we got here.",
|
||||
});
|
||||
|
||||
const gameFile = path.join(versionDir, filename);
|
||||
if (!fs.existsSync(gameFile))
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid game file" });
|
||||
|
||||
const gameFileStats = fs.statSync(gameFile);
|
||||
const peek = await libraryManager.peekFile(
|
||||
game.libraryId,
|
||||
game.libraryPath,
|
||||
versionName,
|
||||
filename,
|
||||
);
|
||||
if (!peek)
|
||||
throw createError({ status: 400, statusMessage: "Failed to peek file" });
|
||||
|
||||
const start = chunkIndex * chunkSize;
|
||||
const end = Math.min((chunkIndex + 1) * chunkSize, gameFileStats.size);
|
||||
const end = Math.min((chunkIndex + 1) * chunkSize, peek.size);
|
||||
const currentChunkSize = end - start;
|
||||
setHeader(h3, "Content-Length", currentChunkSize);
|
||||
|
||||
@@ -57,7 +65,18 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Invalid chunk index",
|
||||
});
|
||||
|
||||
const gameReadStream = fs.createReadStream(gameFile, { start, end: end - 1 }); // end needs to be offset by 1
|
||||
const gameReadStream = await libraryManager.readFile(
|
||||
game.libraryId,
|
||||
game.libraryPath,
|
||||
versionName,
|
||||
filename,
|
||||
{ start, end: end - 1 },
|
||||
); // end needs to be offset by 1
|
||||
if (!gameReadStream)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Failed to create stream",
|
||||
});
|
||||
|
||||
return sendStream(h3, gameReadStream);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user