mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
feat: migrating games to level
This commit is contained in:
parent
1f0e195854
commit
d760d0139d
@ -1,11 +1,11 @@
|
|||||||
import { DataSource } from "typeorm";
|
import { DataSource } from "typeorm";
|
||||||
import { DownloadQueue, Game, UserPreferences } from "@main/entity";
|
import { UserPreferences } from "@main/entity";
|
||||||
|
|
||||||
import { databasePath } from "./constants";
|
import { databasePath } from "./constants";
|
||||||
|
|
||||||
export const dataSource = new DataSource({
|
export const dataSource = new DataSource({
|
||||||
type: "better-sqlite3",
|
type: "better-sqlite3",
|
||||||
entities: [Game, UserPreferences, DownloadQueue],
|
entities: [UserPreferences],
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
database: databasePath,
|
database: databasePath,
|
||||||
});
|
});
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
OneToOne,
|
|
||||||
JoinColumn,
|
|
||||||
} from "typeorm";
|
|
||||||
import type { Game } from "./game.entity";
|
|
||||||
|
|
||||||
@Entity("download_queue")
|
|
||||||
export class DownloadQueue {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@OneToOne("Game", "downloadQueue")
|
|
||||||
@JoinColumn()
|
|
||||||
game: Game;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
OneToOne,
|
|
||||||
} from "typeorm";
|
|
||||||
|
|
||||||
import type { GameShop, GameStatus } from "@types";
|
|
||||||
import { Downloader } from "@shared";
|
|
||||||
import type { DownloadQueue } from "./download-queue.entity";
|
|
||||||
|
|
||||||
@Entity("game")
|
|
||||||
export class Game {
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column("text", { unique: true })
|
|
||||||
objectID: string;
|
|
||||||
|
|
||||||
@Column("text", { unique: true, nullable: true })
|
|
||||||
remoteId: string | null;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
iconUrl: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
folderName: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
downloadPath: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
executablePath: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
launchOptions: string | null;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
winePrefixPath: string | null;
|
|
||||||
|
|
||||||
@Column("int", { default: 0 })
|
|
||||||
playTimeInMilliseconds: number;
|
|
||||||
|
|
||||||
@Column("text")
|
|
||||||
shop: GameShop;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
status: GameStatus | null;
|
|
||||||
|
|
||||||
@Column("int", { default: Downloader.Torrent })
|
|
||||||
downloader: Downloader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Progress is a float between 0 and 1
|
|
||||||
*/
|
|
||||||
@Column("float", { default: 0 })
|
|
||||||
progress: number;
|
|
||||||
|
|
||||||
@Column("int", { default: 0 })
|
|
||||||
bytesDownloaded: number;
|
|
||||||
|
|
||||||
@Column("datetime", { nullable: true })
|
|
||||||
lastTimePlayed: Date | null;
|
|
||||||
|
|
||||||
@Column("float", { default: 0 })
|
|
||||||
fileSize: number;
|
|
||||||
|
|
||||||
@Column("text", { nullable: true })
|
|
||||||
uri: string | null;
|
|
||||||
|
|
||||||
@OneToOne("DownloadQueue", "game")
|
|
||||||
downloadQueue: DownloadQueue;
|
|
||||||
|
|
||||||
@Column("boolean", { default: false })
|
|
||||||
isDeleted: boolean;
|
|
||||||
|
|
||||||
@Column("boolean", { default: false })
|
|
||||||
shouldSeed: boolean;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
@ -1,3 +1 @@
|
|||||||
export * from "./game.entity";
|
|
||||||
export * from "./user-preferences.entity";
|
export * from "./user-preferences.entity";
|
||||||
export * from "./download-queue.entity";
|
|
||||||
|
@ -1,31 +1,25 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
|
import { DownloadManager, HydraApi, gamesPlaytime } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
|
||||||
import { DownloadQueue, Game } from "@main/entity";
|
|
||||||
import { PythonRPC } from "@main/services/python-rpc";
|
import { PythonRPC } from "@main/services/python-rpc";
|
||||||
import { db, levelKeys } from "@main/level";
|
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
const signOut = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||||
const databaseOperations = dataSource
|
const databaseOperations = db
|
||||||
.transaction(async (transactionalEntityManager) => {
|
.batch([
|
||||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({});
|
{
|
||||||
|
type: "del",
|
||||||
await transactionalEntityManager.getRepository(Game).delete({});
|
key: levelKeys.auth,
|
||||||
|
},
|
||||||
await db.batch([
|
{
|
||||||
{
|
type: "del",
|
||||||
type: "del",
|
key: levelKeys.user,
|
||||||
key: levelKeys.auth,
|
},
|
||||||
},
|
])
|
||||||
{
|
|
||||||
type: "del",
|
|
||||||
key: levelKeys.user,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
/* Removes all games being played */
|
/* Removes all games being played */
|
||||||
gamesPlaytime.clear();
|
gamesPlaytime.clear();
|
||||||
|
|
||||||
|
return Promise.all([gamesSublevel.clear(), downloadsSublevel.clear()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Cancels any ongoing downloads */
|
/* Cancels any ongoing downloads */
|
||||||
|
@ -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,5 +1,3 @@
|
|||||||
import { gameRepository } from "@main/repository";
|
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import type { Game, GameShop } from "@types";
|
import type { Game, GameShop } from "@types";
|
||||||
@ -8,7 +6,7 @@ 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";
|
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const addGameToLibrary = async (
|
const addGameToLibrary = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
@ -16,41 +14,42 @@ const addGameToLibrary = async (
|
|||||||
objectId: string,
|
objectId: string,
|
||||||
title: string
|
title: string
|
||||||
) => {
|
) => {
|
||||||
return gameRepository
|
const gameKey = levelKeys.game(shop, objectId);
|
||||||
.update(
|
const game = await gamesSublevel.get(gameKey);
|
||||||
{
|
|
||||||
objectID: objectId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
shop,
|
|
||||||
status: null,
|
|
||||||
isDeleted: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(async ({ affected }) => {
|
|
||||||
if (!affected) {
|
|
||||||
const steamGame = await steamGamesWorker.run(Number(objectId), {
|
|
||||||
name: "getById",
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconUrl = steamGame?.clientIcon
|
if (game) {
|
||||||
? steamUrlBuilder.icon(objectId, steamGame.clientIcon)
|
await downloadsSublevel.del(gameKey);
|
||||||
: null;
|
|
||||||
|
|
||||||
const game: Game = {
|
await gamesSublevel.put(gameKey, {
|
||||||
title,
|
...game,
|
||||||
iconUrl,
|
isDeleted: false,
|
||||||
objectId,
|
|
||||||
shop,
|
|
||||||
};
|
|
||||||
|
|
||||||
await gamesSublevel.put(levelKeys.game(shop, objectId), game);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLocalUnlockedAchivements(game!);
|
|
||||||
|
|
||||||
createGame(game!).catch(() => {});
|
|
||||||
});
|
});
|
||||||
|
} 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);
|
registerEvent("addGameToLibrary", addGameToLibrary);
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { gamesSublevel } from "@main/level";
|
import { downloadsSublevel, gamesSublevel } from "@main/level";
|
||||||
|
|
||||||
const getLibrary = async () => {
|
const getLibrary = async () => {
|
||||||
// TODO: Add sorting
|
|
||||||
return gamesSublevel
|
return gamesSublevel
|
||||||
.values()
|
.iterator()
|
||||||
.all()
|
.all()
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
return results.filter((game) => game.isDeleted === false);
|
return Promise.all(
|
||||||
|
results
|
||||||
|
.filter(([_key, game]) => game.isDeleted === false)
|
||||||
|
.map(async ([key, game]) => {
|
||||||
|
const download = await downloadsSublevel.get(key);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...game,
|
||||||
|
download,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { writeFile } from "node:fs/promises";
|
|
||||||
import { spawnSync, exec } from "node:child_process";
|
import { spawnSync, exec } from "node:child_process";
|
||||||
|
|
||||||
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 { downloadsSublevel, levelKeys } from "@main/level";
|
||||||
import { GameShop } from "@types";
|
import { GameShop } from "@types";
|
||||||
|
|
||||||
const executeGameInstaller = (filePath: string) => {
|
const executeGameInstaller = (filePath: string) => {
|
||||||
@ -29,18 +27,18 @@ const openGameInstaller = async (
|
|||||||
shop: GameShop,
|
shop: GameShop,
|
||||||
objectId: string
|
objectId: string
|
||||||
) => {
|
) => {
|
||||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
const downloadKey = levelKeys.game(shop, objectId);
|
||||||
|
const download = await downloadsSublevel.get(downloadKey);
|
||||||
|
|
||||||
if (!game || game.isDeleted || !game.folderName) return true;
|
if (!download || !download.folderName) return true;
|
||||||
|
|
||||||
const gamePath = path.join(
|
const gamePath = path.join(
|
||||||
game.downloadPath ?? (await getDownloadsPath()),
|
download.downloadPath ?? (await getDownloadsPath()),
|
||||||
game.folderName!
|
download.folderName!
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!fs.existsSync(gamePath)) {
|
if (!fs.existsSync(gamePath)) {
|
||||||
// TODO: LEVELDB Remove download?
|
await downloadsSublevel.del(downloadKey);
|
||||||
// await gameRepository.update({ id: gameId }, { status: null });
|
|
||||||
return true;
|
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);
|
shell.openPath(gamePath);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,31 @@
|
|||||||
import { gameRepository } from "@main/repository";
|
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { shell } from "electron";
|
import { shell } from "electron";
|
||||||
import { parseExecutablePath } from "../helpers/parse-executable-path";
|
import { parseExecutablePath } from "../helpers/parse-executable-path";
|
||||||
|
import { levelKeys } from "@main/level";
|
||||||
|
import { gamesSublevel } from "@main/level";
|
||||||
|
import { GameShop } from "@types";
|
||||||
|
|
||||||
const openGame = async (
|
const openGame = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
gameId: number,
|
shop: GameShop,
|
||||||
|
objectId: string,
|
||||||
executablePath: string,
|
executablePath: string,
|
||||||
launchOptions: string | null
|
launchOptions: string | null
|
||||||
) => {
|
) => {
|
||||||
// TODO: revisit this for launchOptions
|
// TODO: revisit this for launchOptions
|
||||||
const parsedPath = parseExecutablePath(executablePath);
|
const parsedPath = parseExecutablePath(executablePath);
|
||||||
|
|
||||||
await gameRepository.update(
|
const gameKey = levelKeys.game(shop, objectId);
|
||||||
{ id: gameId },
|
|
||||||
{ executablePath: parsedPath, launchOptions }
|
const game = await gamesSublevel.get(gameKey);
|
||||||
);
|
|
||||||
|
if (!game) return;
|
||||||
|
|
||||||
|
await gamesSublevel.put(gameKey, {
|
||||||
|
...game,
|
||||||
|
executablePath: parsedPath,
|
||||||
|
launchOptions,
|
||||||
|
});
|
||||||
|
|
||||||
shell.openPath(parsedPath);
|
shell.openPath(parsedPath);
|
||||||
};
|
};
|
||||||
|
@ -1,31 +1,17 @@
|
|||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
|
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { GameShop } from "@types";
|
||||||
import { DownloadQueue, Game } from "@main/entity";
|
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
const cancelGameDownload = async (
|
const cancelGameDownload = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
gameId: number
|
shop: GameShop,
|
||||||
|
objectId: string
|
||||||
) => {
|
) => {
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
await DownloadManager.cancelDownload(shop, objectId);
|
||||||
await DownloadManager.cancelDownload(gameId);
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(DownloadQueue).delete({
|
await downloadsSublevel.del(levelKeys.game(shop, objectId));
|
||||||
game: { id: gameId },
|
|
||||||
});
|
|
||||||
|
|
||||||
await transactionalEntityManager.getRepository(Game).update(
|
|
||||||
{
|
|
||||||
id: gameId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: "removed",
|
|
||||||
bytesDownloaded: 0,
|
|
||||||
progress: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("cancelGameDownload", cancelGameDownload);
|
registerEvent("cancelGameDownload", cancelGameDownload);
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
|
import { downloadsSublevel } from "@main/level";
|
||||||
|
import { levelKeys } from "@main/level";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { gameRepository } from "@main/repository";
|
import type { GameShop } from "@types";
|
||||||
|
|
||||||
const pauseGameSeed = async (
|
const pauseGameSeed = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
gameId: number
|
shop: GameShop,
|
||||||
|
objectId: string
|
||||||
) => {
|
) => {
|
||||||
await gameRepository.update(gameId, {
|
const downloadKey = levelKeys.game(shop, objectId);
|
||||||
status: "complete",
|
const download = await downloadsSublevel.get(downloadKey);
|
||||||
|
|
||||||
|
if (!download) return;
|
||||||
|
|
||||||
|
await downloadsSublevel.put(downloadKey, {
|
||||||
|
...download,
|
||||||
shouldSeed: false,
|
shouldSeed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await DownloadManager.pauseSeeding(gameId);
|
await DownloadManager.pauseSeeding(download);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("pauseGameSeed", pauseGameSeed);
|
registerEvent("pauseGameSeed", pauseGameSeed);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { Not } from "typeorm";
|
import { Not } from "typeorm";
|
||||||
|
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { gameRepository } from "../../repository";
|
|
||||||
|
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { dataSource } from "@main/data-source";
|
||||||
import { DownloadQueue, Game } from "@main/entity";
|
|
||||||
|
|
||||||
const resumeGameDownload = async (
|
const resumeGameDownload = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
|
import { downloadsSublevel } from "@main/level";
|
||||||
|
import { levelKeys } from "@main/level";
|
||||||
import { registerEvent } from "../register-event";
|
import { registerEvent } from "../register-event";
|
||||||
import { gameRepository } from "../../repository";
|
|
||||||
import { DownloadManager } from "@main/services";
|
import { DownloadManager } from "@main/services";
|
||||||
import { Downloader } from "@shared";
|
import type { GameShop } from "@types";
|
||||||
|
|
||||||
const resumeGameSeed = async (
|
const resumeGameSeed = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
gameId: number
|
shop: GameShop,
|
||||||
|
objectId: string
|
||||||
) => {
|
) => {
|
||||||
const game = await gameRepository.findOne({
|
const download = await downloadsSublevel.get(levelKeys.game(shop, objectId));
|
||||||
where: {
|
|
||||||
id: gameId,
|
|
||||||
isDeleted: false,
|
|
||||||
downloader: Downloader.Torrent,
|
|
||||||
progress: 1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!game) return;
|
if (!download) return;
|
||||||
|
|
||||||
await gameRepository.update(gameId, {
|
await downloadsSublevel.put(levelKeys.game(shop, objectId), {
|
||||||
status: "seeding",
|
...download,
|
||||||
shouldSeed: true,
|
shouldSeed: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await DownloadManager.resumeSeeding(game);
|
await DownloadManager.resumeSeeding(download);
|
||||||
};
|
};
|
||||||
|
|
||||||
registerEvent("resumeGameSeed", resumeGameSeed);
|
registerEvent("resumeGameSeed", resumeGameSeed);
|
||||||
|
@ -3,14 +3,11 @@ import updater from "electron-updater";
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import url from "node:url";
|
import url from "node:url";
|
||||||
import fs from "node:fs";
|
|
||||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||||
import { logger, WindowManager } from "@main/services";
|
import { logger, WindowManager } from "@main/services";
|
||||||
import { dataSource } from "@main/data-source";
|
import { dataSource } from "@main/data-source";
|
||||||
import resources from "@locales";
|
import resources from "@locales";
|
||||||
import { userPreferencesRepository } from "@main/repository";
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
import { knexClient, migrationConfig } from "./knex-client";
|
|
||||||
import { databaseDirectory } from "./constants";
|
|
||||||
import { PythonRPC } from "./services/python-rpc";
|
import { PythonRPC } from "./services/python-rpc";
|
||||||
import { Aria2 } from "./services/aria2";
|
import { Aria2 } from "./services/aria2";
|
||||||
|
|
||||||
@ -50,21 +47,6 @@ if (process.defaultApp) {
|
|||||||
app.setAsDefaultProtocolClient(PROTOCOL);
|
app.setAsDefaultProtocolClient(PROTOCOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
const runMigrations = async () => {
|
|
||||||
if (!fs.existsSync(databaseDirectory)) {
|
|
||||||
fs.mkdirSync(databaseDirectory, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
await knexClient.migrate.list(migrationConfig).then((result) => {
|
|
||||||
logger.log(
|
|
||||||
"Migrations to run:",
|
|
||||||
result[1].map((migration) => migration.name)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await knexClient.migrate.latest(migrationConfig);
|
|
||||||
};
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
@ -76,14 +58,6 @@ app.whenReady().then(async () => {
|
|||||||
return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString());
|
return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
await runMigrations()
|
|
||||||
.then(() => {
|
|
||||||
logger.log("Migrations executed successfully");
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.log("Migrations failed to run:", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
await dataSource.initialize();
|
await dataSource.initialize();
|
||||||
|
|
||||||
await import("./main");
|
await import("./main");
|
||||||
|
@ -1,53 +1,6 @@
|
|||||||
import knex, { Knex } from "knex";
|
import knex from "knex";
|
||||||
import { databasePath } from "./constants";
|
import { databasePath } from "./constants";
|
||||||
import { Hydra2_0_3 } from "./migrations/20240830143811_Hydra_2_0_3";
|
|
||||||
import { RepackUris } from "./migrations/20240830143906_RepackUris";
|
|
||||||
import { UpdateUserLanguage } from "./migrations/20240913213944_update_user_language";
|
|
||||||
import { EnsureRepackUris } from "./migrations/20240915035339_ensure_repack_uris";
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns";
|
|
||||||
import { CreateGameAchievement } from "./migrations/20240919030940_create_game_achievement";
|
|
||||||
import { AddAchievementNotificationPreference } from "./migrations/20241013012900_add_achievement_notification_preference";
|
|
||||||
import { CreateUserSubscription } from "./migrations/20241015235142_create_user_subscription";
|
|
||||||
import { AddBackgroundImageUrl } from "./migrations/20241016100249_add_background_image_url";
|
|
||||||
import { AddWinePrefixToGame } from "./migrations/20241019081648_add_wine_prefix_to_game";
|
|
||||||
import { AddStartMinimizedColumn } from "./migrations/20241030171454_add_start_minimized_column";
|
|
||||||
import { AddDisableNsfwAlertColumn } from "./migrations/20241106053733_add_disable_nsfw_alert_column";
|
|
||||||
import { AddShouldSeedColumn } from "./migrations/20241108200154_add_should_seed_colum";
|
|
||||||
import { AddSeedAfterDownloadColumn } from "./migrations/20241108201806_add_seed_after_download";
|
|
||||||
import { AddHiddenAchievementDescriptionColumn } from "./migrations/20241216140633_add_hidden_achievement_description_column ";
|
|
||||||
import { AddLaunchOptionsColumnToGame } from "./migrations/20241226044022_add_launch_options_column_to_game";
|
|
||||||
|
|
||||||
export type HydraMigration = Knex.Migration & { name: string };
|
|
||||||
|
|
||||||
class MigrationSource implements Knex.MigrationSource<HydraMigration> {
|
|
||||||
getMigrations(): Promise<HydraMigration[]> {
|
|
||||||
return Promise.resolve([
|
|
||||||
Hydra2_0_3,
|
|
||||||
RepackUris,
|
|
||||||
UpdateUserLanguage,
|
|
||||||
EnsureRepackUris,
|
|
||||||
FixMissingColumns,
|
|
||||||
CreateGameAchievement,
|
|
||||||
AddAchievementNotificationPreference,
|
|
||||||
CreateUserSubscription,
|
|
||||||
AddBackgroundImageUrl,
|
|
||||||
AddWinePrefixToGame,
|
|
||||||
AddStartMinimizedColumn,
|
|
||||||
AddDisableNsfwAlertColumn,
|
|
||||||
AddShouldSeedColumn,
|
|
||||||
AddSeedAfterDownloadColumn,
|
|
||||||
AddHiddenAchievementDescriptionColumn,
|
|
||||||
AddLaunchOptionsColumnToGame,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
getMigrationName(migration: HydraMigration): string {
|
|
||||||
return migration.name;
|
|
||||||
}
|
|
||||||
getMigration(migration: HydraMigration): Promise<Knex.Migration> {
|
|
||||||
return Promise.resolve(migration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const knexClient = knex({
|
export const knexClient = knex({
|
||||||
debug: !app.isPackaged,
|
debug: !app.isPackaged,
|
||||||
@ -56,7 +9,3 @@ export const knexClient = knex({
|
|||||||
filename: databasePath,
|
filename: databasePath,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const migrationConfig: Knex.MigratorConfig = {
|
|
||||||
migrationSource: new MigrationSource(),
|
|
||||||
};
|
|
||||||
|
11
src/main/level/sublevels/downloads.ts
Normal file
11
src/main/level/sublevels/downloads.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { Download } from "@types";
|
||||||
|
|
||||||
|
import { db } from "../level";
|
||||||
|
import { levelKeys } from "./keys";
|
||||||
|
|
||||||
|
export const downloadsSublevel = db.sublevel<string, Download>(
|
||||||
|
levelKeys.downloads,
|
||||||
|
{
|
||||||
|
valueEncoding: "json",
|
||||||
|
}
|
||||||
|
);
|
@ -1,171 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const Hydra2_0_3: HydraMigration = {
|
|
||||||
name: "Hydra_2_0_3",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
const timestamp = new Date().getTime();
|
|
||||||
|
|
||||||
await knex.schema.hasTable("migrations").then(async (exists) => {
|
|
||||||
if (exists) {
|
|
||||||
await knex.schema.dropTable("migrations");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("download_source").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("download_source", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table
|
|
||||||
.text("url")
|
|
||||||
.unique({ indexName: "download_source_url_unique_" + timestamp });
|
|
||||||
table.text("name").notNullable();
|
|
||||||
table.text("etag");
|
|
||||||
table.integer("downloadCount").notNullable().defaultTo(0);
|
|
||||||
table.text("status").notNullable().defaultTo(0);
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("repack").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("repack", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table
|
|
||||||
.text("title")
|
|
||||||
.notNullable()
|
|
||||||
.unique({ indexName: "repack_title_unique_" + timestamp });
|
|
||||||
table
|
|
||||||
.text("magnet")
|
|
||||||
.notNullable()
|
|
||||||
.unique({ indexName: "repack_magnet_unique_" + timestamp });
|
|
||||||
table.integer("page");
|
|
||||||
table.text("repacker").notNullable();
|
|
||||||
table.text("fileSize").notNullable();
|
|
||||||
table.datetime("uploadDate").notNullable();
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table
|
|
||||||
.integer("downloadSourceId")
|
|
||||||
.references("download_source.id")
|
|
||||||
.onDelete("CASCADE");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("game").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("game", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table
|
|
||||||
.text("objectID")
|
|
||||||
.notNullable()
|
|
||||||
.unique({ indexName: "game_objectID_unique_" + timestamp });
|
|
||||||
table
|
|
||||||
.text("remoteId")
|
|
||||||
.unique({ indexName: "game_remoteId_unique_" + timestamp });
|
|
||||||
table.text("title").notNullable();
|
|
||||||
table.text("iconUrl");
|
|
||||||
table.text("folderName");
|
|
||||||
table.text("downloadPath");
|
|
||||||
table.text("executablePath");
|
|
||||||
table.integer("playTimeInMilliseconds").notNullable().defaultTo(0);
|
|
||||||
table.text("shop").notNullable();
|
|
||||||
table.text("status");
|
|
||||||
table.integer("downloader").notNullable().defaultTo(1);
|
|
||||||
table.float("progress").notNullable().defaultTo(0);
|
|
||||||
table.integer("bytesDownloaded").notNullable().defaultTo(0);
|
|
||||||
table.datetime("lastTimePlayed");
|
|
||||||
table.float("fileSize").notNullable().defaultTo(0);
|
|
||||||
table.text("uri");
|
|
||||||
table.boolean("isDeleted").notNullable().defaultTo(0);
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table
|
|
||||||
.integer("repackId")
|
|
||||||
.references("repack.id")
|
|
||||||
.unique("repack_repackId_unique_" + timestamp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("user_preferences").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("user_preferences", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.text("downloadsPath");
|
|
||||||
table.text("language").notNullable().defaultTo("en");
|
|
||||||
table.text("realDebridApiToken");
|
|
||||||
table
|
|
||||||
.boolean("downloadNotificationsEnabled")
|
|
||||||
.notNullable()
|
|
||||||
.defaultTo(0);
|
|
||||||
table
|
|
||||||
.boolean("repackUpdatesNotificationsEnabled")
|
|
||||||
.notNullable()
|
|
||||||
.defaultTo(0);
|
|
||||||
table.boolean("preferQuitInsteadOfHiding").notNullable().defaultTo(0);
|
|
||||||
table.boolean("runAtStartup").notNullable().defaultTo(0);
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("game_shop_cache").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("game_shop_cache", (table) => {
|
|
||||||
table.text("objectID").primary().notNullable();
|
|
||||||
table.text("shop").notNullable();
|
|
||||||
table.text("serializedData");
|
|
||||||
table.text("howLongToBeatSerializedData");
|
|
||||||
table.text("language");
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("download_queue").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("download_queue", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table
|
|
||||||
.integer("gameId")
|
|
||||||
.references("game.id")
|
|
||||||
.unique("download_queue_gameId_unique_" + timestamp);
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasTable("user_auth").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.createTable("user_auth", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.text("userId").notNullable().defaultTo("");
|
|
||||||
table.text("displayName").notNullable().defaultTo("");
|
|
||||||
table.text("profileImageUrl");
|
|
||||||
table.text("accessToken").notNullable().defaultTo("");
|
|
||||||
table.text("refreshToken").notNullable().defaultTo("");
|
|
||||||
table.integer("tokenExpirationTimestamp").notNullable().defaultTo(0);
|
|
||||||
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
await knex.schema.dropTableIfExists("game");
|
|
||||||
await knex.schema.dropTableIfExists("repack");
|
|
||||||
await knex.schema.dropTableIfExists("download_queue");
|
|
||||||
await knex.schema.dropTableIfExists("user_auth");
|
|
||||||
await knex.schema.dropTableIfExists("game_shop_cache");
|
|
||||||
await knex.schema.dropTableIfExists("user_preferences");
|
|
||||||
await knex.schema.dropTableIfExists("download_source");
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const RepackUris: HydraMigration = {
|
|
||||||
name: "RepackUris",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
await knex.schema.alterTable("repack", (table) => {
|
|
||||||
table.text("uris").notNullable().defaultTo("[]");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
await knex.schema.alterTable("repack", (table) => {
|
|
||||||
table.integer("page");
|
|
||||||
table.dropColumn("uris");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,13 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const UpdateUserLanguage: HydraMigration = {
|
|
||||||
name: "UpdateUserLanguage",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
await knex("user_preferences")
|
|
||||||
.update("language", "pt-BR")
|
|
||||||
.where("language", "pt");
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (_knex: Knex) => {},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const EnsureRepackUris: HydraMigration = {
|
|
||||||
name: "EnsureRepackUris",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
await knex.schema.hasColumn("repack", "uris").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.table("repack", (table) => {
|
|
||||||
table.text("uris").notNullable().defaultTo("[]");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (_knex: Knex) => {},
|
|
||||||
};
|
|
@ -1,41 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const FixMissingColumns: HydraMigration = {
|
|
||||||
name: "FixMissingColumns",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
const timestamp = new Date().getTime();
|
|
||||||
await knex.schema
|
|
||||||
.hasColumn("repack", "downloadSourceId")
|
|
||||||
.then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.table("repack", (table) => {
|
|
||||||
table
|
|
||||||
.integer("downloadSourceId")
|
|
||||||
.references("download_source.id")
|
|
||||||
.onDelete("CASCADE");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasColumn("game", "remoteId").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.table("game", (table) => {
|
|
||||||
table
|
|
||||||
.text("remoteId")
|
|
||||||
.unique({ indexName: "game_remoteId_unique_" + timestamp });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await knex.schema.hasColumn("game", "uri").then(async (exists) => {
|
|
||||||
if (!exists) {
|
|
||||||
await knex.schema.table("game", (table) => {
|
|
||||||
table.text("uri");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (_knex: Knex) => {},
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const CreateGameAchievement: HydraMigration = {
|
|
||||||
name: "CreateGameAchievement",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.createTable("game_achievement", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.text("objectId").notNullable();
|
|
||||||
table.text("shop").notNullable();
|
|
||||||
table.text("achievements");
|
|
||||||
table.text("unlockedAchievements");
|
|
||||||
table.unique(["objectId", "shop"]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (knex: Knex) => {
|
|
||||||
return knex.schema.dropTable("game_achievement");
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddAchievementNotificationPreference: HydraMigration = {
|
|
||||||
name: "AddAchievementNotificationPreference",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.boolean("achievementNotificationsEnabled").defaultTo(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.dropColumn("achievementNotificationsEnabled");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,27 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const CreateUserSubscription: HydraMigration = {
|
|
||||||
name: "CreateUserSubscription",
|
|
||||||
up: async (knex: Knex) => {
|
|
||||||
return knex.schema.createTable("user_subscription", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.string("subscriptionId").defaultTo("");
|
|
||||||
table
|
|
||||||
.text("userId")
|
|
||||||
.notNullable()
|
|
||||||
.references("user_auth.id")
|
|
||||||
.onDelete("CASCADE");
|
|
||||||
table.string("status").defaultTo("");
|
|
||||||
table.string("planId").defaultTo("");
|
|
||||||
table.string("planName").defaultTo("");
|
|
||||||
table.dateTime("expiresAt").nullable();
|
|
||||||
table.dateTime("createdAt").defaultTo(knex.fn.now());
|
|
||||||
table.dateTime("updatedAt").defaultTo(knex.fn.now());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.dropTable("user_subscription");
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddBackgroundImageUrl: HydraMigration = {
|
|
||||||
name: "AddBackgroundImageUrl",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_auth", (table) => {
|
|
||||||
return table.text("backgroundImageUrl").nullable();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_auth", (table) => {
|
|
||||||
return table.dropColumn("backgroundImageUrl");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddWinePrefixToGame: HydraMigration = {
|
|
||||||
name: "AddWinePrefixToGame",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.text("winePrefixPath").nullable();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.dropColumn("winePrefixPath");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddStartMinimizedColumn: HydraMigration = {
|
|
||||||
name: "AddStartMinimizedColumn",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.boolean("startMinimized").notNullable().defaultTo(0);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.dropColumn("startMinimized");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddDisableNsfwAlertColumn: HydraMigration = {
|
|
||||||
name: "AddDisableNsfwAlertColumn",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.boolean("disableNsfwAlert").notNullable().defaultTo(0);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.dropColumn("disableNsfwAlert");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddShouldSeedColumn: HydraMigration = {
|
|
||||||
name: "AddShouldSeedColumn",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.boolean("shouldSeed").notNullable().defaultTo(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.dropColumn("shouldSeed");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddSeedAfterDownloadColumn: HydraMigration = {
|
|
||||||
name: "AddSeedAfterDownloadColumn",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table
|
|
||||||
.boolean("seedAfterDownloadComplete")
|
|
||||||
.notNullable()
|
|
||||||
.defaultTo(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.dropColumn("seedAfterDownloadComplete");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddHiddenAchievementDescriptionColumn: HydraMigration = {
|
|
||||||
name: "AddHiddenAchievementDescriptionColumn",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table
|
|
||||||
.boolean("showHiddenAchievementsDescription")
|
|
||||||
.notNullable()
|
|
||||||
.defaultTo(0);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("user_preferences", (table) => {
|
|
||||||
return table.dropColumn("showHiddenAchievementsDescription");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const AddLaunchOptionsColumnToGame: HydraMigration = {
|
|
||||||
name: "AddLaunchOptionsColumnToGame",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.string("launchOptions").nullable();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {
|
|
||||||
return knex.schema.alterTable("game", (table) => {
|
|
||||||
return table.dropColumn("launchOptions");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import type { HydraMigration } from "@main/knex-client";
|
|
||||||
import type { Knex } from "knex";
|
|
||||||
|
|
||||||
export const MigrationName: HydraMigration = {
|
|
||||||
name: "MigrationName",
|
|
||||||
up: (knex: Knex) => {
|
|
||||||
return knex.schema.createTable("table_name", async (table) => {});
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (knex: Knex) => {},
|
|
||||||
};
|
|
@ -1,9 +1,5 @@
|
|||||||
import { dataSource } from "./data-source";
|
import { dataSource } from "./data-source";
|
||||||
import { DownloadQueue, Game, UserPreferences } from "@main/entity";
|
import { UserPreferences } from "@main/entity";
|
||||||
|
|
||||||
export const gameRepository = dataSource.getRepository(Game);
|
|
||||||
|
|
||||||
export const userPreferencesRepository =
|
export const userPreferencesRepository =
|
||||||
dataSource.getRepository(UserPreferences);
|
dataSource.getRepository(UserPreferences);
|
||||||
|
|
||||||
export const downloadQueueRepository = dataSource.getRepository(DownloadQueue);
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { gameRepository } from "@main/repository";
|
|
||||||
import { parseAchievementFile } from "./parse-achievement-file";
|
import { parseAchievementFile } from "./parse-achievement-file";
|
||||||
import { Game } from "@main/entity";
|
|
||||||
import { mergeAchievements } from "./merge-achievements";
|
import { mergeAchievements } from "./merge-achievements";
|
||||||
import fs, { readdirSync } from "node:fs";
|
import fs, { readdirSync } from "node:fs";
|
||||||
import {
|
import {
|
||||||
@ -9,10 +7,9 @@ import {
|
|||||||
findAllAchievementFiles,
|
findAllAchievementFiles,
|
||||||
getAlternativeObjectIds,
|
getAlternativeObjectIds,
|
||||||
} from "./find-achivement-files";
|
} from "./find-achivement-files";
|
||||||
import type { AchievementFile, UnlockedAchievement } from "@types";
|
import type { AchievementFile, Game, UnlockedAchievement } from "@types";
|
||||||
import { achievementsLogger } from "../logger";
|
import { achievementsLogger } from "../logger";
|
||||||
import { Cracker } from "@shared";
|
import { Cracker } from "@shared";
|
||||||
import { IsNull, Not } from "typeorm";
|
|
||||||
import { publishCombinedNewAchievementNotification } from "../notifications";
|
import { publishCombinedNewAchievementNotification } from "../notifications";
|
||||||
import { gamesSublevel } from "@main/level";
|
import { gamesSublevel } from "@main/level";
|
||||||
|
|
||||||
@ -47,12 +44,12 @@ const watchAchievementsWindows = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const watchAchievementsWithWine = async () => {
|
const watchAchievementsWithWine = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gamesSublevel
|
||||||
where: {
|
.values()
|
||||||
isDeleted: false,
|
.all()
|
||||||
winePrefixPath: Not(IsNull()),
|
.then((games) =>
|
||||||
},
|
games.filter((game) => !game.isDeleted && game.winePrefixPath)
|
||||||
});
|
);
|
||||||
|
|
||||||
for (const game of games) {
|
for (const game of games) {
|
||||||
const gameAchievementFiles = findAchievementFiles(game);
|
const gameAchievementFiles = findAchievementFiles(game);
|
||||||
@ -188,11 +185,10 @@ export class AchievementWatcherManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static preSearchAchievementsWindows = async () => {
|
private static preSearchAchievementsWindows = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gamesSublevel
|
||||||
where: {
|
.values()
|
||||||
isDeleted: false,
|
.all()
|
||||||
},
|
.then((games) => games.filter((game) => !game.isDeleted));
|
||||||
});
|
|
||||||
|
|
||||||
const gameAchievementFilesMap = findAllAchievementFiles();
|
const gameAchievementFilesMap = findAllAchievementFiles();
|
||||||
|
|
||||||
@ -200,7 +196,7 @@ export class AchievementWatcherManager {
|
|||||||
games.map((game) => {
|
games.map((game) => {
|
||||||
const gameAchievementFiles: AchievementFile[] = [];
|
const gameAchievementFiles: AchievementFile[] = [];
|
||||||
|
|
||||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
for (const objectId of getAlternativeObjectIds(game.objectId)) {
|
||||||
gameAchievementFiles.push(
|
gameAchievementFiles.push(
|
||||||
...(gameAchievementFilesMap.get(objectId) || [])
|
...(gameAchievementFilesMap.get(objectId) || [])
|
||||||
);
|
);
|
||||||
@ -216,11 +212,10 @@ export class AchievementWatcherManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static preSearchAchievementsWithWine = async () => {
|
private static preSearchAchievementsWithWine = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gamesSublevel
|
||||||
where: {
|
.values()
|
||||||
isDeleted: false,
|
.all()
|
||||||
},
|
.then((games) => games.filter((game) => !game.isDeleted));
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
games.map((game) => {
|
games.map((game) => {
|
||||||
|
@ -254,7 +254,7 @@ export const findAchievementFiles = (game: Game) => {
|
|||||||
|
|
||||||
for (const cracker of crackers) {
|
for (const cracker of crackers) {
|
||||||
for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) {
|
for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) {
|
||||||
for (const objectId of getAlternativeObjectIds(game.objectID)) {
|
for (const objectId of getAlternativeObjectIds(game.objectId)) {
|
||||||
const filePath = path.join(
|
const filePath = path.join(
|
||||||
game.winePrefixPath ?? "",
|
game.winePrefixPath ?? "",
|
||||||
folderPath,
|
folderPath,
|
||||||
|
@ -4,8 +4,7 @@ import {
|
|||||||
} from "./find-achivement-files";
|
} from "./find-achivement-files";
|
||||||
import { parseAchievementFile } from "./parse-achievement-file";
|
import { parseAchievementFile } from "./parse-achievement-file";
|
||||||
import { mergeAchievements } from "./merge-achievements";
|
import { mergeAchievements } from "./merge-achievements";
|
||||||
import type { UnlockedAchievement } from "@types";
|
import type { Game, UnlockedAchievement } from "@types";
|
||||||
import { Game } from "@main/entity";
|
|
||||||
|
|
||||||
export const updateLocalUnlockedAchivements = async (game: Game) => {
|
export const updateLocalUnlockedAchivements = async (game: Game) => {
|
||||||
const gameAchievementFiles = findAchievementFiles(game);
|
const gameAchievementFiles = findAchievementFiles(game);
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { Game } from "@main/entity";
|
|
||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
import { WindowManager } from "../window-manager";
|
import { WindowManager } from "../window-manager";
|
||||||
import {
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
downloadQueueRepository,
|
|
||||||
gameRepository,
|
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
import { publishDownloadCompleteNotification } from "../notifications";
|
import { publishDownloadCompleteNotification } from "../notifications";
|
||||||
import type { Download, DownloadProgress } from "@types";
|
import type { Download, DownloadProgress } from "@types";
|
||||||
import { GofileApi, QiwiApi, DatanodesApi } from "../hosters";
|
import { GofileApi, QiwiApi, DatanodesApi } from "../hosters";
|
||||||
@ -23,7 +18,7 @@ import { logger } from "../logger";
|
|||||||
import { downloadsSublevel, levelKeys } from "@main/level";
|
import { downloadsSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloadingGameId: number | null = null;
|
private static downloadingGameId: string | null = null;
|
||||||
|
|
||||||
public static async startRPC(
|
public static async startRPC(
|
||||||
download?: Download,
|
download?: Download,
|
||||||
@ -34,13 +29,15 @@ export class DownloadManager {
|
|||||||
? await this.getDownloadPayload(download).catch(() => undefined)
|
? await this.getDownloadPayload(download).catch(() => undefined)
|
||||||
: undefined,
|
: undefined,
|
||||||
downloadsToSeed?.map((download) => ({
|
downloadsToSeed?.map((download) => ({
|
||||||
game_id: game.id,
|
game_id: `${download.shop}-${download.objectId}`,
|
||||||
url: game.uri!,
|
url: download.uri!,
|
||||||
save_path: game.downloadPath!,
|
save_path: download.downloadPath!,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
this.downloadingGameId = game?.id ?? null;
|
if (download) {
|
||||||
|
this.downloadingGameId = `${download.shop}-${download.objectId}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async getDownloadStatus() {
|
private static async getDownloadStatus() {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export interface PauseDownloadPayload {
|
export interface PauseDownloadPayload {
|
||||||
game_id: number;
|
game_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CancelDownloadPayload {
|
export interface CancelDownloadPayload {
|
||||||
game_id: number;
|
game_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LibtorrentStatus {
|
export enum LibtorrentStatus {
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { gameRepository } from "@main/repository";
|
|
||||||
import { HydraApi } from "../hydra-api";
|
import { HydraApi } from "../hydra-api";
|
||||||
import { steamGamesWorker } from "@main/workers";
|
import { steamGamesWorker } from "@main/workers";
|
||||||
import { steamUrlBuilder } from "@shared";
|
import { steamUrlBuilder } from "@shared";
|
||||||
|
import { gamesSublevel, levelKeys } from "@main/level";
|
||||||
|
|
||||||
export const mergeWithRemoteGames = async () => {
|
export const mergeWithRemoteGames = async () => {
|
||||||
return HydraApi.get("/profile/games")
|
return HydraApi.get("/profile/games")
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
for (const game of response) {
|
for (const game of response) {
|
||||||
const localGame = await gameRepository.findOne({
|
const localGame = await gamesSublevel.get(
|
||||||
where: {
|
levelKeys.game(game.shop, game.objectId)
|
||||||
objectID: game.objectId,
|
);
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (localGame) {
|
if (localGame) {
|
||||||
const updatedLastTimePlayed =
|
const updatedLastTimePlayed =
|
||||||
@ -26,17 +24,12 @@ export const mergeWithRemoteGames = async () => {
|
|||||||
? game.playTimeInMilliseconds
|
? game.playTimeInMilliseconds
|
||||||
: localGame.playTimeInMilliseconds;
|
: localGame.playTimeInMilliseconds;
|
||||||
|
|
||||||
gameRepository.update(
|
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
||||||
{
|
...localGame,
|
||||||
objectID: game.objectId,
|
remoteId: game.id,
|
||||||
shop: "steam",
|
lastTimePlayed: updatedLastTimePlayed,
|
||||||
},
|
playTimeInMilliseconds: updatedPlayTime,
|
||||||
{
|
});
|
||||||
remoteId: game.id,
|
|
||||||
lastTimePlayed: updatedLastTimePlayed,
|
|
||||||
playTimeInMilliseconds: updatedPlayTime,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
const steamGame = await steamGamesWorker.run(Number(game.objectId), {
|
||||||
name: "getById",
|
name: "getById",
|
||||||
@ -47,14 +40,15 @@ export const mergeWithRemoteGames = async () => {
|
|||||||
? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon)
|
? steamUrlBuilder.icon(game.objectId, steamGame.clientIcon)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
gameRepository.insert({
|
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
|
||||||
objectID: game.objectId,
|
objectId: game.objectId,
|
||||||
title: steamGame?.name,
|
title: steamGame?.name,
|
||||||
remoteId: game.id,
|
remoteId: game.id,
|
||||||
shop: game.shop,
|
shop: game.shop,
|
||||||
iconUrl,
|
iconUrl,
|
||||||
lastTimePlayed: game.lastTimePlayed,
|
lastTimePlayed: game.lastTimePlayed,
|
||||||
playTimeInMilliseconds: game.playTimeInMilliseconds,
|
playTimeInMilliseconds: game.playTimeInMilliseconds,
|
||||||
|
isDeleted: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Notification, app } from "electron";
|
import { Notification, app } from "electron";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import trayIcon from "@resources/tray-icon.png?asset";
|
import trayIcon from "@resources/tray-icon.png?asset";
|
||||||
import { Game } from "@main/entity";
|
|
||||||
import { userPreferencesRepository } from "@main/repository";
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@ -11,6 +10,7 @@ import { achievementSoundPath } from "@main/constants";
|
|||||||
import icon from "@resources/icon.png?asset";
|
import icon from "@resources/icon.png?asset";
|
||||||
import { NotificationOptions, toXmlString } from "./xml";
|
import { NotificationOptions, toXmlString } from "./xml";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
|
import type { Game } from "@types";
|
||||||
|
|
||||||
async function downloadImage(url: string | null) {
|
async function downloadImage(url: string | null) {
|
||||||
if (!url) return undefined;
|
if (!url) return undefined;
|
||||||
|
@ -10,7 +10,7 @@ import { Readable } from "node:stream";
|
|||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
|
|
||||||
interface GamePayload {
|
interface GamePayload {
|
||||||
game_id: number;
|
game_id: string;
|
||||||
url: string;
|
url: string;
|
||||||
save_path: string;
|
save_path: string;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,11 @@ import i18next, { t } from "i18next";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import icon from "@resources/icon.png?asset";
|
import icon from "@resources/icon.png?asset";
|
||||||
import trayIcon from "@resources/tray-icon.png?asset";
|
import trayIcon from "@resources/tray-icon.png?asset";
|
||||||
import { gameRepository, userPreferencesRepository } from "@main/repository";
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
import { IsNull, Not } from "typeorm";
|
|
||||||
import { HydraApi } from "./hydra-api";
|
import { HydraApi } from "./hydra-api";
|
||||||
import UserAgent from "user-agents";
|
import UserAgent from "user-agents";
|
||||||
|
import { gamesSublevel } from "@main/level";
|
||||||
|
import { slice, sortBy } from "lodash-es";
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
public static mainWindow: Electron.BrowserWindow | null = null;
|
public static mainWindow: Electron.BrowserWindow | null = null;
|
||||||
@ -207,17 +208,22 @@ export class WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateSystemTray = async () => {
|
const updateSystemTray = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gamesSublevel
|
||||||
where: {
|
.values()
|
||||||
isDeleted: false,
|
.all()
|
||||||
executablePath: Not(IsNull()),
|
.then((games) =>
|
||||||
lastTimePlayed: Not(IsNull()),
|
slice(
|
||||||
},
|
sortBy(
|
||||||
take: 5,
|
games.filter(
|
||||||
order: {
|
(game) =>
|
||||||
lastTimePlayed: "DESC",
|
!game.isDeleted && game.executablePath && game.lastTimePlayed
|
||||||
},
|
),
|
||||||
});
|
"lastTimePlayed",
|
||||||
|
"DESC"
|
||||||
|
),
|
||||||
|
5
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
|
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
|
||||||
games.map(({ title, executablePath }) => ({
|
games.map(({ title, executablePath }) => ({
|
||||||
|
@ -101,9 +101,9 @@ export function GameDetailsContextProvider({
|
|||||||
|
|
||||||
const updateGame = useCallback(async () => {
|
const updateGame = useCallback(async () => {
|
||||||
return window.electron
|
return window.electron
|
||||||
.getGameByObjectId(objectId!)
|
.getGameByObjectId(shop, objectId!)
|
||||||
.then((result) => setGame(result));
|
.then((result) => setGame(result));
|
||||||
}, [setGame, objectId]);
|
}, [setGame, shop, objectId]);
|
||||||
|
|
||||||
const isGameDownloading = lastPacket?.game.id === game?.id;
|
const isGameDownloading = lastPacket?.game.id === game?.id;
|
||||||
|
|
||||||
|
@ -9,7 +9,11 @@ import {
|
|||||||
setGameDeleting,
|
setGameDeleting,
|
||||||
removeGameFromDeleting,
|
removeGameFromDeleting,
|
||||||
} from "@renderer/features";
|
} from "@renderer/features";
|
||||||
import type { DownloadProgress, StartGameDownloadPayload } from "@types";
|
import type {
|
||||||
|
DownloadProgress,
|
||||||
|
GameShop,
|
||||||
|
StartGameDownloadPayload,
|
||||||
|
} from "@types";
|
||||||
import { useDate } from "./use-date";
|
import { useDate } from "./use-date";
|
||||||
import { formatBytes } from "@shared";
|
import { formatBytes } from "@shared";
|
||||||
|
|
||||||
@ -31,48 +35,48 @@ export function useDownload() {
|
|||||||
return game;
|
return game;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pauseDownload = async (gameId: number) => {
|
const pauseDownload = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.pauseGameDownload(gameId);
|
await window.electron.pauseGameDownload(shop, objectId);
|
||||||
await updateLibrary();
|
await updateLibrary();
|
||||||
dispatch(clearDownload());
|
dispatch(clearDownload());
|
||||||
};
|
};
|
||||||
|
|
||||||
const resumeDownload = async (gameId: number) => {
|
const resumeDownload = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.resumeGameDownload(gameId);
|
await window.electron.resumeGameDownload(shop, objectId);
|
||||||
return updateLibrary();
|
return updateLibrary();
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeGameInstaller = async (gameId: number) => {
|
const removeGameInstaller = async (shop: GameShop, objectId: string) => {
|
||||||
dispatch(setGameDeleting(gameId));
|
dispatch(setGameDeleting(objectId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.electron.deleteGameFolder(gameId);
|
await window.electron.deleteGameFolder(shop, objectId);
|
||||||
updateLibrary();
|
updateLibrary();
|
||||||
} finally {
|
} finally {
|
||||||
dispatch(removeGameFromDeleting(gameId));
|
dispatch(removeGameFromDeleting(objectId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelDownload = async (gameId: number) => {
|
const cancelDownload = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.cancelGameDownload(gameId);
|
await window.electron.cancelGameDownload(shop, objectId);
|
||||||
dispatch(clearDownload());
|
dispatch(clearDownload());
|
||||||
updateLibrary();
|
updateLibrary();
|
||||||
|
|
||||||
removeGameInstaller(gameId);
|
removeGameInstaller(shop, objectId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeGameFromLibrary = (gameId: number) =>
|
const removeGameFromLibrary = (shop: GameShop, objectId: string) =>
|
||||||
window.electron.removeGameFromLibrary(gameId).then(() => {
|
window.electron.removeGameFromLibrary(shop, objectId).then(() => {
|
||||||
updateLibrary();
|
updateLibrary();
|
||||||
});
|
});
|
||||||
|
|
||||||
const pauseSeeding = async (gameId: number) => {
|
const pauseSeeding = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.pauseGameSeed(gameId);
|
await window.electron.pauseGameSeed(shop, objectId);
|
||||||
await updateLibrary();
|
await updateLibrary();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resumeSeeding = async (gameId: number) => {
|
const resumeSeeding = async (shop: GameShop, objectId: string) => {
|
||||||
await window.electron.resumeGameSeed(gameId);
|
await window.electron.resumeGameSeed(shop, objectId);
|
||||||
await updateLibrary();
|
await updateLibrary();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,8 +94,8 @@ export function useDownload() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isGameDeleting = (gameId: number) => {
|
const isGameDeleting = (objectId: string) => {
|
||||||
return gamesWithDeletionInProgress.includes(gameId);
|
return gamesWithDeletionInProgress.includes(objectId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -33,7 +33,6 @@ export interface User {
|
|||||||
export interface Game {
|
export interface Game {
|
||||||
title: string;
|
title: string;
|
||||||
iconUrl: string | null;
|
iconUrl: string | null;
|
||||||
status: GameStatus | null;
|
|
||||||
playTimeInMilliseconds: number;
|
playTimeInMilliseconds: number;
|
||||||
lastTimePlayed: Date | null;
|
lastTimePlayed: Date | null;
|
||||||
objectId: string;
|
objectId: string;
|
||||||
@ -58,6 +57,8 @@ export interface Download {
|
|||||||
lastTimePlayed: Date | null;
|
lastTimePlayed: Date | null;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
shouldSeed: boolean;
|
shouldSeed: boolean;
|
||||||
|
// TODO: Rename to DownloadStatus
|
||||||
|
status: GameStatus | null;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user