feat: migrating games to level

This commit is contained in:
Chubby Granny Chaser
2025-01-20 10:09:49 +00:00
parent 1f0e195854
commit d760d0139d
47 changed files with 219 additions and 941 deletions

View File

@@ -1,31 +1,25 @@
import { registerEvent } from "../register-event";
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
import { dataSource } from "@main/data-source";
import { DownloadQueue, Game } from "@main/entity";
import { PythonRPC } from "@main/services/python-rpc";
import { db, levelKeys } from "@main/level";
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
const databaseOperations = dataSource
.transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.getRepository(DownloadQueue).delete({});
await transactionalEntityManager.getRepository(Game).delete({});
await db.batch([
{
type: "del",
key: levelKeys.auth,
},
{
type: "del",
key: levelKeys.user,
},
]);
})
const databaseOperations = db
.batch([
{
type: "del",
key: levelKeys.auth,
},
{
type: "del",
key: levelKeys.user,
},
])
.then(() => {
/* Removes all games being played */
gamesPlaytime.clear();
return Promise.all([gamesSublevel.clear(), downloadsSublevel.clear()]);
});
/* Cancels any ongoing downloads */

View File

@@ -1,44 +0,0 @@
import { Document as YMLDocument } from "yaml";
import { Game } from "@main/entity";
import path from "node:path";
export const generateYML = (game: Game) => {
const slugifiedGameTitle = game.title.replace(/\s/g, "-").toLocaleLowerCase();
const doc = new YMLDocument({
name: game.title,
game_slug: slugifiedGameTitle,
slug: `${slugifiedGameTitle}-installer`,
version: "Installer",
runner: "wine",
script: {
game: {
prefix: "$GAMEDIR",
arch: "win64",
working_dir: "$GAMEDIR",
},
installer: [
{
task: {
name: "create_prefix",
arch: "win64",
prefix: "$GAMEDIR",
},
},
{
task: {
executable: path.join(
game.downloadPath!,
game.folderName!,
"setup.exe"
),
name: "wineexec",
prefix: "$GAMEDIR",
},
},
],
},
});
return doc.toString();
};

View File

@@ -1,5 +1,3 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event";
import type { Game, GameShop } from "@types";
@@ -8,7 +6,7 @@ import { steamGamesWorker } from "@main/workers";
import { createGame } from "@main/services/library-sync";
import { steamUrlBuilder } from "@shared";
import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
import { gamesSublevel, levelKeys } from "@main/level";
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent,
@@ -16,41 +14,42 @@ const addGameToLibrary = async (
objectId: string,
title: string
) => {
return gameRepository
.update(
{
objectID: objectId,
},
{
shop,
status: null,
isDeleted: false,
}
)
.then(async ({ affected }) => {
if (!affected) {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null;
if (game) {
await downloadsSublevel.del(gameKey);
const game: Game = {
title,
iconUrl,
objectId,
shop,
};
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
}
updateLocalUnlockedAchivements(game!);
createGame(game!).catch(() => {});
await gamesSublevel.put(gameKey, {
...game,
isDeleted: false,
});
} else {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
const iconUrl = steamGame?.clientIcon
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null;
const game: Game = {
title,
iconUrl,
objectId,
shop,
remoteId: null,
isDeleted: false,
playTimeInMilliseconds: 0,
lastTimePlayed: null,
};
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
updateLocalUnlockedAchivements(game!);
createGame(game!).catch(() => {});
}
};
registerEvent("addGameToLibrary", addGameToLibrary);

View File

@@ -1,13 +1,23 @@
import { registerEvent } from "../register-event";
import { gamesSublevel } from "@main/level";
import { downloadsSublevel, gamesSublevel } from "@main/level";
const getLibrary = async () => {
// TODO: Add sorting
return gamesSublevel
.values()
.iterator()
.all()
.then((results) => {
return results.filter((game) => game.isDeleted === false);
return Promise.all(
results
.filter(([_key, game]) => game.isDeleted === false)
.map(async ([key, game]) => {
const download = await downloadsSublevel.get(key);
return {
...game,
download,
};
})
);
});
};

View File

@@ -1,13 +1,11 @@
import { shell } from "electron";
import path from "node:path";
import fs from "node:fs";
import { writeFile } from "node:fs/promises";
import { spawnSync, exec } from "node:child_process";
import { generateYML } from "../helpers/generate-lutris-yaml";
import { getDownloadsPath } from "../helpers/get-downloads-path";
import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import { downloadsSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const executeGameInstaller = (filePath: string) => {
@@ -29,18 +27,18 @@ const openGameInstaller = async (
shop: GameShop,
objectId: string
) => {
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
const downloadKey = levelKeys.game(shop, objectId);
const download = await downloadsSublevel.get(downloadKey);
if (!game || game.isDeleted || !game.folderName) return true;
if (!download || !download.folderName) return true;
const gamePath = path.join(
game.downloadPath ?? (await getDownloadsPath()),
game.folderName!
download.downloadPath ?? (await getDownloadsPath()),
download.folderName!
);
if (!fs.existsSync(gamePath)) {
// TODO: LEVELDB Remove download?
// await gameRepository.update({ id: gameId }, { status: null });
await downloadsSublevel.del(downloadKey);
return true;
}
@@ -70,13 +68,6 @@ const openGameInstaller = async (
);
}
if (spawnSync("which", ["lutris"]).status === 0) {
const ymlPath = path.join(gamePath, "setup.yml");
await writeFile(ymlPath, generateYML(game));
exec(`lutris --install "${ymlPath}"`);
return true;
}
shell.openPath(gamePath);
return true;
};

View File

@@ -1,22 +1,31 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event";
import { shell } from "electron";
import { parseExecutablePath } from "../helpers/parse-executable-path";
import { levelKeys } from "@main/level";
import { gamesSublevel } from "@main/level";
import { GameShop } from "@types";
const openGame = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number,
shop: GameShop,
objectId: string,
executablePath: string,
launchOptions: string | null
) => {
// TODO: revisit this for launchOptions
const parsedPath = parseExecutablePath(executablePath);
await gameRepository.update(
{ id: gameId },
{ executablePath: parsedPath, launchOptions }
);
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
executablePath: parsedPath,
launchOptions,
});
shell.openPath(parsedPath);
};

