mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge pull request #837 from hydralauncher/feat/logs-python-process-errors
feat: logs python process
This commit is contained in:
commit
7e7c4dae1d
@ -19,6 +19,7 @@ import {
|
|||||||
LibtorrentPayload,
|
LibtorrentPayload,
|
||||||
ProcessPayload,
|
ProcessPayload,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { pythonInstanceLogger as logger } from "../logger";
|
||||||
|
|
||||||
export class PythonInstance {
|
export class PythonInstance {
|
||||||
private static pythonProcess: cp.ChildProcess | null = null;
|
private static pythonProcess: cp.ChildProcess | null = null;
|
||||||
@ -32,11 +33,13 @@ export class PythonInstance {
|
|||||||
});
|
});
|
||||||
|
|
||||||
public static spawn(args?: StartDownloadPayload) {
|
public static spawn(args?: StartDownloadPayload) {
|
||||||
|
logger.log("spawning python process with args:", args);
|
||||||
this.pythonProcess = startRPCClient(args);
|
this.pythonProcess = startRPCClient(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static kill() {
|
public static kill() {
|
||||||
if (this.pythonProcess) {
|
if (this.pythonProcess) {
|
||||||
|
logger.log("killing python process");
|
||||||
this.pythonProcess.kill();
|
this.pythonProcess.kill();
|
||||||
this.pythonProcess = null;
|
this.pythonProcess = null;
|
||||||
this.downloadingGameId = -1;
|
this.downloadingGameId = -1;
|
||||||
@ -45,6 +48,7 @@ export class PythonInstance {
|
|||||||
|
|
||||||
public static killTorrent() {
|
public static killTorrent() {
|
||||||
if (this.pythonProcess) {
|
if (this.pythonProcess) {
|
||||||
|
logger.log("killing torrent in python process");
|
||||||
this.rpc.post("/action", { action: "kill-torrent" });
|
this.rpc.post("/action", { action: "kill-torrent" });
|
||||||
this.downloadingGameId = -1;
|
this.downloadingGameId = -1;
|
||||||
}
|
}
|
||||||
@ -138,12 +142,14 @@ export class PythonInstance {
|
|||||||
save_path: game.downloadPath!,
|
save_path: game.downloadPath!,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.rpc.post("/action", {
|
await this.rpc
|
||||||
action: "start",
|
.post("/action", {
|
||||||
game_id: game.id,
|
action: "start",
|
||||||
magnet: game.uri,
|
game_id: game.id,
|
||||||
save_path: game.downloadPath,
|
magnet: game.uri,
|
||||||
} as StartDownloadPayload);
|
save_path: game.downloadPath,
|
||||||
|
} as StartDownloadPayload)
|
||||||
|
.catch(this.handleRpcError);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloadingGameId = game.id;
|
this.downloadingGameId = game.id;
|
||||||
@ -159,4 +165,14 @@ export class PythonInstance {
|
|||||||
|
|
||||||
this.downloadingGameId = -1;
|
this.downloadingGameId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async handleRpcError(_error: unknown) {
|
||||||
|
await this.rpc.get("/healthcheck").catch(() => {
|
||||||
|
logger.error(
|
||||||
|
"RPC healthcheck failed. Killing process and starting again"
|
||||||
|
);
|
||||||
|
this.kill();
|
||||||
|
this.spawn();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import crypto from "node:crypto";
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
import type { StartDownloadPayload } from "./types";
|
import type { StartDownloadPayload } from "./types";
|
||||||
|
import { Readable } from "node:stream";
|
||||||
|
import { pythonInstanceLogger as logger } from "../logger";
|
||||||
|
|
||||||
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
|
const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
|
||||||
darwin: "hydra-download-manager",
|
darwin: "hydra-download-manager",
|
||||||
@ -15,6 +17,13 @@ export const BITTORRENT_PORT = "5881";
|
|||||||
export const RPC_PORT = "8084";
|
export const RPC_PORT = "8084";
|
||||||
export const RPC_PASSWORD = crypto.randomBytes(32).toString("hex");
|
export const RPC_PASSWORD = crypto.randomBytes(32).toString("hex");
|
||||||
|
|
||||||
|
const logStderr = (readable: Readable | null) => {
|
||||||
|
if (!readable) return;
|
||||||
|
|
||||||
|
readable.setEncoding("utf-8");
|
||||||
|
readable.on("data", logger.log);
|
||||||
|
};
|
||||||
|
|
||||||
export const startTorrentClient = (args?: StartDownloadPayload) => {
|
export const startTorrentClient = (args?: StartDownloadPayload) => {
|
||||||
const commonArgs = [
|
const commonArgs = [
|
||||||
BITTORRENT_PORT,
|
BITTORRENT_PORT,
|
||||||
@ -40,10 +49,14 @@ export const startTorrentClient = (args?: StartDownloadPayload) => {
|
|||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cp.spawn(binaryPath, commonArgs, {
|
const childProcess = cp.spawn(binaryPath, commonArgs, {
|
||||||
stdio: "inherit",
|
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
|
stdio: ["inherit", "inherit"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logStderr(childProcess.stderr);
|
||||||
|
|
||||||
|
return childProcess;
|
||||||
} else {
|
} else {
|
||||||
const scriptPath = path.join(
|
const scriptPath = path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -53,8 +66,12 @@ export const startTorrentClient = (args?: StartDownloadPayload) => {
|
|||||||
"main.py"
|
"main.py"
|
||||||
);
|
);
|
||||||
|
|
||||||
return cp.spawn("python3", [scriptPath, ...commonArgs], {
|
const childProcess = cp.spawn("python3", [scriptPath, ...commonArgs], {
|
||||||
stdio: "inherit",
|
stdio: ["inherit", "inherit"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logStderr(childProcess.stderr);
|
||||||
|
|
||||||
|
return childProcess;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,10 @@ log.transports.file.resolvePathFn = (
|
|||||||
_: log.PathVariables,
|
_: log.PathVariables,
|
||||||
message?: log.LogMessage | undefined
|
message?: log.LogMessage | undefined
|
||||||
) => {
|
) => {
|
||||||
|
if (message?.scope === "python-instance") {
|
||||||
|
return path.join(logsPath, "pythoninstance.txt");
|
||||||
|
}
|
||||||
|
|
||||||
if (message?.level === "error") {
|
if (message?.level === "error") {
|
||||||
return path.join(logsPath, "error.txt");
|
return path.join(logsPath, "error.txt");
|
||||||
}
|
}
|
||||||
@ -23,4 +27,5 @@ log.errorHandler.startCatching({
|
|||||||
|
|
||||||
log.initialize();
|
log.initialize();
|
||||||
|
|
||||||
|
export const pythonInstanceLogger = log.scope("python-instance");
|
||||||
export const logger = log.scope("main");
|
export const logger = log.scope("main");
|
||||||
|
@ -158,7 +158,7 @@ export class WindowManager {
|
|||||||
|
|
||||||
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
|
const recentlyPlayedGames: Array<MenuItemConstructorOptions | MenuItem> =
|
||||||
games.map(({ title, executablePath }) => ({
|
games.map(({ title, executablePath }) => ({
|
||||||
label: title,
|
label: title.length > 15 ? `${title.slice(0, 15)}…` : title,
|
||||||
type: "normal",
|
type: "normal",
|
||||||
click: async () => {
|
click: async () => {
|
||||||
if (!executablePath) return;
|
if (!executablePath) return;
|
||||||
|
@ -20,6 +20,23 @@ if start_download_payload:
|
|||||||
class Handler(BaseHTTPRequestHandler):
|
class Handler(BaseHTTPRequestHandler):
|
||||||
rpc_password_header = 'x-hydra-rpc-password'
|
rpc_password_header = 'x-hydra-rpc-password'
|
||||||
|
|
||||||
|
skip_log_routes = [
|
||||||
|
"process-list",
|
||||||
|
"status"
|
||||||
|
]
|
||||||
|
|
||||||
|
def log_error(self, format, *args):
|
||||||
|
sys.stderr.write("%s - - [%s] %s\n" %
|
||||||
|
(self.address_string(),
|
||||||
|
self.log_date_time_string(),
|
||||||
|
format%args))
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
for route in self.skip_log_routes:
|
||||||
|
if route in args[0]: return
|
||||||
|
|
||||||
|
super().log_message(format, *args)
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
if self.path == "/status":
|
if self.path == "/status":
|
||||||
if self.headers.get(self.rpc_password_header) != rpc_password:
|
if self.headers.get(self.rpc_password_header) != rpc_password:
|
||||||
|
Loading…
Reference in New Issue
Block a user