diff --git a/i18n/locales/en_us.json b/i18n/locales/en_us.json index 292affba..715020dd 100644 --- a/i18n/locales/en_us.json +++ b/i18n/locales/en_us.json @@ -755,6 +755,7 @@ "cleanupSessionsDescription": "Cleans up expired sessions to save space and ensure security.", "cleanupSessionsName": "Clean up sessions." }, + "utilityTitle": "Utility tasks", "viewTask": "View {arrow}", "weeklyScheduledTitle": "Weekly scheduled tasks" } diff --git a/pages/admin/task/index.vue b/pages/admin/task/index.vue index 4e07d868..7772c319 100644 --- a/pages/admin/task/index.vue +++ b/pages/admin/task/index.vue @@ -166,6 +166,44 @@ +

+ {{ $t("tasks.admin.utilityTitle") }} +

+ @@ -185,7 +223,7 @@ definePageMeta({ const { t } = useI18n(); -const { runningTasks, historicalTasks, dailyTasks, weeklyTasks } = +const { runningTasks, historicalTasks, dailyTasks, weeklyTasks, other } = await $dropFetch("/api/v1/admin/task"); const liveRunningTasks = ref( @@ -219,9 +257,9 @@ const scheduledTasks: { name: "", description: "", }, - debug: { - name: "", - description: "", + "import:check-integrity": { + name: "Check Integrity", + description: "Re-imports all versions and updates their manifests.", }, }; diff --git a/server/api/v1/admin/task/index.get.ts b/server/api/v1/admin/task/index.get.ts index 18d2b418..5afcf5c2 100644 --- a/server/api/v1/admin/task/index.get.ts +++ b/server/api/v1/admin/task/index.get.ts @@ -1,6 +1,7 @@ import aclManager from "~/server/internal/acls"; import prisma from "~/server/internal/db/database"; import taskHandler from "~/server/internal/tasks"; +import type { TaskGroup } from "~/server/internal/tasks/group"; export default defineEventHandler(async (h3) => { const allowed = await aclManager.allowSystemACL(h3, ["task:read"]); @@ -38,6 +39,7 @@ export default defineEventHandler(async (h3) => { }); const dailyTasks = await taskHandler.dailyTasks(); const weeklyTasks = await taskHandler.weeklyTasks(); + const other: TaskGroup[] = ["import:check-integrity"]; - return { runningTasks, historicalTasks, dailyTasks, weeklyTasks }; + return { runningTasks, historicalTasks, dailyTasks, weeklyTasks, other }; }); diff --git a/server/internal/services/torrential/droplet-interface.ts b/server/internal/services/torrential/droplet-interface.ts index 8b8e5a2b..30509fdd 100644 --- a/server/internal/services/torrential/droplet-interface.ts +++ b/server/internal/services/torrential/droplet-interface.ts @@ -217,7 +217,7 @@ class DropletInterfaceManager { run: async (message) => { const callbacks = this.callbacks.get(message.messageId); if (!callbacks) { - logger.warn( + logger.debug( `got a droplet message with old message id: ${message.type}, ${message.messageId}`, ); return undefined; diff --git a/server/internal/tasks/group.ts b/server/internal/tasks/group.ts index 4ddef804..36c29853 100644 --- a/server/internal/tasks/group.ts +++ b/server/internal/tasks/group.ts @@ -17,8 +17,8 @@ export const taskGroups = { "import:version": { concurrency: true, }, - debug: { - concurrency: true, + "import:check-integrity": { + concurrency: false, }, } as const; diff --git a/server/internal/tasks/index.ts b/server/internal/tasks/index.ts index 8a47483f..c34715b9 100644 --- a/server/internal/tasks/index.ts +++ b/server/internal/tasks/index.ts @@ -1,11 +1,6 @@ import type { MinimumRequestObject } from "~/server/h3"; import type { GlobalACL } from "../acls"; import aclManager from "../acls"; - -import cleanupInvites from "./registry/invitations"; -import cleanupSessions from "./registry/sessions"; -import checkUpdate from "./registry/update"; -import cleanupObjects from "./registry/objects"; import { taskGroups, type TaskGroup } from "./group"; import prisma from "../db/database"; import { ArkErrors, type } from "arktype"; @@ -13,6 +8,12 @@ import pino from "pino"; import { logger } from "~/server/internal/logging"; import { Writable } from "node:stream"; +import cleanupInvites from "./registry/invitations"; +import cleanupSessions from "./registry/sessions"; +import checkUpdate from "./registry/update"; +import cleanupObjects from "./registry/objects"; +import checkIntegrity from "./registry/check-integrity"; + type TaskActionLink = `${string}:${string}`; // a task that has been run @@ -65,7 +66,7 @@ class TaskHandler { this.saveScheduledTask(cleanupSessions); this.saveScheduledTask(checkUpdate); this.saveScheduledTask(cleanupObjects); - //this.saveScheduledTask(debug); + this.saveScheduledTask(checkIntegrity); } /** diff --git a/server/internal/tasks/registry/check-integrity.ts b/server/internal/tasks/registry/check-integrity.ts new file mode 100644 index 00000000..d7ec62bc --- /dev/null +++ b/server/internal/tasks/registry/check-integrity.ts @@ -0,0 +1,80 @@ +import prisma from "~/server/internal/db/database"; +import { defineDropTask, wrapTaskContext } from ".."; +import { libraryManager } from "../../library"; + +export default defineDropTask({ + buildId: () => `import:check-integrity:${new Date().toISOString()}`, + name: "Check version integrity", + acls: ["system:import:version:read"], + taskGroup: "import:check-integrity", + async run({ progress, logger, addAction }) { + const versions = await prisma.gameVersion.findMany({ + where: { + versionPath: { + not: null, + }, + }, + select: { + versionId: true, + versionPath: true, + displayName: true, + game: { + select: { + libraryId: true, + libraryPath: true, + mName: true, + }, + }, + }, + }); + + logger.info(`Checking version integrity for ${versions.length} versions`); + + let i = 0; + const progressStep = 100 / versions.length; + for (const version of versions) { + const displayName = `${version.game.mName} ${version.displayName ?? version.versionPath}`; + logger.info(`Starting integrity check for ${displayName}`); + + const library = await libraryManager.getLibrary(version.game.libraryId); + if (!library) { + logger.warn(`No library for ${displayName}`); + continue; + } + + const min = i * progressStep; + const max = (i + 1) * progressStep; + const taskContext = wrapTaskContext( + { progress, logger, addAction }, + { min, max, prefix: `re-check ${displayName}` }, + ); + + const manifest = await library.generateDropletManifest( + version.game.libraryPath, + version.versionPath!, + taskContext.progress, + (value) => { + taskContext.logger.info(value); + }, + ); + + // SAFETY: this is requested from the database + // eslint-disable-next-line drop/no-prisma-delete + await prisma.gameVersion.update({ + where: { + versionId: version.versionId, + }, + data: { + versionId: crypto.randomUUID(), + dropletManifest: manifest, + }, + }); + + logger.info(`Finished integrity check for ${displayName}`); + i++; + } + + logger.info("Done"); + progress(100); + }, +}); diff --git a/server/internal/tasks/registry/debug.ts b/server/internal/tasks/registry/debug.ts deleted file mode 100644 index 9cd8a4a2..00000000 --- a/server/internal/tasks/registry/debug.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { defineDropTask } from ".."; - -export default defineDropTask({ - buildId: () => `debug:${new Date().toISOString()}`, - name: "Debug Task", - acls: ["system:maintenance:read"], - taskGroup: "debug", - async run({ progress, logger }) { - const amount = 1000; - for (let i = 0; i < amount; i++) { - progress((i / amount) * 100); - logger.info(`dajksdkajd ${i}`); - logger.warn("warning"); - logger.error("error\nmultiline and stuff\nwoah more lines"); - await new Promise((r) => setTimeout(r, 1500)); - } - }, -});