import z from "zod"; import { IJob, JobContext } from "../task-queue"; import { ActiveGameSchema, ActiveGameType } from "@/bun/types/typesc.schema"; import { db, events, plugins } from "../app"; import * as appSchema from "@schema/app"; import { eq, sql } from "drizzle-orm"; import { spawn } from 'node:child_process'; export class LaunchGameJob implements IJob, "playing"> { static id = "launch-game" as const; static dataSchema = z.optional(ActiveGameSchema); group = "launch-game"; activeGame?: ActiveGameType; gameId: number; validCommand: CommandEntry; gameSource: string; gameSourceId: string; constructor(gameId: number, validCommand: CommandEntry, source: string, sourceId: string) { this.gameId = gameId; this.validCommand = validCommand; this.gameSource = source; this.gameSourceId = sourceId; } async start (context: JobContext, ActiveGameType, "playing">) { const localGame = await db.query.games.findFirst({ where: eq(appSchema.games.id, this.gameId), columns: { name: true, source_id: true, source: true } }); const commandArgs = await plugins.hooks.games.emulatorLaunch.promise({ autoValidCommand: this.validCommand, game: { source: this.gameSource, id: this.gameId } }); const command = commandArgs ? this.validCommand.metadata.emulatorBin ?? this.validCommand.command : this.validCommand.command; await new Promise((resolve, reject) => { const game = spawn(command, commandArgs, { shell: true, cwd: this.validCommand.startDir, signal: context.abortSignal }); game.stdout.on('data', data => console.log(data)); game.on('close', (code) => { resolve(code); }); game.on('error', e => { console.error(e); reject(e); }); this.activeGame = { process: game, name: localGame?.name ?? "Unknown", gameId: this.gameId, command: this.validCommand }; const updatePlayed = async (source: string, id: string) => { await db.update(appSchema.games).set({ last_played: new Date() }).where(eq(appSchema.games.id, this.gameId)); await plugins.hooks.games.updatePlayed.promise({ source, id }).then(v => { if (v) events.emit('notification', { message: "Updated Last Played", type: 'success' }); }); }; if (this.gameSource !== 'local') { updatePlayed(this.gameSource, this.gameSourceId); } else if (localGame?.source && localGame?.source !== 'local' && localGame.source_id) { updatePlayed(localGame.source, localGame.source_id); } }); /* Old spawn lanching, cases issues, needs to be ran as shell const cmd = Array.from(validCommand.command.command.matchAll(/(".*?"|[^\s"]+)/g)).map(m => m[0]); const game = setActiveGame({ process: Bun.spawn({ cmd, env: { ...process.env }, onExit (subprocess, exitCode, signalCode, error) { events.emit('activegameexit', { subprocess, exitCode, signalCode, error }); }, stdin: "ignore", stdout: "inherit", stderr: "inherit", }), name: localGame?.name ?? "Unknown", gameId: validCommand.gameId, command: validCommand.command.command }); await game.process.exited; if (game.process.exitCode && game.process.exitCode > 0) { return status('Internal Server Error'); }*/ } exposeData () { return this.activeGame; } }