feat: Implemented AppImage building
This commit is contained in:
parent
d8f471dadc
commit
6a288f765e
38 changed files with 1036 additions and 147 deletions
|
|
@ -8,7 +8,7 @@ import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
|||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import Conf from "conf";
|
||||
import projectPackage from '~/package.json';
|
||||
import { Notification, SERVER_URL, SettingsSchema, SettingsType } from "@shared/constants";
|
||||
import { Notification, SettingsSchema, SettingsType } from "@shared/constants";
|
||||
import { client } from "@clients/romm/client.gen";
|
||||
import * as schema from "./schema/app";
|
||||
import * as emulatorSchema from "./schema/emulators";
|
||||
|
|
@ -18,14 +18,17 @@ import os from 'node:os';
|
|||
import { ActiveGame } from "../types/types";
|
||||
import EventEmitter from "node:events";
|
||||
import { ErrorLike } from "bun";
|
||||
import { getErrorMessage } from "../utils";
|
||||
import { appPath, getErrorMessage } from "../utils";
|
||||
import { DrizzleSqliteDODatabase } from "drizzle-orm/durable-sqlite";
|
||||
|
||||
export const config = new Conf<SettingsType>({
|
||||
projectName: projectPackage.name,
|
||||
projectSuffix: 'bun',
|
||||
schema: Object.fromEntries(Object.entries(SettingsSchema.shape).map(([key, schema]) => [key, schema.toJSONSchema() as any])) as any,
|
||||
defaults: SettingsSchema.parse({}),
|
||||
defaults: SettingsSchema.parse({
|
||||
downloadPath: path.join(os.homedir(), "gameflow"),
|
||||
windowSize: { width: 1280, height: 800 }
|
||||
} satisfies SettingsType),
|
||||
});
|
||||
export const customEmulators = new Conf<Record<string, string>>({
|
||||
projectName: projectPackage.name,
|
||||
|
|
@ -41,6 +44,7 @@ export const customEmulators = new Conf<Record<string, 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);
|
||||
const fileCookieStore = new FileCookieStore(path.join(path.dirname(config.path), 'cookies.json'));
|
||||
console.log("Cookie Jar Path Located At: ", fileCookieStore.filePath);
|
||||
export const jar = new CookieJar(fileCookieStore);
|
||||
|
|
@ -48,8 +52,8 @@ await fs.mkdir(config.get('downloadPath'), { recursive: true });
|
|||
let sqlite: Database;
|
||||
export let db: DrizzleSqliteDODatabase<typeof schema>;
|
||||
await reloadDatabase();
|
||||
migrate(db!, { migrationsFolder: "./drizzle" });
|
||||
const emulatorsSqlite = new Database(`./vendors/es-de/emulators.${os.platform()}.${os.arch()}.sqlite`, { readonly: true });
|
||||
migrate(db!, { migrationsFolder: appPath("./drizzle") });
|
||||
const emulatorsSqlite = new Database(appPath(`./vendors/es-de/emulators.${os.platform()}.${os.arch()}.sqlite`), { readonly: true });
|
||||
export const emulatorsDb = drizzle(emulatorsSqlite, { schema: emulatorSchema });
|
||||
export const taskQueue = new TaskQueue();
|
||||
config.onDidChange('rommAddress', v => client.setConfig({ baseUrl: v }));
|
||||
|
|
|
|||
|
|
@ -13,10 +13,40 @@ import buildStatusResponse, { getValidLaunchCommandsForGame } from "./services/s
|
|||
import { errorToResponse } from "elysia/adapter/bun/handler";
|
||||
import { launchCommand } from "./services/launchGameService";
|
||||
import { getErrorMessage } from "@/bun/utils";
|
||||
import sharp from 'sharp';
|
||||
import { Jimp } from 'jimp';
|
||||
|
||||
async function processImage (img: string | Buffer | ArrayBuffer, { blur, width, height }: { blur?: number, width?: number, height?: number; })
|
||||
{
|
||||
if (blur)
|
||||
{
|
||||
const jimp = await Jimp.read(img);
|
||||
if (width)
|
||||
{
|
||||
jimp.resize({ w: width, h: height });
|
||||
}
|
||||
if (height)
|
||||
{
|
||||
jimp.resize({ w: width, h: height });
|
||||
}
|
||||
if (blur)
|
||||
{
|
||||
jimp.blur(blur);
|
||||
}
|
||||
|
||||
return jimp.getBuffer('image/png');
|
||||
}
|
||||
|
||||
if (typeof img === 'string')
|
||||
{
|
||||
const rommFetch = await fetch(img);
|
||||
return rommFetch;
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
export default new Elysia()
|
||||
.get('/game/local/:id/cover', async ({ params: { id }, query: { blur, width, height }, set }) =>
|
||||
.get('/game/local/:id/cover', async ({ params: { id }, query, set }) =>
|
||||
{
|
||||
const coverBlob = await db.query.games.findFirst({ columns: { cover: true, cover_type: true }, where: eq(schema.games.id, id) });
|
||||
if (!coverBlob || !coverBlob.cover)
|
||||
|
|
@ -28,22 +58,32 @@ export default new Elysia()
|
|||
set.headers["content-type"] = coverBlob.cover_type;
|
||||
}
|
||||
|
||||
return sharp(coverBlob.cover).resize({ width, height, withoutEnlargement: true }).blur(blur);
|
||||
return processImage(coverBlob.cover, query);
|
||||
/*return sharp(coverBlob.cover)
|
||||
.resize({ width, height, withoutEnlargement: true })
|
||||
.blur(blur)
|
||||
.toBuffer();*/
|
||||
}, {
|
||||
params: z.object({ id: z.coerce.number() }),
|
||||
query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() })
|
||||
})
|
||||
.get('/image/:source/*', async ({ params: { source, "*": path }, query: { blur, width, height } }) =>
|
||||
.get('/image/:source/*', async ({ params: { source, "*": path }, query }) =>
|
||||
{
|
||||
if (source === 'romm')
|
||||
{
|
||||
const rommAdress = config.get('rommAddress');
|
||||
return processImage(`${rommAdress}/${path}`, query);
|
||||
|
||||
/*
|
||||
const rommFetch = await fetch(`${rommAdress}/${path}`);
|
||||
return sharp(await rommFetch.arrayBuffer()).resize({ width, height, withoutEnlargement: true }).sharpen().blur(blur);
|
||||
return sharp(await rommFetch.arrayBuffer())
|
||||
.resize({ width, height, withoutEnlargement: true })
|
||||
.blur(blur)
|
||||
.toBuffer();*/
|
||||
}
|
||||
return status('Not Found');
|
||||
}, { query: z.object({ blur: z.coerce.number().optional(), width: z.coerce.number().optional(), height: z.coerce.number().optional() }) })
|
||||
.get('/screenshot/:id', async ({ params: { id }, query: { blur, width, height }, set }) =>
|
||||
.get('/screenshot/:id', async ({ params: { id }, query, set }) =>
|
||||
{
|
||||
const screenshot = await db.query.screenshots.findFirst({ where: eq(schema.screenshots.id, id), columns: { content: true, type: true } });
|
||||
if (screenshot)
|
||||
|
|
@ -52,8 +92,10 @@ export default new Elysia()
|
|||
{
|
||||
set.headers["content-type"] = screenshot.type;
|
||||
}
|
||||
return sharp(screenshot.content).resize({ width, height, withoutEnlargement: true }).blur(blur);
|
||||
|
||||
return processImage(screenshot.content, query);
|
||||
//return sharp(screenshot.content).resize({ width, height, withoutEnlargement: true }).blur(blur).toBuffer();
|
||||
//return screenshot.content;
|
||||
}
|
||||
|
||||
return status(404);
|
||||
|
|
@ -158,7 +200,7 @@ export default new Elysia()
|
|||
paths_screenshots: localGame.screenshots.map(s => `/api/romm/screenshot/${s.id}`),
|
||||
local: true,
|
||||
missing: !exists,
|
||||
platform_display_name: localGame.platform.name,
|
||||
platform_display_name: localGame.platform?.name,
|
||||
summary: localGame.summary,
|
||||
source: localGame.source,
|
||||
source_id: localGame.source_id,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const games = sqliteTable('games', {
|
|||
export const gamesRelations = relations(games, ({ many, one }) => ({
|
||||
screenshots: many(screenshots),
|
||||
platform: one(platforms, {
|
||||
fields: [games.id],
|
||||
fields: [games.platform_id],
|
||||
references: [platforms.id]
|
||||
})
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ export const system = new Elysia({ prefix: '/api/system' })
|
|||
})
|
||||
.get('/info', async () =>
|
||||
{
|
||||
let source = 'unknown';
|
||||
if (process.env.APPIMAGE === 'true')
|
||||
source = "AppImage";
|
||||
if (process.env.FLATPAK === 'true')
|
||||
source = "Flatpak";
|
||||
|
||||
return {
|
||||
homeDir: os.homedir(),
|
||||
user: os.userInfo().username,
|
||||
|
|
@ -42,6 +48,7 @@ export const system = new Elysia({ prefix: '/api/system' })
|
|||
hostname: os.hostname(),
|
||||
steamDeck: process.env.SteamDeck,
|
||||
machine: os.machine(),
|
||||
source
|
||||
};
|
||||
})
|
||||
.get('/notifications', ({ set }) =>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ async function cleanup ()
|
|||
|
||||
if (Bun.env.HEADLESS)
|
||||
{
|
||||
// Called by outside force
|
||||
process.on('message', ({ type }) =>
|
||||
{
|
||||
if (type === 'exitapp')
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
});
|
||||
// Called by user
|
||||
events.on('exitapp', () =>
|
||||
{
|
||||
process.send?.({ type: 'exitapp' });
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { SERVER_PORT } from "../shared/constants";
|
|||
import path from 'node:path';
|
||||
import appInfo from '../../package.json';
|
||||
import { host } from "./utils/host";
|
||||
import { appPath } from "./utils";
|
||||
|
||||
export function RunBunServer ()
|
||||
{
|
||||
|
|
@ -10,9 +11,9 @@ export function RunBunServer ()
|
|||
port: SERVER_PORT,
|
||||
hostname: host,
|
||||
routes: {
|
||||
"/": Bun.file("./dist/index.html"),
|
||||
"/": Bun.file(appPath("./dist/index.html")),
|
||||
// Serve a file by lazily loading it into memory
|
||||
"/favicon.ico": Bun.file("./dist/favicon.ico"),
|
||||
"/favicon.ico": Bun.file(appPath("./dist/favicon.ico")),
|
||||
"/.well-known/appspecific/com.chrome.devtools.json": new Response(
|
||||
JSON.stringify({
|
||||
name: appInfo.name,
|
||||
|
|
@ -30,7 +31,7 @@ export function RunBunServer ()
|
|||
fetch: async (req) =>
|
||||
{
|
||||
const url = new URL(req.url);
|
||||
return new Response(Bun.file(`./${path.join('dist', url.pathname)}`));
|
||||
return new Response(Bun.file(appPath(`./${path.join('dist', url.pathname)}`)));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
import { $ } from 'bun';
|
||||
import path from 'node:path';
|
||||
|
||||
export function checkRunning (pid: number)
|
||||
{
|
||||
|
|
@ -39,6 +40,19 @@ export async function isSteamDeck ()
|
|||
}
|
||||
}
|
||||
|
||||
export function appPath (input: string): string
|
||||
{
|
||||
if (path.isAbsolute(input))
|
||||
{
|
||||
return input;
|
||||
}
|
||||
if (process.env.APPDIR)
|
||||
{
|
||||
return path.join(process.env.APPDIR ?? '', 'usr', 'share', input);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
export async function openExternal (target: string)
|
||||
{
|
||||
if (process.platform === "linux")
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export async function BuildParams (data: { configPath: string; })
|
|||
args.push('--disabled-features=WindowControlsOverlay,navigationControls,Translate,msUndersideButton');
|
||||
args.push(`--profile-directory=Default`);
|
||||
|
||||
if (Bun.env.NODE_ENV !== 'production')
|
||||
if (Bun.env.NODE_ENV === 'development')
|
||||
{
|
||||
args.push('--auto-open-devtools-for-tabs');
|
||||
args.push('--remote-debugging-port=9222');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { SERVER_URL } from "@/shared/constants";
|
||||
import Webview from "@rcompat/webview";
|
||||
import { host } from "../utils/host";
|
||||
|
||||
export default function (webview: Webview)
|
||||
export default function (webview: { navigate: (url: string) => void; run: () => void; destroy: () => void; })
|
||||
{
|
||||
self.addEventListener('message', (e) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,33 @@ import Webview from "@rcompat/webview";
|
|||
import platform from "@rcompat/webview/linux-x64";
|
||||
import webviewWorkerBase from "./base";
|
||||
|
||||
console.log("Launching Webview");
|
||||
const webview = new Webview({ debug: import.meta.env.NODE_ENV === 'development', platform });
|
||||
webviewWorkerBase(webview);
|
||||
if (process.env.FLATPAK_BUILD === "true")
|
||||
{
|
||||
let webview: Bun.Subprocess | undefined = undefined;
|
||||
let hostUrl: string | undefined = undefined;
|
||||
webviewWorkerBase({
|
||||
navigate: (url) =>
|
||||
{
|
||||
hostUrl = url;
|
||||
|
||||
}, destroy: () => webview?.kill(), run: () =>
|
||||
{
|
||||
webview = Bun.spawn(["webview", hostUrl ?? ''], {
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
onExit ()
|
||||
{
|
||||
postMessage({ data: 'destroyed' });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else
|
||||
{
|
||||
console.log("Launching Webview");
|
||||
const webview = new Webview({ debug: import.meta.env.NODE_ENV === 'development', platform });
|
||||
webviewWorkerBase(webview);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue