mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-23 02:41:02 +00:00
Merge branch 'feat/migration-to-leveldb' into feature/custom-themes
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
import { userAuthRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { Auth } from "@types";
|
||||
import { Crypto } from "@main/services";
|
||||
|
||||
const getSessionHash = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const auth = await userAuthRepository.findOne({ where: { id: 1 } });
|
||||
const auth = await db.get<string, Auth>(levelKeys.auth, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
if (!auth) return null;
|
||||
const payload = jwt.decode(auth.accessToken) as jwt.JwtPayload;
|
||||
const payload = jwt.decode(
|
||||
Crypto.decrypt(auth.accessToken)
|
||||
) as jwt.JwtPayload;
|
||||
|
||||
if (!payload) return null;
|
||||
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { DownloadQueue, Game, UserAuth, UserSubscription } from "@main/entity";
|
||||
import { PythonRPC } from "@main/services/python-rpc";
|
||||
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 transactionalEntityManager
|
||||
.getRepository(UserAuth)
|
||||
.delete({ id: 1 });
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(UserSubscription)
|
||||
.delete({ id: 1 });
|
||||
})
|
||||
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 */
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { gameShopCacheRepository } from "@main/repository";
|
||||
import { getSteamAppDetails } from "@main/services";
|
||||
import { getSteamAppDetails, logger } from "@main/services";
|
||||
|
||||
import type { ShopDetails, GameShop, SteamAppDetails } from "@types";
|
||||
import type { ShopDetails, GameShop } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { gamesShopCacheSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const getLocalizedSteamAppDetails = async (
|
||||
objectId: string,
|
||||
@@ -39,35 +39,27 @@ const getGameShopDetails = async (
|
||||
language: string
|
||||
): Promise<ShopDetails | null> => {
|
||||
if (shop === "steam") {
|
||||
const cachedData = await gameShopCacheRepository.findOne({
|
||||
where: { objectID: objectId, language },
|
||||
});
|
||||
const cachedData = await gamesShopCacheSublevel.get(
|
||||
levelKeys.gameShopCacheItem(shop, objectId, language)
|
||||
);
|
||||
|
||||
const appDetails = getLocalizedSteamAppDetails(objectId, language).then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
gameShopCacheRepository.upsert(
|
||||
{
|
||||
objectID: objectId,
|
||||
shop: "steam",
|
||||
language,
|
||||
serializedData: JSON.stringify(result),
|
||||
},
|
||||
["objectID"]
|
||||
);
|
||||
gamesShopCacheSublevel
|
||||
.put(levelKeys.gameShopCacheItem(shop, objectId, language), result)
|
||||
.catch((err) => {
|
||||
logger.error("Could not cache game details", err);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
const cachedGame = cachedData?.serializedData
|
||||
? (JSON.parse(cachedData?.serializedData) as SteamAppDetails)
|
||||
: null;
|
||||
|
||||
if (cachedGame) {
|
||||
if (cachedData) {
|
||||
return {
|
||||
...cachedGame,
|
||||
...cachedData,
|
||||
objectId,
|
||||
} as ShopDetails;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import type { TrendingGame } from "@types";
|
||||
|
||||
const getTrendingGames = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
|
||||
const language = userPreferences?.language || "en";
|
||||
const language = await db
|
||||
.get<string, string>(levelKeys.language, {
|
||||
valueEncoding: "utf-8",
|
||||
})
|
||||
.then((language) => language || "en");
|
||||
|
||||
const trendingGames = await HydraApi.get<TrendingGame[]>(
|
||||
"/games/trending",
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import type { GameShop } from "@types";
|
||||
import { Ludusavi } from "@main/services";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const getGameBackupPreview = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectId: string,
|
||||
shop: GameShop
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
shop,
|
||||
},
|
||||
});
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath);
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import os from "node:os";
|
||||
import { backupsPath } from "@main/constants";
|
||||
import { app } from "electron";
|
||||
import { normalizePath } from "@main/helpers";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const bundleBackup = async (
|
||||
shop: GameShop,
|
||||
@@ -46,12 +46,7 @@ const uploadSaveGame = async (
|
||||
shop: GameShop,
|
||||
downloadOptionTitle: string | null
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
shop,
|
||||
},
|
||||
});
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
const bundleLocation = await bundleBackup(
|
||||
shop,
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -1,12 +1,14 @@
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { defaultDownloadsPath } from "@main/constants";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
export const getDownloadsPath = async () => {
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: {
|
||||
id: 1,
|
||||
},
|
||||
});
|
||||
const userPreferences = await db.get<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
if (userPreferences && userPreferences.downloadsPath)
|
||||
return userPreferences.downloadsPath;
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import type { Game, GameShop } from "@types";
|
||||
|
||||
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 { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const addGameToLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
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);
|
||||
|
||||
await gameRepository.insert({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID: objectId,
|
||||
shop,
|
||||
});
|
||||
}
|
||||
|
||||
const game = await gameRepository.findOne({
|
||||
where: { objectID: objectId },
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { logger } from "@main/services";
|
||||
import sudo from "sudo-prompt";
|
||||
import { app } from "electron";
|
||||
import { PythonRPC } from "@main/services/python-rpc";
|
||||
import { ProcessPayload } from "@main/services/download/types";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const getKillCommand = (pid: number) => {
|
||||
if (process.platform == "win32") {
|
||||
@@ -16,15 +17,14 @@ const getKillCommand = (pid: number) => {
|
||||
|
||||
const closeGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const processes =
|
||||
(await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data ||
|
||||
[];
|
||||
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
if (!game) return;
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { IsNull, Not } from "typeorm";
|
||||
import createDesktopShortcut from "create-desktop-shortcuts";
|
||||
import path from "node:path";
|
||||
import { app } from "electron";
|
||||
import { removeSymbolsFromName } from "@shared";
|
||||
import { GameShop } from "@types";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const createGameShortcut = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
): Promise<boolean> => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id, executablePath: Not(IsNull()) },
|
||||
});
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
const filePath = game.executablePath;
|
||||
|
||||
@@ -1,37 +1,27 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { logger } from "@main/services";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { GameShop } from "@types";
|
||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const deleteGameFolder = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
): Promise<void> => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: [
|
||||
{
|
||||
id: gameId,
|
||||
isDeleted: false,
|
||||
status: "removed",
|
||||
},
|
||||
{
|
||||
id: gameId,
|
||||
progress: 1,
|
||||
isDeleted: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
|
||||
if (!game) return;
|
||||
const download = await downloadsSublevel.get(downloadKey);
|
||||
|
||||
if (game.folderName) {
|
||||
if (!download) return;
|
||||
|
||||
if (download.folderName) {
|
||||
const folderPath = path.join(
|
||||
game.downloadPath ?? (await getDownloadsPath()),
|
||||
game.folderName
|
||||
download.downloadPath ?? (await getDownloadsPath()),
|
||||
download.folderName
|
||||
);
|
||||
|
||||
if (fs.existsSync(folderPath)) {
|
||||
@@ -52,10 +42,7 @@ const deleteGameFolder = async (
|
||||
}
|
||||
}
|
||||
|
||||
await gameRepository.update(
|
||||
{ id: gameId },
|
||||
{ downloadPath: null, folderName: null, status: null, progress: 0 }
|
||||
);
|
||||
await downloadsSublevel.del(downloadKey);
|
||||
};
|
||||
|
||||
registerEvent("deleteGameFolder", deleteGameFolder);
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, downloadsSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const getGameByObjectId = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) =>
|
||||
gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
) => {
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
const [game, download] = await Promise.all([
|
||||
gamesSublevel.get(gameKey),
|
||||
downloadsSublevel.get(gameKey),
|
||||
]);
|
||||
|
||||
if (!game || game.isDeleted) return null;
|
||||
|
||||
return { id: gameKey, ...game, download };
|
||||
};
|
||||
|
||||
registerEvent("getGameByObjectId", getGameByObjectId);
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import type { LibraryGame } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { downloadsSublevel, gamesSublevel } from "@main/level";
|
||||
|
||||
const getLibrary = async () =>
|
||||
gameRepository.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
relations: {
|
||||
downloadQueue: true,
|
||||
},
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
const getLibrary = async (): Promise<LibraryGame[]> => {
|
||||
return gamesSublevel
|
||||
.iterator()
|
||||
.all()
|
||||
.then((results) => {
|
||||
return Promise.all(
|
||||
results
|
||||
.filter(([_key, game]) => game.isDeleted === false)
|
||||
.map(async ([key, game]) => {
|
||||
const download = await downloadsSublevel.get(key);
|
||||
|
||||
return {
|
||||
id: key,
|
||||
...game,
|
||||
download: download ?? null,
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("getLibrary", getLibrary);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { shell } from "electron";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const openGameExecutablePath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
if (!game || !game.executablePath) return;
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { shell } from "electron";
|
||||
import path from "node:path";
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { GameShop } from "@types";
|
||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const openGameInstallerPath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
const download = await downloadsSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
if (!game || !game.folderName || !game.downloadPath) return true;
|
||||
if (!download || !download.folderName || !download.downloadPath) return true;
|
||||
|
||||
const gamePath = path.join(
|
||||
game.downloadPath ?? (await getDownloadsPath()),
|
||||
game.folderName!
|
||||
download.downloadPath ?? (await getDownloadsPath()),
|
||||
download.folderName!
|
||||
);
|
||||
|
||||
shell.showItemInFolder(gamePath);
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
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 { gameRepository } from "@main/repository";
|
||||
|
||||
import { generateYML } from "../helpers/generate-lutris-yaml";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const executeGameInstaller = (filePath: string) => {
|
||||
if (process.platform === "win32") {
|
||||
@@ -26,21 +24,21 @@ const executeGameInstaller = (filePath: string) => {
|
||||
|
||||
const openGameInstaller = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: { id: gameId, isDeleted: false },
|
||||
});
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
const download = await downloadsSublevel.get(downloadKey);
|
||||
|
||||
if (!game || !game.folderName) return true;
|
||||
if (!download?.folderName) return true;
|
||||
|
||||
const gamePath = path.join(
|
||||
game.downloadPath ?? (await getDownloadsPath()),
|
||||
game.folderName!
|
||||
download.downloadPath ?? (await getDownloadsPath()),
|
||||
download.folderName
|
||||
);
|
||||
|
||||
if (!fs.existsSync(gamePath)) {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { shell } from "electron";
|
||||
import { parseExecutablePath } from "../helpers/parse-executable-path";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const openGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
executablePath: string,
|
||||
launchOptions: string | null
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gameRepository } from "../../repository";
|
||||
import { HydraApi, logger } from "@main/services";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const removeGameFromLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
gameRepository.update(
|
||||
{ id: gameId },
|
||||
{ isDeleted: true, executablePath: null }
|
||||
);
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
removeRemoveGameFromLibrary(gameId).catch((err) => {
|
||||
logger.error("removeRemoveGameFromLibrary", err);
|
||||
});
|
||||
};
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
isDeleted: true,
|
||||
executablePath: null,
|
||||
});
|
||||
|
||||
const removeRemoveGameFromLibrary = async (gameId: number) => {
|
||||
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||
|
||||
if (game?.remoteId) {
|
||||
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
|
||||
if (game?.remoteId) {
|
||||
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gameRepository } from "../../repository";
|
||||
import { levelKeys, downloadsSublevel } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const removeGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
await gameRepository.update(
|
||||
{
|
||||
id: gameId,
|
||||
},
|
||||
{
|
||||
status: "removed",
|
||||
downloadPath: null,
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
);
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
await downloadsSublevel.del(downloadKey);
|
||||
};
|
||||
|
||||
registerEvent("removeGame", removeGame);
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { gameAchievementRepository, gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
|
||||
import fs from "fs";
|
||||
import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
|
||||
import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
|
||||
import {
|
||||
gameAchievementsSublevel,
|
||||
gamesSublevel,
|
||||
levelKeys,
|
||||
} from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const resetGameAchievements = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
try {
|
||||
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
if (!game) return;
|
||||
|
||||
@@ -23,28 +29,34 @@ const resetGameAchievements = async (
|
||||
}
|
||||
}
|
||||
|
||||
await gameAchievementRepository.update(
|
||||
{ objectId: game.objectID },
|
||||
{
|
||||
unlockedAchievements: null,
|
||||
}
|
||||
);
|
||||
const levelKey = levelKeys.game(game.shop, game.objectId);
|
||||
|
||||
await gameAchievementsSublevel
|
||||
.get(levelKey)
|
||||
.then(async (gameAchievements) => {
|
||||
if (gameAchievements) {
|
||||
await gameAchievementsSublevel.put(levelKey, {
|
||||
...gameAchievements,
|
||||
unlockedAchievements: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
|
||||
() =>
|
||||
achievementsLogger.log(
|
||||
`Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}`
|
||||
`Deleted achievements from ${game.remoteId} - ${game.objectId} - ${game.title}`
|
||||
)
|
||||
);
|
||||
|
||||
const gameAchievements = await getUnlockedAchievements(
|
||||
game.objectID,
|
||||
game.objectId,
|
||||
game.shop,
|
||||
true
|
||||
);
|
||||
|
||||
WindowManager.mainWindow?.webContents.send(
|
||||
`on-update-achievements-${game.objectID}-${game.shop}`,
|
||||
`on-update-achievements-${game.objectId}-${game.shop}`,
|
||||
gameAchievements
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { levelKeys, gamesSublevel } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const selectGameWinePrefix = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
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);
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { parseExecutablePath } from "../helpers/parse-executable-path";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const updateExecutablePath = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
executablePath: string | null
|
||||
) => {
|
||||
const parsedPath = executablePath
|
||||
? parseExecutablePath(executablePath)
|
||||
: null;
|
||||
|
||||
return gameRepository.update(
|
||||
{
|
||||
id,
|
||||
},
|
||||
{
|
||||
executablePath: parsedPath,
|
||||
}
|
||||
);
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
if (!game) return;
|
||||
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
executablePath: parsedPath,
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("updateExecutablePath", updateExecutablePath);
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const updateLaunchOptions = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
launchOptions: string | null
|
||||
) => {
|
||||
return gameRepository.update(
|
||||
{
|
||||
id,
|
||||
},
|
||||
{
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
launchOptions: launchOptions?.trim() != "" ? launchOptions : null,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("updateLaunchOptions", updateLaunchOptions);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { gameRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel } from "@main/level";
|
||||
|
||||
const verifyExecutablePathInUse = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
executablePath: string
|
||||
) => {
|
||||
return gameRepository.findOne({
|
||||
where: { executablePath },
|
||||
});
|
||||
for await (const game of gamesSublevel.values()) {
|
||||
if (game.executablePath === executablePath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
registerEvent("verifyExecutablePathInUse", verifyExecutablePathInUse);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { shell } from "electron";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { userAuthRepository } from "@main/repository";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { Crypto, HydraApi } from "@main/services";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { Auth } from "@types";
|
||||
|
||||
const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const userAuth = await userAuthRepository.findOne({ where: { id: 1 } });
|
||||
const auth = await db.get<string, Auth>(levelKeys.auth, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
if (!userAuth) {
|
||||
if (!auth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const paymentToken = await HydraApi.post("/auth/payment", {
|
||||
refreshToken: userAuth.refreshToken,
|
||||
refreshToken: Crypto.decrypt(auth.refreshToken),
|
||||
}).then((response) => response.accessToken);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Notification } from "electron";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { t } from "i18next";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
const publishNewRepacksNotification = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -9,9 +10,12 @@ const publishNewRepacksNotification = async (
|
||||
) => {
|
||||
if (newRepacksCount < 1) return;
|
||||
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
const userPreferences = await db.get<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
if (userPreferences?.repackUpdatesNotificationsEnabled) {
|
||||
new Notification({
|
||||
|
||||
@@ -1,31 +1,19 @@
|
||||
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);
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
|
||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({
|
||||
game: { id: gameId },
|
||||
});
|
||||
await DownloadManager.cancelDownload(downloadKey);
|
||||
|
||||
await transactionalEntityManager.getRepository(Game).update(
|
||||
{
|
||||
id: gameId,
|
||||
},
|
||||
{
|
||||
status: "removed",
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
await downloadsSublevel.del(downloadKey);
|
||||
};
|
||||
|
||||
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
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 pauseGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
||||
await DownloadManager.pauseDownload();
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({
|
||||
game: { id: gameId },
|
||||
const download = await downloadsSublevel.get(gameKey);
|
||||
|
||||
if (download) {
|
||||
await DownloadManager.pauseDownload(gameKey);
|
||||
|
||||
await downloadsSublevel.put(gameKey, {
|
||||
...download,
|
||||
status: "paused",
|
||||
queued: false,
|
||||
});
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
.update({ id: gameId }, { status: "paused" });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("pauseGameDownload", pauseGameDownload);
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { downloadsSublevel, 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(downloadKey);
|
||||
};
|
||||
|
||||
registerEvent("pauseGameSeed", pauseGameSeed);
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
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";
|
||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
|
||||
const resumeGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
id: gameId,
|
||||
isDeleted: false,
|
||||
},
|
||||
});
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
if (!game) return;
|
||||
const download = await downloadsSublevel.get(gameKey);
|
||||
|
||||
if (game.status === "paused") {
|
||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
||||
await DownloadManager.pauseDownload();
|
||||
if (download?.status === "paused") {
|
||||
await DownloadManager.pauseDownload();
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
.update({ status: "active", progress: Not(1) }, { status: "paused" });
|
||||
for await (const [key, value] of downloadsSublevel.iterator()) {
|
||||
if (value.status === "active" && value.progress !== 1) {
|
||||
await downloadsSublevel.put(key, {
|
||||
...value,
|
||||
status: "paused",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await DownloadManager.resumeDownload(game);
|
||||
await DownloadManager.resumeDownload(download);
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(DownloadQueue)
|
||||
.delete({ game: { id: gameId } });
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(DownloadQueue)
|
||||
.insert({ game: { id: gameId } });
|
||||
|
||||
await transactionalEntityManager
|
||||
.getRepository(Game)
|
||||
.update({ id: gameId }, { status: "active" });
|
||||
await downloadsSublevel.put(gameKey, {
|
||||
...download,
|
||||
status: "active",
|
||||
timestamp: Date.now(),
|
||||
queued: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import { downloadsSublevel, 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);
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import type { StartGameDownloadPayload } from "@types";
|
||||
import type { Download, StartGameDownloadPayload } from "@types";
|
||||
import { DownloadManager, HydraApi } from "@main/services";
|
||||
|
||||
import { Not } from "typeorm";
|
||||
import { steamGamesWorker } from "@main/workers";
|
||||
import { createGame } from "@main/services/library-sync";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import { DownloadQueue, Game } from "@main/entity";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const startGameDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -15,85 +13,85 @@ const startGameDownload = async (
|
||||
) => {
|
||||
const { objectId, title, shop, downloadPath, downloader, uri } = payload;
|
||||
|
||||
return dataSource.transaction(async (transactionalEntityManager) => {
|
||||
const gameRepository = transactionalEntityManager.getRepository(Game);
|
||||
const downloadQueueRepository =
|
||||
transactionalEntityManager.getRepository(DownloadQueue);
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
shop,
|
||||
},
|
||||
});
|
||||
await DownloadManager.pauseDownload();
|
||||
|
||||
await DownloadManager.pauseDownload();
|
||||
|
||||
await gameRepository.update(
|
||||
{ status: "active", progress: Not(1) },
|
||||
{ status: "paused" }
|
||||
);
|
||||
|
||||
if (game) {
|
||||
await gameRepository.update(
|
||||
{
|
||||
id: game.id,
|
||||
},
|
||||
{
|
||||
status: "active",
|
||||
progress: 0,
|
||||
bytesDownloaded: 0,
|
||||
downloadPath,
|
||||
downloader,
|
||||
uri,
|
||||
isDeleted: false,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
await gameRepository.insert({
|
||||
title,
|
||||
iconUrl,
|
||||
objectID: objectId,
|
||||
downloader,
|
||||
shop,
|
||||
status: "active",
|
||||
downloadPath,
|
||||
uri,
|
||||
for await (const [key, value] of downloadsSublevel.iterator()) {
|
||||
if (value.status === "active" && value.progress !== 1) {
|
||||
await downloadsSublevel.put(key, {
|
||||
...value,
|
||||
status: "paused",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const updatedGame = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID: objectId,
|
||||
},
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
/* Delete any previous download */
|
||||
await downloadsSublevel.del(gameKey);
|
||||
|
||||
if (game?.isDeleted) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
isDeleted: false,
|
||||
});
|
||||
} else {
|
||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
||||
name: "getById",
|
||||
});
|
||||
|
||||
await DownloadManager.cancelDownload(updatedGame!.id);
|
||||
await DownloadManager.startDownload(updatedGame!);
|
||||
const iconUrl = steamGame?.clientIcon
|
||||
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
|
||||
: null;
|
||||
|
||||
await downloadQueueRepository.delete({ game: { id: updatedGame!.id } });
|
||||
await downloadQueueRepository.insert({ game: { id: updatedGame!.id } });
|
||||
await gamesSublevel.put(gameKey, {
|
||||
title,
|
||||
iconUrl,
|
||||
objectId,
|
||||
shop,
|
||||
remoteId: null,
|
||||
playTimeInMilliseconds: 0,
|
||||
lastTimePlayed: null,
|
||||
isDeleted: false,
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
createGame(updatedGame!).catch(() => {}),
|
||||
HydraApi.post(
|
||||
"/games/download",
|
||||
{
|
||||
objectId: updatedGame!.objectID,
|
||||
shop: updatedGame!.shop,
|
||||
},
|
||||
{ needsAuth: false }
|
||||
).catch(() => {}),
|
||||
]);
|
||||
});
|
||||
await DownloadManager.cancelDownload(gameKey);
|
||||
|
||||
const download: Download = {
|
||||
shop,
|
||||
objectId,
|
||||
status: "active",
|
||||
progress: 0,
|
||||
bytesDownloaded: 0,
|
||||
downloadPath,
|
||||
downloader,
|
||||
uri,
|
||||
folderName: null,
|
||||
fileSize: null,
|
||||
shouldSeed: false,
|
||||
timestamp: Date.now(),
|
||||
queued: true,
|
||||
};
|
||||
|
||||
await downloadsSublevel.put(gameKey, download);
|
||||
|
||||
await DownloadManager.startDownload(download);
|
||||
|
||||
const updatedGame = await gamesSublevel.get(gameKey);
|
||||
|
||||
await Promise.all([
|
||||
createGame(updatedGame!).catch(() => {}),
|
||||
HydraApi.post(
|
||||
"/games/download",
|
||||
{
|
||||
objectId,
|
||||
shop,
|
||||
},
|
||||
{ needsAuth: false }
|
||||
).catch(() => {}),
|
||||
]);
|
||||
};
|
||||
|
||||
registerEvent("startGameDownload", startGameDownload);
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import { Crypto } from "@main/services";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
const getUserPreferences = async () =>
|
||||
userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
db
|
||||
.get<string, UserPreferences>(levelKeys.userPreferences, {
|
||||
valueEncoding: "json",
|
||||
})
|
||||
.then((userPreferences) => {
|
||||
if (userPreferences.realDebridApiToken) {
|
||||
userPreferences.realDebridApiToken = Crypto.decrypt(
|
||||
userPreferences.realDebridApiToken
|
||||
);
|
||||
}
|
||||
|
||||
return userPreferences;
|
||||
});
|
||||
|
||||
registerEvent("getUserPreferences", getUserPreferences);
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { UserPreferences } from "@types";
|
||||
import i18next from "i18next";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
|
||||
const updateUserPreferences = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
preferences: Partial<UserPreferences>
|
||||
) => {
|
||||
const userPreferences = await db.get<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{ valueEncoding: "json" }
|
||||
);
|
||||
|
||||
if (preferences.language) {
|
||||
await db.put<string, string>(levelKeys.language, preferences.language, {
|
||||
valueEncoding: "utf-8",
|
||||
});
|
||||
|
||||
i18next.changeLanguage(preferences.language);
|
||||
}
|
||||
|
||||
return userPreferencesRepository.upsert(
|
||||
await db.put<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
id: 1,
|
||||
...userPreferences,
|
||||
...preferences,
|
||||
},
|
||||
["id"]
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { ComparedAchievements, GameShop } from "@types";
|
||||
import type { ComparedAchievements, GameShop, UserPreferences } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
|
||||
import { HydraApi } from "@main/services";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
|
||||
const getComparedUnlockedAchievements = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -9,9 +10,12 @@ const getComparedUnlockedAchievements = async (
|
||||
shop: GameShop,
|
||||
userId: string
|
||||
) => {
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
const userPreferences = await db.get<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
const showHiddenAchievementsDescription =
|
||||
userPreferences?.showHiddenAchievementsDescription || false;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import type { GameShop, UnlockedAchievement, UserAchievement } from "@types";
|
||||
import type { GameShop, UserAchievement, UserPreferences } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import {
|
||||
gameAchievementRepository,
|
||||
userPreferencesRepository,
|
||||
} from "@main/repository";
|
||||
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
|
||||
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
|
||||
|
||||
export const getUnlockedAchievements = async (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
useCachedData: boolean
|
||||
): Promise<UserAchievement[]> => {
|
||||
const cachedAchievements = await gameAchievementRepository.findOne({
|
||||
where: { objectId, shop },
|
||||
});
|
||||
const cachedAchievements = await gameAchievementsSublevel.get(
|
||||
levelKeys.game(shop, objectId)
|
||||
);
|
||||
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
const userPreferences = await db.get<string, UserPreferences>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
const showHiddenAchievementsDescription =
|
||||
userPreferences?.showHiddenAchievementsDescription || false;
|
||||
@@ -25,12 +25,10 @@ export const getUnlockedAchievements = async (
|
||||
const achievementsData = await getGameAchievementData(
|
||||
objectId,
|
||||
shop,
|
||||
useCachedData ? cachedAchievements : null
|
||||
useCachedData
|
||||
);
|
||||
|
||||
const unlockedAchievements = JSON.parse(
|
||||
cachedAchievements?.unlockedAchievements || "[]"
|
||||
) as UnlockedAchievement[];
|
||||
const unlockedAchievements = cachedAchievements?.unlockedAchievements ?? [];
|
||||
|
||||
return achievementsData
|
||||
.map((achievementData) => {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { userAuthRepository } from "@main/repository";
|
||||
import { db } from "@main/level";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import type { UserFriends } from "@types";
|
||||
import type { User, UserFriends } from "@types";
|
||||
import { levelKeys } from "@main/level/sublevels";
|
||||
|
||||
export const getUserFriends = async (
|
||||
userId: string,
|
||||
take: number,
|
||||
skip: number
|
||||
): Promise<UserFriends> => {
|
||||
const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } });
|
||||
const user = await db.get<string, User>(levelKeys.user, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
if (loggedUser?.userId === userId) {
|
||||
if (user?.id === userId) {
|
||||
return HydraApi.get(`/profile/friends`, { take, skip });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user