feat: adding new messages to hero panel

This commit is contained in:
Chubby Granny Chaser
2024-05-24 00:26:21 +01:00
parent a240c3ae24
commit d431c01d1b
28 changed files with 398 additions and 358 deletions

View File

@@ -6,7 +6,7 @@ import { gameRepository, userPreferencesRepository } from "@main/repository";
import path from "node:path";
import { WindowManager } from "./window-manager";
import { RealDebridClient } from "./real-debrid";
import { Notification } from "electron";
import { Notification, app } from "electron";
import { t } from "i18next";
import { Downloader } from "@shared";
import { DownloadProgress } from "@types";
@@ -16,33 +16,36 @@ import { Game } from "@main/entity";
export class DownloadManager {
private static downloads = new Map<number, string>();
private static connected = false;
private static gid: string | null = null;
private static gameId: number | null = null;
private static aria2 = new Aria2({});
static async connect() {
const binary = path.join(
__dirname,
"..",
"..",
"aria2-1.37.0-win-64bit-build1",
"aria2c"
);
private static connect(): Promise<boolean> {
return new Promise((resolve) => {
const binaryPath = app.isPackaged
? path.join(process.resourcesPath, "aria2", "aria2c")
: path.join(__dirname, "..", "..", "aria2", "aria2c");
spawn(
binary,
[
const cp = spawn(binaryPath, [
"--enable-rpc",
"--rpc-listen-all",
"--file-allocation=none",
"--allow-overwrite=true",
],
{ stdio: "inherit" }
);
]);
await this.aria2.open();
this.attachListener();
cp.stdout.on("data", async (data) => {
const msg = Buffer.from(data).toString("utf-8");
if (msg.includes("IPv6 RPC: listening on TCP")) {
await this.aria2.open();
this.connected = true;
resolve(true);
}
});
});
}
private static getETA(status: StatusResponse) {
@@ -84,91 +87,78 @@ export class DownloadManager {
return "";
}
private static async attachListener() {
// eslint-disable-next-line no-constant-condition
while (true) {
try {
if (!this.gid || !this.gameId) {
continue;
public static async watchDownloads() {
if (!this.gid || !this.gameId) return;
const status = await this.aria2.call("tellStatus", this.gid);
const downloadingMetadata = status.bittorrent && !status.bittorrent?.info;
if (status.followedBy?.length) {
this.gid = status.followedBy[0];
this.downloads.set(this.gameId, this.gid);
return;
}
const progress =
Number(status.completedLength) / Number(status.totalLength);
if (!downloadingMetadata) {
const update: QueryDeepPartialEntity<Game> = {
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
status: status.status,
};
if (!isNaN(progress)) update.progress = progress;
await gameRepository.update(
{ id: this.gameId },
{
...update,
status: status.status,
folderName: this.getFolderName(status),
}
);
}
const status = await this.aria2.call("tellStatus", this.gid);
const game = await gameRepository.findOne({
where: { id: this.gameId, isDeleted: false },
relations: { repack: true },
});
const downloadingMetadata =
status.bittorrent && !status.bittorrent?.info;
if (status.followedBy?.length) {
this.gid = status.followedBy[0];
this.downloads.set(this.gameId, this.gid);
continue;
}
const progress =
Number(status.completedLength) / Number(status.totalLength);
if (!downloadingMetadata) {
const update: QueryDeepPartialEntity<Game> = {
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
status: status.status,
};
if (!isNaN(progress)) update.progress = progress;
await gameRepository.update(
{ id: this.gameId },
{
...update,
status: status.status,
folderName: this.getFolderName(status),
}
);
}
const game = await gameRepository.findOne({
where: { id: this.gameId, isDeleted: false },
relations: { repack: true },
});
if (progress === 1 && game && !downloadingMetadata) {
await this.publishNotification();
/*
Only cancel bittorrent downloads to stop seeding
*/
if (status.bittorrent) {
await this.cancelDownload(game.id);
} else {
this.clearCurrentDownload();
}
}
if (WindowManager.mainWindow && game) {
WindowManager.mainWindow.setProgressBar(
progress === 1 || downloadingMetadata ? -1 : progress,
{ mode: downloadingMetadata ? "indeterminate" : "normal" }
);
const payload = {
progress,
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
numPeers: Number(status.connections),
numSeeds: Number(status.numSeeders ?? 0),
downloadSpeed: Number(status.downloadSpeed),
timeRemaining: this.getETA(status),
downloadingMetadata: !!downloadingMetadata,
game,
} as DownloadProgress;
WindowManager.mainWindow.webContents.send(
"on-download-progress",
JSON.parse(JSON.stringify(payload))
);
}
} finally {
await new Promise((resolve) => setTimeout(resolve, 500));
if (progress === 1 && game && !downloadingMetadata) {
await this.publishNotification();
/*
Only cancel bittorrent downloads to stop seeding
*/
if (status.bittorrent) {
await this.cancelDownload(game.id);
} else {
this.clearCurrentDownload();
}
}
if (WindowManager.mainWindow && game) {
WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress);
const payload = {
progress,
bytesDownloaded: Number(status.completedLength),
fileSize: Number(status.totalLength),
numPeers: Number(status.connections),
numSeeds: Number(status.numSeeders ?? 0),
downloadSpeed: Number(status.downloadSpeed),
timeRemaining: this.getETA(status),
downloadingMetadata: !!downloadingMetadata,
game,
} as DownloadProgress;
WindowManager.mainWindow.webContents.send(
"on-download-progress",
JSON.parse(JSON.stringify(payload))
);
}
}
static async getGame(gameId: number) {
@@ -227,6 +217,8 @@ export class DownloadManager {
}
static async startDownload(gameId: number) {
if (!this.connected) await this.connect();
const game = await this.getGame(gameId)!;
if (game) {

View File

@@ -8,3 +8,4 @@ export * from "./window-manager";
export * from "./download-manager";
export * from "./how-long-to-beat";
export * from "./process-watcher";
export * from "./main-loop";

View File

@@ -0,0 +1,16 @@
import { DownloadManager } from "./download-manager";
import { watchProcesses } from "./process-watcher";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const startMainLoop = async () => {
// eslint-disable-next-line no-constant-condition
while (true) {
await Promise.allSettled([
watchProcesses(),
DownloadManager.watchDownloads(),
]);
await sleep(500);
}
};

View File

@@ -5,73 +5,58 @@ import { gameRepository } from "@main/repository";
import { getProcesses } from "@main/helpers";
import { WindowManager } from "./window-manager";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const gamesPlaytime = new Map<number, number>();
export const startProcessWatcher = async () => {
const sleepTime = 500;
const gamesPlaytime = new Map<number, number>();
export const watchProcesses = async () => {
const games = await gameRepository.find({
where: {
executablePath: Not(IsNull()),
isDeleted: false,
},
});
// eslint-disable-next-line no-constant-condition
while (true) {
const games = await gameRepository.find({
where: {
executablePath: Not(IsNull()),
isDeleted: false,
},
if (games.length === 0) return;
const processes = await getProcesses();
for (const game of games) {
const executablePath = game.executablePath!;
const basename = path.win32.basename(executablePath);
const basenameWithoutExtension = path.win32.basename(
executablePath,
path.extname(executablePath)
);
const gameProcess = processes.find((runningProcess) => {
if (process.platform === "win32") {
return runningProcess.name === basename;
}
return [basename, basenameWithoutExtension].includes(runningProcess.name);
});
if (games.length === 0) {
await sleep(sleepTime);
continue;
}
if (gameProcess) {
if (gamesPlaytime.has(game.id)) {
const zero = gamesPlaytime.get(game.id) ?? 0;
const delta = performance.now() - zero;
const processes = await getProcesses();
for (const game of games) {
const executablePath = game.executablePath!;
const basename = path.win32.basename(executablePath);
const basenameWithoutExtension = path.win32.basename(
executablePath,
path.extname(executablePath)
);
const gameProcess = processes.find((runningProcess) => {
if (process.platform === "win32") {
return runningProcess.name === basename;
}
return [basename, basenameWithoutExtension].includes(
runningProcess.name
);
});
if (gameProcess) {
if (gamesPlaytime.has(game.id)) {
const zero = gamesPlaytime.get(game.id) ?? 0;
const delta = performance.now() - zero;
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-playtime", game.id);
}
await gameRepository.update(game.id, {
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
});
gameRepository.update(game.id, {
lastTimePlayed: new Date().toUTCString(),
});
}
gamesPlaytime.set(game.id, performance.now());
} else if (gamesPlaytime.has(game.id)) {
gamesPlaytime.delete(game.id);
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
WindowManager.mainWindow.webContents.send("on-playtime", game.id);
}
await gameRepository.update(game.id, {
playTimeInMilliseconds: game.playTimeInMilliseconds + delta,
lastTimePlayed: new Date().toUTCString(),
});
}
gamesPlaytime.set(game.id, performance.now());
} else if (gamesPlaytime.has(game.id)) {
gamesPlaytime.delete(game.id);
if (WindowManager.mainWindow) {
WindowManager.mainWindow.webContents.send("on-game-close", game.id);
}
}
await sleep(sleepTime);
}
};