import { TaskQueue } from "./task-queue"; import { Database } from "bun:sqlite"; import { CookieJar } from 'tough-cookie'; import FileCookieStore from 'tough-cookie-file-store'; import path from 'node:path'; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import { BunSQLiteDatabase, drizzle } from "drizzle-orm/bun-sqlite"; import Conf from "conf"; import projectPackage from '~/package.json'; import { SettingsSchema, SettingsType } from "@shared/constants"; import { client } from "@clients/romm/client.gen"; import * as schema from "@schema/app"; import cacheSchema from "@schema/cache"; import * as emulatorSchema from "@schema/emulators"; import os from 'node:os'; import EventEmitter from "node:events"; import { appPath } from "../utils"; import { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite"; import { ensureDir } from "fs-extra"; import { PluginManager } from "./plugins/plugin-manager"; import registerPlugins from "./plugins/register-plugins"; import controls from './controls/controls'; import { RunAPIServer } from "./rpc"; import { RunBunServer } from "../server"; import ReloadPluginsJob from "./jobs/reload-plugins-job"; export let config: Conf; export let customEmulators: Conf>; export let fileCookieStore: FileCookieStore; export let jar: CookieJar; let sqlite: Database; export let cachePath: string; let cacheSqlite: Database; export let db: DrizzleSqliteDODatabase; export let cache: DrizzleSqliteDODatabase; let emulatorsSqlite: Database; export let emulatorsDb: BunSQLiteDatabase & { $client: Database; }; export let taskQueue: TaskQueue; export let plugins: PluginManager; export let events: EventEmitter; let controlsHandle: { cleanup: () => void; }; let api: { cleanup: () => Promise; }; let bunServer: { cleanup: () => Promise; } | undefined; let cleannedUp = false; let cleaningUp = false; export async function load () { config = new Conf({ projectName: projectPackage.name, projectSuffix: 'bun', cwd: process.env.CONFIG_CWD, schema: Object.fromEntries(Object.entries(SettingsSchema.shape).map(([key, schema]) => [key, schema.toJSONSchema() as any])) as any, defaults: SettingsSchema.parse({ downloadPath: process.env.DEFAULT_DOWNLOAD_PATH ?? path.join(os.homedir(), "gameflow"), windowSize: { width: 1280, height: 800 } }), }); customEmulators = new Conf>({ projectName: projectPackage.name, projectSuffix: 'bun', cwd: process.env.CONFIG_CWD, configName: 'custom-emulators', rootSchema: { "type": "object", "additionalProperties": { "type": "string" } } }); console.log("Config Path Located At: ", config.path); console.log("Custom Emulator Paths Located At: ", customEmulators.path); console.log("App Directory is ", process.env.APPDIR); console.log("Cache Path is ", cachePath); cachePath = path.join(os.tmpdir(), 'gameflow', 'cache.sqlite'); fileCookieStore = new FileCookieStore(path.join(path.dirname(config.path), 'cookies.json')); console.log("Cookie Jar Path Located At: ", fileCookieStore.filePath); jar = new CookieJar(fileCookieStore); taskQueue = new TaskQueue(); events = new EventEmitter(); emulatorsSqlite = new Database(appPath(`./vendors/es-de/emulators.${os.platform()}.${os.arch()}.sqlite`), { readonly: true }); emulatorsDb = drizzle(emulatorsSqlite, { schema: emulatorSchema }); await reloadDatabase(); plugins = new PluginManager(); api = await RunAPIServer(); await registerPlugins(plugins); taskQueue.enqueue(ReloadPluginsJob.id, new ReloadPluginsJob()); controlsHandle = await controls(); if (!process.env.PUBLIC_ACCESS) bunServer = await RunBunServer(); config.onDidChange('downloadPath', () => reloadDatabase()); config.onDidChange('rommAddress', v => client.setConfig({ baseUrl: v })); } export async function cleanup () { if (cleaningUp) throw new Error("Already Cleaning Up"); cleaningUp = true; if (cleannedUp) throw new Error("Already Cleaned Up. Skipping"); console.log("Cleaning Up"); await bunServer?.cleanup(); await api.cleanup(); await taskQueue.close(); await plugins.cleanup(); controlsHandle.cleanup(); cacheSqlite.close(); emulatorsSqlite.close(); sqlite.close(); config._closeWatcher(); customEmulators._closeWatcher(); console.log("Finished Cleaning Up"); cleannedUp = true; } export async function reloadDatabase () { await ensureDir(config.get('downloadPath')); sqlite = new Database(path.join(config.get('downloadPath'), 'db.sqlite'), { create: true, readwrite: true }); await ensureDir(path.join(os.tmpdir(), 'gameflow')); console.log("Loaded Cache from: ", cachePath); cacheSqlite = new Database(cachePath, { create: true, readwrite: true }); db = drizzle(sqlite, { schema }); cache = drizzle(cacheSqlite, { schema: cacheSchema }); migrate(db!, { migrationsFolder: appPath("./drizzle") }); sqlite.run("PRAGMA foreign_keys = ON;"); await cache.run(` CREATE TABLE IF NOT EXISTS item_cache ( key TEXT PRIMARY KEY, data TEXT NOT NULL, expire_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); }