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") }}
+
+
+ -
+
+
+
+
+ {{ scheduledTasks[task].name }}
+
+
+
+ {{ scheduledTasks[task].description }}
+
+
+
+
+
+
@@ -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));
- }
- },
-});