feat: migrating games to leveldb

This commit is contained in:
Chubby Granny Chaser 2025-01-19 17:59:39 +00:00
parent c115040e90
commit 1f0e195854
No known key found for this signature in database
34 changed files with 410 additions and 343 deletions

View File

@ -1,19 +1,14 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { GameShop } from "@types"; import type { GameShop } from "@types";
import { Ludusavi } from "@main/services"; import { Ludusavi } from "@main/services";
import { gameRepository } from "@main/repository"; import { gamesSublevel, levelKeys } from "@main/level";
const getGameBackupPreview = async ( const getGameBackupPreview = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
objectId: string, objectId: string,
shop: GameShop shop: GameShop
) => { ) => {
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: {
objectID: objectId,
shop,
},
});
return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath); return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath);
}; };

View File

@ -10,7 +10,8 @@ import os from "node:os";
import { backupsPath } from "@main/constants"; import { backupsPath } from "@main/constants";
import { app } from "electron"; import { app } from "electron";
import { normalizePath } from "@main/helpers"; import { normalizePath } from "@main/helpers";
import { gameRepository } from "@main/repository"; import { gamesSublevel } from "@main/level";
import { levelKeys } from "@main/level";
const bundleBackup = async ( const bundleBackup = async (
shop: GameShop, shop: GameShop,
@ -46,12 +47,7 @@ const uploadSaveGame = async (
shop: GameShop, shop: GameShop,
downloadOptionTitle: string | null downloadOptionTitle: string | null
) => { ) => {
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: {
objectID: objectId,
shop,
},
});
const bundleLocation = await bundleBackup( const bundleLocation = await bundleBackup(
shop, shop,

View File

@ -2,18 +2,19 @@ import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import type { GameShop } from "@types"; import type { Game, GameShop } from "@types";
import { steamGamesWorker } from "@main/workers"; import { steamGamesWorker } from "@main/workers";
import { createGame } from "@main/services/library-sync"; import { createGame } from "@main/services/library-sync";
import { steamUrlBuilder } from "@shared"; import { steamUrlBuilder } from "@shared";
import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements"; import { updateLocalUnlockedAchivements } from "@main/services/achievements/update-local-unlocked-achivements";
import { gamesSublevel, levelKeys } from "@main/level";
const addGameToLibrary = async ( const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string, objectId: string,
title: string, title: string
shop: GameShop
) => { ) => {
return gameRepository return gameRepository
.update( .update(
@ -36,17 +37,15 @@ const addGameToLibrary = async (
? steamUrlBuilder.icon(objectId, steamGame.clientIcon) ? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
: null; : null;
await gameRepository.insert({ const game: Game = {
title, title,
iconUrl, iconUrl,
objectID: objectId, objectId,
shop, shop,
}); };
}
const game = await gameRepository.findOne({ await gamesSublevel.put(levelKeys.game(shop, objectId), game);
where: { objectID: objectId }, }
});
updateLocalUnlockedAchivements(game!); updateLocalUnlockedAchivements(game!);

View File

@ -1,10 +1,11 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { logger } from "@main/services"; import { logger } from "@main/services";
import sudo from "sudo-prompt"; import sudo from "sudo-prompt";
import { app } from "electron"; import { app } from "electron";
import { PythonRPC } from "@main/services/python-rpc"; import { PythonRPC } from "@main/services/python-rpc";
import { ProcessPayload } from "@main/services/download/types"; import { ProcessPayload } from "@main/services/download/types";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const getKillCommand = (pid: number) => { const getKillCommand = (pid: number) => {
if (process.platform == "win32") { if (process.platform == "win32") {
@ -16,15 +17,14 @@ const getKillCommand = (pid: number) => {
const closeGame = async ( const closeGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
const processes = const processes =
(await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data || (await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data ||
[]; [];
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: { id: gameId, isDeleted: false },
});
if (!game) return; if (!game) return;

View File

@ -1,18 +1,18 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { IsNull, Not } from "typeorm";
import createDesktopShortcut from "create-desktop-shortcuts"; import createDesktopShortcut from "create-desktop-shortcuts";
import path from "node:path"; import path from "node:path";
import { app } from "electron"; import { app } from "electron";
import { removeSymbolsFromName } from "@shared"; import { removeSymbolsFromName } from "@shared";
import { GameShop } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
const createGameShortcut = async ( const createGameShortcut = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
id: number shop: GameShop,
objectId: string
): Promise<boolean> => { ): Promise<boolean> => {
const game = await gameRepository.findOne({ const gameKey = levelKeys.game(shop, objectId);
where: { id, executablePath: Not(IsNull()) }, const game = await gamesSublevel.get(gameKey);
});
if (game) { if (game) {
const filePath = game.executablePath; const filePath = game.executablePath;

View File

@ -1,37 +1,27 @@
import path from "node:path"; import path from "node:path";
import fs from "node:fs"; import fs from "node:fs";
import { gameRepository } from "@main/repository";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { logger } from "@main/services"; import { logger } from "@main/services";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { GameShop } from "@types";
import { downloadsSublevel, levelKeys } from "@main/level";
const deleteGameFolder = async ( const deleteGameFolder = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
): Promise<void> => { ): Promise<void> => {
const game = await gameRepository.findOne({ const downloadKey = levelKeys.game(shop, objectId);
where: [
{
id: gameId,
isDeleted: false,
status: "removed",
},
{
id: gameId,
progress: 1,
isDeleted: false,
},
],
});
if (!game) return; const download = await downloadsSublevel.get(downloadKey);
if (game.folderName) { if (!download) return;
if (download.folderName) {
const folderPath = path.join( const folderPath = path.join(
game.downloadPath ?? (await getDownloadsPath()), download.downloadPath ?? (await getDownloadsPath()),
game.folderName download.folderName
); );
if (fs.existsSync(folderPath)) { if (fs.existsSync(folderPath)) {
@ -52,10 +42,7 @@ const deleteGameFolder = async (
} }
} }
await gameRepository.update( await downloadsSublevel.del(downloadKey);
{ id: gameId },
{ downloadPath: null, folderName: null, status: null, progress: 0 }
);
}; };
registerEvent("deleteGameFolder", deleteGameFolder); registerEvent("deleteGameFolder", deleteGameFolder);

View File

@ -1,16 +1,17 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { levelKeys } from "@main/level";
import { gamesSublevel } from "@main/level";
import type { GameShop } from "@types";
const getGameByObjectId = async ( const getGameByObjectId = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string objectId: string
) => ) => {
gameRepository.findOne({ const gameKey = levelKeys.game(shop, objectId);
where: { const game = await gamesSublevel.get(gameKey);
objectID: objectId,
isDeleted: false, return game;
}, };
});
registerEvent("getGameByObjectId", getGameByObjectId); registerEvent("getGameByObjectId", getGameByObjectId);

View File

@ -1,17 +1,14 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel } from "@main/level";
const getLibrary = async () => const getLibrary = async () => {
gameRepository.find({ // TODO: Add sorting
where: { return gamesSublevel
isDeleted: false, .values()
}, .all()
relations: { .then((results) => {
downloadQueue: true, return results.filter((game) => game.isDeleted === false);
}, });
order: { };
createdAt: "desc",
},
});
registerEvent("getLibrary", getLibrary); registerEvent("getLibrary", getLibrary);

View File

@ -1,14 +1,14 @@
import { shell } from "electron"; import { shell } from "electron";
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const openGameExecutablePath = async ( const openGameExecutablePath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: { id: gameId, isDeleted: false },
});
if (!game || !game.executablePath) return; if (!game || !game.executablePath) return;

View File

@ -1,16 +1,16 @@
import { shell } from "electron"; import { shell } from "electron";
import path from "node:path"; import path from "node:path";
import { gameRepository } from "@main/repository";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { GameShop } from "@types";
import { gamesSublevel, levelKeys } from "@main/level";
const openGameInstallerPath = async ( const openGameInstallerPath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: { id: gameId, isDeleted: false },
});
if (!game || !game.folderName || !game.downloadPath) return true; if (!game || !game.folderName || !game.downloadPath) return true;

View File

@ -4,11 +4,11 @@ import fs from "node:fs";
import { writeFile } from "node:fs/promises"; import { writeFile } from "node:fs/promises";
import { spawnSync, exec } from "node:child_process"; import { spawnSync, exec } from "node:child_process";
import { gameRepository } from "@main/repository";
import { generateYML } from "../helpers/generate-lutris-yaml"; import { generateYML } from "../helpers/generate-lutris-yaml";
import { getDownloadsPath } from "../helpers/get-downloads-path"; import { getDownloadsPath } from "../helpers/get-downloads-path";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import { GameShop } from "@types";
const executeGameInstaller = (filePath: string) => { const executeGameInstaller = (filePath: string) => {
if (process.platform === "win32") { if (process.platform === "win32") {
@ -26,13 +26,12 @@ const executeGameInstaller = (filePath: string) => {
const openGameInstaller = async ( const openGameInstaller = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
const game = await gameRepository.findOne({ const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
where: { id: gameId, isDeleted: false },
});
if (!game || !game.folderName) return true; if (!game || game.isDeleted || !game.folderName) return true;
const gamePath = path.join( const gamePath = path.join(
game.downloadPath ?? (await getDownloadsPath()), game.downloadPath ?? (await getDownloadsPath()),
@ -40,7 +39,8 @@ const openGameInstaller = async (
); );
if (!fs.existsSync(gamePath)) { if (!fs.existsSync(gamePath)) {
await gameRepository.update({ id: gameId }, { status: null }); // TODO: LEVELDB Remove download?
// await gameRepository.update({ id: gameId }, { status: null });
return true; return true;
} }

View File

@ -1,26 +1,27 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository"; import { HydraApi } from "@main/services";
import { HydraApi, logger } from "@main/services"; import { levelKeys } from "@main/level";
import { gamesSublevel } from "@main/level";
import type { GameShop } from "@types";
const removeGameFromLibrary = async ( const removeGameFromLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
gameRepository.update( const gameKey = levelKeys.game(shop, objectId);
{ id: gameId }, const game = await gamesSublevel.get(gameKey);
{ isDeleted: true, executablePath: null }
);
removeRemoveGameFromLibrary(gameId).catch((err) => { if (game) {
logger.error("removeRemoveGameFromLibrary", err); await gamesSublevel.put(gameKey, {
}); ...game,
}; isDeleted: true,
executablePath: null,
});
const removeRemoveGameFromLibrary = async (gameId: number) => { if (game?.remoteId) {
const game = await gameRepository.findOne({ where: { id: gameId } }); HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
}
if (game?.remoteId) {
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
} }
}; };

View File

@ -1,21 +1,15 @@
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gameRepository } from "../../repository"; import { downloadsSublevel } from "@main/level";
import { GameShop } from "@types";
import { levelKeys } from "@main/level";
const removeGame = async ( const removeGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
await gameRepository.update( const downloadKey = levelKeys.game(shop, objectId);
{ await downloadsSublevel.del(downloadKey);
id: gameId,
},
{
status: "removed",
downloadPath: null,
bytesDownloaded: 0,
progress: 0,
}
);
}; };
registerEvent("removeGame", removeGame); registerEvent("removeGame", removeGame);

View File

@ -1,17 +1,22 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files"; import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
import fs from "fs"; import fs from "fs";
import { achievementsLogger, HydraApi, WindowManager } from "@main/services"; import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
import { getUnlockedAchievements } from "../user/get-unlocked-achievements"; import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
import { gameAchievementsSublevel, levelKeys } from "@main/level"; import {
gameAchievementsSublevel,
gamesSublevel,
levelKeys,
} from "@main/level";
import type { GameShop } from "@types";
const resetGameAchievements = async ( const resetGameAchievements = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number shop: GameShop,
objectId: string
) => { ) => {
try { try {
const game = await gameRepository.findOne({ where: { id: gameId } }); const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
if (!game) return; if (!game) return;
@ -24,37 +29,34 @@ const resetGameAchievements = async (
} }
} }
const levelKey = levelKeys.game(game.shop, game.objectID); const levelKey = levelKeys.game(game.shop, game.objectId);
await gameAchievementsSublevel await gameAchievementsSublevel
.get(levelKey) .get(levelKey)
.then(async (gameAchievements) => { .then(async (gameAchievements) => {
if (gameAchievements) { if (gameAchievements) {
await gameAchievementsSublevel.put( await gameAchievementsSublevel.put(levelKey, {
levelKeys.game(game.shop, game.objectID), ...gameAchievements,
{ unlockedAchievements: [],
...gameAchievements, });
unlockedAchievements: [],
}
);
} }
}); });
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then( await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
() => () =>
achievementsLogger.log( achievementsLogger.log(
`Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}` `Deleted achievements from ${game.remoteId} - ${game.objectId} - ${game.title}`
) )
); );
const gameAchievements = await getUnlockedAchievements( const gameAchievements = await getUnlockedAchievements(
game.objectID, game.objectId,
game.shop, game.shop,
true true
); );
WindowManager.mainWindow?.webContents.send( WindowManager.mainWindow?.webContents.send(
`on-update-achievements-${game.objectID}-${game.shop}`, `on-update-achievements-${game.objectId}-${game.shop}`,
gameAchievements gameAchievements
); );
} catch (error) { } catch (error) {

View File

@ -1,13 +1,24 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel } from "@main/level";
import { levelKeys } from "@main/level";
import type { GameShop } from "@types";
const selectGameWinePrefix = async ( const selectGameWinePrefix = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
id: number, shop: GameShop,
objectId: string,
winePrefixPath: string | null winePrefixPath: string | null
) => { ) => {
return gameRepository.update({ id }, { winePrefixPath: winePrefixPath }); const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
winePrefixPath: winePrefixPath,
});
}; };
registerEvent("selectGameWinePrefix", selectGameWinePrefix); registerEvent("selectGameWinePrefix", selectGameWinePrefix);

View File

@ -1,25 +1,27 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { parseExecutablePath } from "../helpers/parse-executable-path"; import { parseExecutablePath } from "../helpers/parse-executable-path";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const updateExecutablePath = async ( const updateExecutablePath = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
id: number, shop: GameShop,
objectId: string,
executablePath: string | null executablePath: string | null
) => { ) => {
const parsedPath = executablePath const parsedPath = executablePath
? parseExecutablePath(executablePath) ? parseExecutablePath(executablePath)
: null; : null;
return gameRepository.update( const gameKey = levelKeys.game(shop, objectId);
{
id, const game = await gamesSublevel.get(gameKey);
}, if (!game) return;
{
executablePath: parsedPath, await gamesSublevel.put(gameKey, {
} ...game,
); executablePath: parsedPath,
});
}; };
registerEvent("updateExecutablePath", updateExecutablePath); registerEvent("updateExecutablePath", updateExecutablePath);

View File

@ -1,19 +1,24 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { levelKeys } from "@main/level";
import { gamesSublevel } from "@main/level";
import { GameShop } from "@types";
const updateLaunchOptions = async ( const updateLaunchOptions = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
id: number, shop: GameShop,
objectId: string,
launchOptions: string | null launchOptions: string | null
) => { ) => {
return gameRepository.update( const gameKey = levelKeys.game(shop, objectId);
{
id, const game = await gamesSublevel.get(gameKey);
},
{ if (game) {
await gamesSublevel.put(gameKey, {
...game,
launchOptions: launchOptions?.trim() != "" ? launchOptions : null, launchOptions: launchOptions?.trim() != "" ? launchOptions : null,
} });
); }
}; };
registerEvent("updateLaunchOptions", updateLaunchOptions); registerEvent("updateLaunchOptions", updateLaunchOptions);

View File

@ -1,13 +1,17 @@
import { gameRepository } from "@main/repository";
import { registerEvent } from "../register-event"; import { registerEvent } from "../register-event";
import { gamesSublevel } from "@main/level";
const verifyExecutablePathInUse = async ( const verifyExecutablePathInUse = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
executablePath: string executablePath: string
) => { ) => {
return gameRepository.findOne({ for await (const game of gamesSublevel.values()) {
where: { executablePath }, if (game.executablePath === executablePath) {
}); return true;
}
}
return false;
}; };
registerEvent("verifyExecutablePathInUse", verifyExecutablePathInUse); registerEvent("verifyExecutablePathInUse", verifyExecutablePathInUse);

View File

@ -1,3 +1,4 @@
export * from "./downloads";
export * from "./games"; export * from "./games";
export * from "./game-shop-cache"; export * from "./game-shop-cache";
export * from "./game-achievements"; export * from "./game-achievements";

View File

@ -9,4 +9,5 @@ export const levelKeys = {
gameShopCacheItem: (shop: GameShop, objectId: string, language: string) => gameShopCacheItem: (shop: GameShop, objectId: string, language: string) =>
`${shop}:${objectId}:${language}`, `${shop}:${objectId}:${language}`,
gameAchievements: "gameAchievements", gameAchievements: "gameAchievements",
downloads: "downloads",
}; };

View File

@ -1,16 +1,13 @@
import { DownloadManager, Ludusavi, startMainLoop } from "./services"; import { DownloadManager, Ludusavi, startMainLoop } from "./services";
import { import { userPreferencesRepository } from "./repository";
downloadQueueRepository,
gameRepository,
userPreferencesRepository,
} from "./repository";
import { UserPreferences } from "./entity"; import { UserPreferences } from "./entity";
import { RealDebridClient } from "./services/download/real-debrid"; import { RealDebridClient } from "./services/download/real-debrid";
import { HydraApi } from "./services/hydra-api"; import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync"; import { uploadGamesBatch } from "./services/library-sync";
import { Aria2 } from "./services/aria2"; import { Aria2 } from "./services/aria2";
import { downloadsSublevel } from "./level/sublevels/downloads";
import { sortBy } from "lodash-es";
import { Downloader } from "@shared"; import { Downloader } from "@shared";
import { IsNull, Not } from "typeorm";
const loadState = async (userPreferences: UserPreferences | null) => { const loadState = async (userPreferences: UserPreferences | null) => {
import("./events"); import("./events");
@ -27,25 +24,24 @@ const loadState = async (userPreferences: UserPreferences | null) => {
uploadGamesBatch(); uploadGamesBatch();
}); });
const [nextQueueItem] = await downloadQueueRepository.find({ const downloads = await downloadsSublevel
order: { .values()
id: "DESC", .all()
}, .then((games) => {
relations: { return sortBy(games, "timestamp", "DESC");
game: true, });
},
});
const seedList = await gameRepository.find({ const [nextItemOnQueue] = downloads;
where: {
shouldSeed: true,
downloader: Downloader.Torrent,
progress: 1,
uri: Not(IsNull()),
},
});
await DownloadManager.startRPC(nextQueueItem?.game, seedList); const downloadsToSeed = downloads.filter(
(download) =>
download.shouldSeed &&
download.downloader === Downloader.Torrent &&
download.progress === 1 &&
download.uri !== null
);
await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed);
startMainLoop(); startMainLoop();
}; };

View File

@ -14,16 +14,16 @@ import { achievementsLogger } from "../logger";
import { Cracker } from "@shared"; import { Cracker } from "@shared";
import { IsNull, Not } from "typeorm"; import { IsNull, Not } from "typeorm";
import { publishCombinedNewAchievementNotification } from "../notifications"; import { publishCombinedNewAchievementNotification } from "../notifications";
import { gamesSublevel } from "@main/level";
const fileStats: Map<string, number> = new Map(); const fileStats: Map<string, number> = new Map();
const fltFiles: Map<string, Set<string>> = new Map(); const fltFiles: Map<string, Set<string>> = new Map();
const watchAchievementsWindows = async () => { const watchAchievementsWindows = async () => {
const games = await gameRepository.find({ const games = await gamesSublevel
where: { .values()
isDeleted: false, .all()
}, .then((games) => games.filter((game) => !game.isDeleted));
});
if (games.length === 0) return; if (games.length === 0) return;
@ -32,7 +32,7 @@ const watchAchievementsWindows = async () => {
for (const game of games) { for (const game of games) {
const gameAchievementFiles: AchievementFile[] = []; const gameAchievementFiles: AchievementFile[] = [];
for (const objectId of getAlternativeObjectIds(game.objectID)) { for (const objectId of getAlternativeObjectIds(game.objectId)) {
gameAchievementFiles.push(...(achievementFiles.get(objectId) || [])); gameAchievementFiles.push(...(achievementFiles.get(objectId) || []));
gameAchievementFiles.push( gameAchievementFiles.push(

View File

@ -3,8 +3,8 @@ import fs from "node:fs";
import { app } from "electron"; import { app } from "electron";
import type { AchievementFile } from "@types"; import type { AchievementFile } from "@types";
import { Cracker } from "@shared"; import { Cracker } from "@shared";
import { Game } from "@main/entity";
import { achievementsLogger } from "../logger"; import { achievementsLogger } from "../logger";
import type { Game } from "@types";
const getAppDataPath = () => { const getAppDataPath = () => {
if (process.platform === "win32") { if (process.platform === "win32") {

View File

@ -7,7 +7,7 @@ import {
userPreferencesRepository, userPreferencesRepository,
} from "@main/repository"; } from "@main/repository";
import { publishDownloadCompleteNotification } from "../notifications"; import { publishDownloadCompleteNotification } from "../notifications";
import type { DownloadProgress } from "@types"; import type { Download, DownloadProgress } from "@types";
import { GofileApi, QiwiApi, DatanodesApi } from "../hosters"; import { GofileApi, QiwiApi, DatanodesApi } from "../hosters";
import { PythonRPC } from "../python-rpc"; import { PythonRPC } from "../python-rpc";
import { import {
@ -20,16 +20,20 @@ import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity
import { RealDebridClient } from "./real-debrid"; import { RealDebridClient } from "./real-debrid";
import path from "path"; import path from "path";
import { logger } from "../logger"; import { logger } from "../logger";
import { downloadsSublevel, levelKeys } from "@main/level";
export class DownloadManager { export class DownloadManager {
private static downloadingGameId: number | null = null; private static downloadingGameId: number | null = null;
public static async startRPC(game?: Game, initialSeeding?: Game[]) { public static async startRPC(
download?: Download,
downloadsToSeed?: Download[]
) {
PythonRPC.spawn( PythonRPC.spawn(
game?.status === "active" download?.status === "active"
? await this.getDownloadPayload(game).catch(() => undefined) ? await this.getDownloadPayload(download).catch(() => undefined)
: undefined, : undefined,
initialSeeding?.map((game) => ({ downloadsToSeed?.map((download) => ({
game_id: game.id, game_id: game.id,
url: game.uri!, url: game.uri!,
save_path: game.downloadPath!, save_path: game.downloadPath!,
@ -105,6 +109,7 @@ export class DownloadManager {
const game = await gameRepository.findOne({ const game = await gameRepository.findOne({
where: { id: gameId, isDeleted: false }, where: { id: gameId, isDeleted: false },
}); });
const userPreferences = await userPreferencesRepository.findOneBy({ const userPreferences = await userPreferencesRepository.findOneBy({
id: 1, id: 1,
}); });
@ -141,7 +146,8 @@ export class DownloadManager {
this.cancelDownload(gameId); this.cancelDownload(gameId);
} }
await downloadQueueRepository.delete({ game }); await downloadsSublevel.del(levelKeys.game(game.shop, game.objectId));
const [nextQueueItem] = await downloadQueueRepository.find({ const [nextQueueItem] = await downloadQueueRepository.find({
order: { order: {
id: "DESC", id: "DESC",

View File

@ -1,5 +1,16 @@
import { gameRepository } from "@main/repository"; import { gamesSublevel, levelKeys } from "@main/level";
export const clearGamesRemoteIds = () => { export const clearGamesRemoteIds = async () => {
return gameRepository.update({}, { remoteId: null }); const games = await gamesSublevel.values().all();
await gamesSublevel.batch(
games.map((game) => ({
type: "put",
key: levelKeys.game(game.shop, game.objectId),
value: {
...game,
remoteId: null,
},
}))
);
}; };

View File

@ -1,19 +1,21 @@
import { Game } from "@main/entity"; import type { Game } from "@types";
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
import { gameRepository } from "@main/repository"; import { gamesSublevel, levelKeys } from "@main/level";
export const createGame = async (game: Game) => { export const createGame = async (game: Game) => {
return HydraApi.post(`/profile/games`, { return HydraApi.post(`/profile/games`, {
objectId: game.objectID, objectId: game.objectId,
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds), playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
shop: game.shop, shop: game.shop,
lastTimePlayed: game.lastTimePlayed, lastTimePlayed: game.lastTimePlayed,
}).then((response) => { }).then((response) => {
const { id: remoteId, playTimeInMilliseconds, lastTimePlayed } = response; const { id: remoteId, playTimeInMilliseconds, lastTimePlayed } = response;
gameRepository.update( gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
{ objectID: game.objectID }, ...game,
{ remoteId, playTimeInMilliseconds, lastTimePlayed } remoteId,
); playTimeInMilliseconds,
lastTimePlayed,
});
}); });
}; };

View File

@ -1,4 +1,4 @@
import { Game } from "@main/entity"; import type { Game } from "@types";
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
export const updateGamePlaytime = async ( export const updateGamePlaytime = async (

View File

@ -1,15 +1,19 @@
import { gameRepository } from "@main/repository";
import { chunk } from "lodash-es"; import { chunk } from "lodash-es";
import { IsNull } from "typeorm";
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
import { mergeWithRemoteGames } from "./merge-with-remote-games"; import { mergeWithRemoteGames } from "./merge-with-remote-games";
import { WindowManager } from "../window-manager"; import { WindowManager } from "../window-manager";
import { AchievementWatcherManager } from "../achievements/achievement-watcher-manager"; import { AchievementWatcherManager } from "../achievements/achievement-watcher-manager";
import { gamesSublevel } from "@main/level";
export const uploadGamesBatch = async () => { export const uploadGamesBatch = async () => {
const games = await gameRepository.find({ const games = await gamesSublevel
where: { remoteId: IsNull(), isDeleted: false }, .values()
}); .all()
.then((results) => {
return results.filter(
(game) => !game.isDeleted && game.remoteId === null
);
});
const gamesChunks = chunk(games, 200); const gamesChunks = chunk(games, 200);
@ -18,7 +22,7 @@ export const uploadGamesBatch = async () => {
"/profile/games/batch", "/profile/games/batch",
chunk.map((game) => { chunk.map((game) => {
return { return {
objectId: game.objectID, objectId: game.objectId,
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds), playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),
shop: game.shop, shop: game.shop,
lastTimePlayed: game.lastTimePlayed, lastTimePlayed: game.lastTimePlayed,

View File

@ -1,12 +1,11 @@
import { gameRepository } from "@main/repository";
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { createGame, updateGamePlaytime } from "./library-sync"; import { createGame, updateGamePlaytime } from "./library-sync";
import type { GameRunning } from "@types"; import type { Game, GameRunning } from "@types";
import { PythonRPC } from "./python-rpc"; import { PythonRPC } from "./python-rpc";
import { Game } from "@main/entity";
import axios from "axios"; import axios from "axios";
import { exec } from "child_process"; import { exec } from "child_process";
import { ProcessPayload } from "./download/types"; import { ProcessPayload } from "./download/types";
import { gamesSublevel, levelKeys } from "@main/level";
const commands = { const commands = {
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`, findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
@ -14,7 +13,7 @@ const commands = {
}; };
export const gamesPlaytime = new Map< export const gamesPlaytime = new Map<
number, string,
{ lastTick: number; firstTick: number; lastSyncTick: number } { lastTick: number; firstTick: number; lastSyncTick: number }
>(); >();
@ -82,23 +81,28 @@ const findGamePathByProcess = (
const pathSet = processMap.get(executable.exe); const pathSet = processMap.get(executable.exe);
if (pathSet) { if (pathSet) {
pathSet.forEach((path) => { pathSet.forEach(async (path) => {
if (path.toLowerCase().endsWith(executable.name)) { if (path.toLowerCase().endsWith(executable.name)) {
gameRepository.update( const gameKey = levelKeys.game("steam", gameId);
{ objectID: gameId, shop: "steam" }, const game = await gamesSublevel.get(gameKey);
{ executablePath: path }
); if (game) {
gamesSublevel.put(gameKey, {
...game,
executablePath: path,
});
}
if (isLinuxPlatform) { if (isLinuxPlatform) {
exec(commands.findWineDir, (err, out) => { exec(commands.findWineDir, (err, out) => {
if (err) return; if (err) return;
gameRepository.update( if (game) {
{ objectID: gameId, shop: "steam" }, gamesSublevel.put(gameKey, {
{ ...game,
winePrefixPath: out.trim().replace("/drive_c/windows", ""), winePrefixPath: out.trim().replace("/drive_c/windows", ""),
} });
); }
}); });
} }
} }
@ -159,11 +163,12 @@ const getSystemProcessMap = async () => {
}; };
export const watchProcesses = async () => { export const watchProcesses = async () => {
const games = await gameRepository.find({ const games = await gamesSublevel
where: { .values()
isDeleted: false, .all()
}, .then((results) => {
}); return results.filter((game) => game.isDeleted === false);
});
if (!games.length) return; if (!games.length) return;
@ -172,8 +177,8 @@ export const watchProcesses = async () => {
for (const game of games) { for (const game of games) {
const executablePath = game.executablePath; const executablePath = game.executablePath;
if (!executablePath) { if (!executablePath) {
if (gameExecutables[game.objectID]) { if (gameExecutables[game.objectId]) {
findGamePathByProcess(processMap, game.objectID); findGamePathByProcess(processMap, game.objectId);
} }
continue; continue;
} }
@ -185,12 +190,12 @@ export const watchProcesses = async () => {
const hasProcess = processMap.get(executable)?.has(executablePath); const hasProcess = processMap.get(executable)?.has(executablePath);
if (hasProcess) { if (hasProcess) {
if (gamesPlaytime.has(game.id)) { if (gamesPlaytime.has(`${game.shop}-${game.objectId}`)) {
onTickGame(game); onTickGame(game);
} else { } else {
onOpenGame(game); onOpenGame(game);
} }
} else if (gamesPlaytime.has(game.id)) { } else if (gamesPlaytime.has(`${game.shop}-${game.objectId}`)) {
onCloseGame(game); onCloseGame(game);
} }
} }
@ -215,7 +220,7 @@ export const watchProcesses = async () => {
function onOpenGame(game: Game) { function onOpenGame(game: Game) {
const now = performance.now(); const now = performance.now();
gamesPlaytime.set(game.id, { gamesPlaytime.set(`${game.shop}-${game.objectId}`, {
lastTick: now, lastTick: now,
firstTick: now, firstTick: now,
lastSyncTick: now, lastSyncTick: now,
@ -230,16 +235,23 @@ function onOpenGame(game: Game) {
function onTickGame(game: Game) { function onTickGame(game: Game) {
const now = performance.now(); const now = performance.now();
const gamePlaytime = gamesPlaytime.get(game.id)!; const gamePlaytime = gamesPlaytime.get(`${game.shop}-${game.objectId}`)!;
const delta = now - gamePlaytime.lastTick; const delta = now - gamePlaytime.lastTick;
gameRepository.update(game.id, { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game,
playTimeInMilliseconds: game.playTimeInMilliseconds + delta, playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
lastTimePlayed: new Date(), lastTimePlayed: new Date(),
}); });
gamesPlaytime.set(game.id, { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game,
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
lastTimePlayed: new Date(),
});
gamesPlaytime.set(`${game.shop}-${game.objectId}`, {
...gamePlaytime, ...gamePlaytime,
lastTick: now, lastTick: now,
}); });
@ -255,7 +267,7 @@ function onTickGame(game: Game) {
gamePromise gamePromise
.then(() => { .then(() => {
gamesPlaytime.set(game.id, { gamesPlaytime.set(`${game.shop}-${game.objectId}`, {
...gamePlaytime, ...gamePlaytime,
lastSyncTick: now, lastSyncTick: now,
}); });
@ -265,8 +277,8 @@ function onTickGame(game: Game) {
} }
const onCloseGame = (game: Game) => { const onCloseGame = (game: Game) => {
const gamePlaytime = gamesPlaytime.get(game.id)!; const gamePlaytime = gamesPlaytime.get(`${game.shop}-${game.objectId}`)!;
gamesPlaytime.delete(game.id); gamesPlaytime.delete(`${game.shop}-${game.objectId}`);
if (game.remoteId) { if (game.remoteId) {
updateGamePlaytime( updateGamePlaytime(

View File

@ -22,16 +22,16 @@ contextBridge.exposeInMainWorld("electron", {
/* Torrenting */ /* Torrenting */
startGameDownload: (payload: StartGameDownloadPayload) => startGameDownload: (payload: StartGameDownloadPayload) =>
ipcRenderer.invoke("startGameDownload", payload), ipcRenderer.invoke("startGameDownload", payload),
cancelGameDownload: (gameId: number) => cancelGameDownload: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("cancelGameDownload", gameId), ipcRenderer.invoke("cancelGameDownload", shop, objectId),
pauseGameDownload: (gameId: number) => pauseGameDownload: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("pauseGameDownload", gameId), ipcRenderer.invoke("pauseGameDownload", shop, objectId),
resumeGameDownload: (gameId: number) => resumeGameDownload: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("resumeGameDownload", gameId), ipcRenderer.invoke("resumeGameDownload", shop, objectId),
pauseGameSeed: (gameId: number) => pauseGameSeed: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("pauseGameSeed", gameId), ipcRenderer.invoke("pauseGameSeed", shop, objectId),
resumeGameSeed: (gameId: number) => resumeGameSeed: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("resumeGameSeed", gameId), ipcRenderer.invoke("resumeGameSeed", shop, objectId),
onDownloadProgress: (cb: (value: DownloadProgress) => void) => { onDownloadProgress: (cb: (value: DownloadProgress) => void) => {
const listener = ( const listener = (
_event: Electron.IpcRendererEvent, _event: Electron.IpcRendererEvent,
@ -98,40 +98,61 @@ contextBridge.exposeInMainWorld("electron", {
ipcRenderer.invoke("putDownloadSource", objectIds), ipcRenderer.invoke("putDownloadSource", objectIds),
/* Library */ /* Library */
addGameToLibrary: (objectId: string, title: string, shop: GameShop) => addGameToLibrary: (shop: GameShop, objectId: string, title: string) =>
ipcRenderer.invoke("addGameToLibrary", objectId, title, shop), ipcRenderer.invoke("addGameToLibrary", shop, objectId, title),
createGameShortcut: (id: number) => createGameShortcut: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("createGameShortcut", id), ipcRenderer.invoke("createGameShortcut", shop, objectId),
updateExecutablePath: (id: number, executablePath: string | null) => updateExecutablePath: (
ipcRenderer.invoke("updateExecutablePath", id, executablePath), shop: GameShop,
updateLaunchOptions: (id: number, launchOptions: string | null) => objectId: string,
ipcRenderer.invoke("updateLaunchOptions", id, launchOptions), executablePath: string | null
selectGameWinePrefix: (id: number, winePrefixPath: string | null) => ) =>
ipcRenderer.invoke("selectGameWinePrefix", id, winePrefixPath), ipcRenderer.invoke("updateExecutablePath", shop, objectId, executablePath),
updateLaunchOptions: (
shop: GameShop,
objectId: string,
launchOptions: string | null
) => ipcRenderer.invoke("updateLaunchOptions", shop, objectId, launchOptions),
selectGameWinePrefix: (
shop: GameShop,
objectId: string,
winePrefixPath: string | null
) =>
ipcRenderer.invoke("selectGameWinePrefix", shop, objectId, winePrefixPath),
verifyExecutablePathInUse: (executablePath: string) => verifyExecutablePathInUse: (executablePath: string) =>
ipcRenderer.invoke("verifyExecutablePathInUse", executablePath), ipcRenderer.invoke("verifyExecutablePathInUse", executablePath),
getLibrary: () => ipcRenderer.invoke("getLibrary"), getLibrary: () => ipcRenderer.invoke("getLibrary"),
openGameInstaller: (gameId: number) => openGameInstaller: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("openGameInstaller", gameId), ipcRenderer.invoke("openGameInstaller", shop, objectId),
openGameInstallerPath: (gameId: number) => openGameInstallerPath: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("openGameInstallerPath", gameId), ipcRenderer.invoke("openGameInstallerPath", shop, objectId),
openGameExecutablePath: (gameId: number) => openGameExecutablePath: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("openGameExecutablePath", gameId), ipcRenderer.invoke("openGameExecutablePath", shop, objectId),
openGame: ( openGame: (
gameId: number, shop: GameShop,
objectId: string,
executablePath: string, executablePath: string,
launchOptions: string | null launchOptions: string | null
) => ipcRenderer.invoke("openGame", gameId, executablePath, launchOptions), ) =>
closeGame: (gameId: number) => ipcRenderer.invoke("closeGame", gameId), ipcRenderer.invoke(
removeGameFromLibrary: (gameId: number) => "openGame",
ipcRenderer.invoke("removeGameFromLibrary", gameId), shop,
removeGame: (gameId: number) => ipcRenderer.invoke("removeGame", gameId), objectId,
deleteGameFolder: (gameId: number) => executablePath,
ipcRenderer.invoke("deleteGameFolder", gameId), launchOptions
),
closeGame: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("closeGame", shop, objectId),
removeGameFromLibrary: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("removeGameFromLibrary", shop, objectId),
removeGame: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("removeGame", shop, objectId),
deleteGameFolder: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("deleteGameFolder", shop, objectId),
getGameByObjectId: (objectId: string) => getGameByObjectId: (objectId: string) =>
ipcRenderer.invoke("getGameByObjectId", objectId), ipcRenderer.invoke("getGameByObjectId", objectId),
resetGameAchievements: (gameId: number) => resetGameAchievements: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("resetGameAchievements", gameId), ipcRenderer.invoke("resetGameAchievements", shop, objectId),
onGamesRunning: ( onGamesRunning: (
cb: ( cb: (
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[] gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]

View File

@ -40,11 +40,11 @@ declare global {
interface Electron { interface Electron {
/* Torrenting */ /* Torrenting */
startGameDownload: (payload: StartGameDownloadPayload) => Promise<void>; startGameDownload: (payload: StartGameDownloadPayload) => Promise<void>;
cancelGameDownload: (gameId: number) => Promise<void>; cancelGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
pauseGameDownload: (gameId: number) => Promise<void>; pauseGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
resumeGameDownload: (gameId: number) => Promise<void>; resumeGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
pauseGameSeed: (gameId: number) => Promise<void>; pauseGameSeed: (shop: GameShop, objectId: string) => Promise<void>;
resumeGameSeed: (gameId: number) => Promise<void>; resumeGameSeed: (shop: GameShop, objectId: string) => Promise<void>;
onDownloadProgress: ( onDownloadProgress: (
cb: (value: DownloadProgress) => void cb: (value: DownloadProgress) => void
) => () => Electron.IpcRenderer; ) => () => Electron.IpcRenderer;
@ -82,45 +82,55 @@ declare global {
/* Library */ /* Library */
addGameToLibrary: ( addGameToLibrary: (
shop: GameShop,
objectId: string, objectId: string,
title: string, title: string
shop: GameShop
) => Promise<void>; ) => Promise<void>;
createGameShortcut: (id: number) => Promise<boolean>; createGameShortcut: (shop: GameShop, objectId: string) => Promise<boolean>;
updateExecutablePath: ( updateExecutablePath: (
id: number, shop: GameShop,
objectId: string,
executablePath: string | null executablePath: string | null
) => Promise<void>; ) => Promise<void>;
updateLaunchOptions: ( updateLaunchOptions: (
id: number, shop: GameShop,
objectId: string,
launchOptions: string | null launchOptions: string | null
) => Promise<void>; ) => Promise<void>;
selectGameWinePrefix: ( selectGameWinePrefix: (
id: number, shop: GameShop,
objectId: string,
winePrefixPath: string | null winePrefixPath: string | null
) => Promise<void>; ) => Promise<void>;
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>; verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
getLibrary: () => Promise<Game[]>; getLibrary: () => Promise<Game[]>;
openGameInstaller: (gameId: number) => Promise<boolean>; openGameInstaller: (shop: GameShop, objectId: string) => Promise<boolean>;
openGameInstallerPath: (gameId: number) => Promise<boolean>; openGameInstallerPath: (
openGameExecutablePath: (gameId: number) => Promise<void>; shop: GameShop,
objectId: string
) => Promise<boolean>;
openGameExecutablePath: (shop: GameShop, objectId: string) => Promise<void>;
openGame: ( openGame: (
gameId: number, shop: GameShop,
objectId: string,
executablePath: string, executablePath: string,
launchOptions: string | null launchOptions: string | null
) => Promise<void>; ) => Promise<void>;
closeGame: (gameId: number) => Promise<boolean>; closeGame: (shop: GameShop, objectId: string) => Promise<boolean>;
removeGameFromLibrary: (gameId: number) => Promise<void>; removeGameFromLibrary: (shop: GameShop, objectId: string) => Promise<void>;
removeGame: (gameId: number) => Promise<void>; removeGame: (shop: GameShop, objectId: string) => Promise<void>;
deleteGameFolder: (gameId: number) => Promise<unknown>; deleteGameFolder: (shop: GameShop, objectId: string) => Promise<unknown>;
getGameByObjectId: (objectId: string) => Promise<Game | null>; getGameByObjectId: (
shop: GameShop,
objectId: string
) => Promise<Game | null>;
onGamesRunning: ( onGamesRunning: (
cb: ( cb: (
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[] gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
) => void ) => void
) => () => Electron.IpcRenderer; ) => () => Electron.IpcRenderer;
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer; onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
resetGameAchievements: (gameId: number) => Promise<void>; resetGameAchievements: (shop: GameShop, objectId: string) => Promise<void>;
/* User preferences */ /* User preferences */
getUserPreferences: () => Promise<UserPreferences | null>; getUserPreferences: () => Promise<UserPreferences | null>;
updateUserPreferences: ( updateUserPreferences: (

View File

@ -1,4 +1,5 @@
import type { Game, GameStatus } from "./game.types"; import type { GameStatus } from "./game.types";
import { Game } from "./level.types";
export interface DownloadProgress { export interface DownloadProgress {
downloadSpeed: number; downloadSpeed: number;

View File

@ -1,5 +1,3 @@
import type { Downloader } from "@shared";
export type GameStatus = export type GameStatus =
| "active" | "active"
| "waiting" | "waiting"
@ -11,33 +9,6 @@ export type GameStatus =
export type GameShop = "steam" | "epic"; export type GameShop = "steam" | "epic";
export interface Game {
// TODO: To be depreacted
id: number;
title: string;
iconUrl: string;
status: GameStatus | null;
folderName: string;
downloadPath: string | null;
progress: number;
bytesDownloaded: number;
playTimeInMilliseconds: number;
downloader: Downloader;
winePrefixPath: string | null;
executablePath: string | null;
launchOptions: string | null;
lastTimePlayed: Date | null;
uri: string | null;
fileSize: number;
objectID: string;
shop: GameShop;
// downloadQueue: DownloadQueue | null;
downloadQueue: any | null;
shouldSeed: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface UnlockedAchievement { export interface UnlockedAchievement {
name: string; name: string;
unlockTime: number; unlockTime: number;

View File

@ -1,4 +1,10 @@
import type { SteamAchievement, UnlockedAchievement } from "./game.types"; import type { Downloader } from "@shared";
import type {
GameShop,
GameStatus,
SteamAchievement,
UnlockedAchievement,
} from "./game.types";
export type SubscriptionStatus = "active" | "pending" | "cancelled"; export type SubscriptionStatus = "active" | "pending" | "cancelled";
@ -24,6 +30,37 @@ export interface User {
subscription: Subscription | null; subscription: Subscription | null;
} }
export interface Game {
title: string;
iconUrl: string | null;
status: GameStatus | null;
playTimeInMilliseconds: number;
lastTimePlayed: Date | null;
objectId: string;
shop: GameShop;
remoteId: string | null;
isDeleted: boolean;
winePrefixPath?: string | null;
executablePath?: string | null;
launchOptions?: string | null;
}
export interface Download {
shop: GameShop;
objectId: string;
uri: string;
folderName: string;
downloadPath: string;
progress: number;
downloader: Downloader;
bytesDownloaded: number;
playTimeInMilliseconds: number;
lastTimePlayed: Date | null;
fileSize: number;
shouldSeed: boolean;
timestamp: number;
}
export interface GameAchievement { export interface GameAchievement {
achievements: SteamAchievement[]; achievements: SteamAchievement[];
unlockedAchievements: UnlockedAchievement[]; unlockedAchievements: UnlockedAchievement[];