View File

@@ -1,31 +1,17 @@
import { registerEvent } from "../register-event";
import { DownloadManager } from "@main/services";
import { dataSource } from "@main/data-source";
import { DownloadQueue, Game } from "@main/entity";
import { GameShop } from "@types";
import { downloadsSublevel, levelKeys } from "@main/level";
const cancelGameDownload = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number
shop: GameShop,
objectId: string
) => {
await dataSource.transaction(async (transactionalEntityManager) => {
await DownloadManager.cancelDownload(gameId);
await DownloadManager.cancelDownload(shop, objectId);
await transactionalEntityManager.getRepository(DownloadQueue).delete({
game: { id: gameId },
});
await transactionalEntityManager.getRepository(Game).update(
{
id: gameId,
},
{
status: "removed",
bytesDownloaded: 0,
progress: 0,
}
);
});
await downloadsSublevel.del(levelKeys.game(shop, objectId));
};
registerEvent("cancelGameDownload", cancelGameDownload);

View File

@@ -1,17 +1,25 @@
import { downloadsSublevel } from "@main/level";
import { levelKeys } from "@main/level";
import { registerEvent } from "../register-event";
import { DownloadManager } from "@main/services";
import { gameRepository } from "@main/repository";
import type { GameShop } from "@types";
const pauseGameSeed = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number
shop: GameShop,
objectId: string
) => {
await gameRepository.update(gameId, {
status: "complete",
const downloadKey = levelKeys.game(shop, objectId);
const download = await downloadsSublevel.get(downloadKey);
if (!download) return;
await downloadsSublevel.put(downloadKey, {
...download,
shouldSeed: false,
});
await DownloadManager.pauseSeeding(gameId);
await DownloadManager.pauseSeeding(download);
};
registerEvent("pauseGameSeed", pauseGameSeed);

View File

@@ -1,11 +1,9 @@
import { Not } from "typeorm";
import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository";
import { DownloadManager } from "@main/services";
import { dataSource } from "@main/data-source";
import { DownloadQueue, Game } from "@main/entity";
const resumeGameDownload = async (
_event: Electron.IpcMainInvokeEvent,

View File

@@ -1,29 +1,24 @@
import { downloadsSublevel } from "@main/level";
import { levelKeys } from "@main/level";
import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository";
import { DownloadManager } from "@main/services";
import { Downloader } from "@shared";
import type { GameShop } from "@types";
const resumeGameSeed = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number
shop: GameShop,
objectId: string
) => {
const game = await gameRepository.findOne({
where: {
id: gameId,
isDeleted: false,
downloader: Downloader.Torrent,
progress: 1,
},
});
const download = await downloadsSublevel.get(levelKeys.game(shop, objectId));
if (!game) return;
if (!download) return;
await gameRepository.update(gameId, {
status: "seeding",
await downloadsSublevel.put(levelKeys.game(shop, objectId), {
...download,
shouldSeed: true,
});
await DownloadManager.resumeSeeding(game);
await DownloadManager.resumeSeeding(download);
};
registerEvent("resumeGameSeed", resumeGameSeed);