mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 05:46:17 +00:00
ci: bumping version
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.5.2",
|
||||
"version": "3.5.3",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
|
||||
@@ -102,7 +102,7 @@ def process_list():
|
||||
if auth_error:
|
||||
return auth_error
|
||||
|
||||
process_list = [proc.info for proc in psutil.process_iter(['exe', 'pid', 'name'])]
|
||||
process_list = [proc.info for proc in psutil.process_iter(['exe', 'cwd', 'pid', 'name', 'environ'])]
|
||||
return jsonify(process_list), 200
|
||||
|
||||
@app.route("/profile-image", methods=["POST"])
|
||||
|
||||
@@ -4,7 +4,12 @@ import i18n from "i18next";
|
||||
import path from "node:path";
|
||||
import url from "node:url";
|
||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||
import { logger, clearGamesPlaytime, WindowManager } from "@main/services";
|
||||
import {
|
||||
logger,
|
||||
clearGamesPlaytime,
|
||||
WindowManager,
|
||||
Lock,
|
||||
} from "@main/services";
|
||||
import resources from "@locales";
|
||||
import { PythonRPC } from "./services/python-rpc";
|
||||
import { db, levelKeys } from "./level";
|
||||
@@ -146,6 +151,8 @@ app.on("window-all-closed", () => {
|
||||
let canAppBeClosed = false;
|
||||
|
||||
app.on("before-quit", async (e) => {
|
||||
await Lock.releaseLock();
|
||||
|
||||
if (!canAppBeClosed) {
|
||||
e.preventDefault();
|
||||
/* Disconnects libtorrent */
|
||||
|
||||
@@ -15,9 +15,12 @@ import {
|
||||
uploadGamesBatch,
|
||||
startMainLoop,
|
||||
Ludusavi,
|
||||
Lock,
|
||||
} from "@main/services";
|
||||
|
||||
export const loadState = async () => {
|
||||
await Lock.acquireLock();
|
||||
|
||||
const userPreferences = await db.get<string, UserPreferences | null>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface ProcessPayload {
|
||||
exe: string | null;
|
||||
pid: number;
|
||||
name: string;
|
||||
environ: Record<string, string> | null;
|
||||
cwd: string | null;
|
||||
}
|
||||
|
||||
export interface PauseSeedingPayload {
|
||||
|
||||
@@ -16,3 +16,4 @@ export * from "./ws";
|
||||
export * from "./system-path";
|
||||
export * from "./library-sync";
|
||||
export * from "./wine";
|
||||
export * from "./lock";
|
||||
|
||||
39
src/main/services/lock.ts
Normal file
39
src/main/services/lock.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { SystemPath } from "./system-path";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export class Lock {
|
||||
private static lockFilePath = path.join(
|
||||
SystemPath.getPath("temp"),
|
||||
"hydra-launcher.lock"
|
||||
);
|
||||
|
||||
public static async acquireLock() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(this.lockFilePath, "", (err) => {
|
||||
if (err) {
|
||||
logger.error("Error acquiring the lock", err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
logger.info("Acquired the lock");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static async releaseLock() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(this.lockFilePath, (err) => {
|
||||
if (err) {
|
||||
logger.error("Error releasing the lock", err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
logger.info("Released the lock");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,11 @@ import { createGame, updateGamePlaytime } from "./library-sync";
|
||||
import type { Game, GameRunning } from "@types";
|
||||
import { PythonRPC } from "./python-rpc";
|
||||
import axios from "axios";
|
||||
import { exec } from "child_process";
|
||||
import { ProcessPayload } from "./download/types";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { CloudSync } from "./cloud-sync";
|
||||
|
||||
const commands = {
|
||||
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||
findWineExecutables: `lsof -c wine 2>/dev/null | grep '\\.exe$' | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||
};
|
||||
import { logger } from "./logger";
|
||||
import path from "path";
|
||||
|
||||
export const gamesPlaytime = new Map<
|
||||
string,
|
||||
@@ -31,8 +27,7 @@ interface GameExecutables {
|
||||
const TICKS_TO_UPDATE_API = 80;
|
||||
let currentTick = 1;
|
||||
|
||||
const isWindowsPlatform = process.platform === "win32";
|
||||
const isLinuxPlatform = process.platform === "linux";
|
||||
const platform = process.platform;
|
||||
|
||||
const getGameExecutables = async () => {
|
||||
const gameExecutables = (
|
||||
@@ -49,16 +44,18 @@ const getGameExecutables = async () => {
|
||||
Object.keys(gameExecutables).forEach((key) => {
|
||||
gameExecutables[key] = gameExecutables[key]
|
||||
.filter((executable) => {
|
||||
if (isWindowsPlatform) {
|
||||
if (platform === "win32") {
|
||||
return executable.os === "win32";
|
||||
} else if (isLinuxPlatform) {
|
||||
} else if (platform === "linux") {
|
||||
return executable.os === "linux" || executable.os === "win32";
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map((executable) => {
|
||||
return {
|
||||
name: isWindowsPlatform
|
||||
name:
|
||||
platform === "win32"
|
||||
? executable.name.replace(/\//g, "\\")
|
||||
: executable.name,
|
||||
os: executable.os,
|
||||
@@ -72,8 +69,9 @@ const getGameExecutables = async () => {
|
||||
|
||||
const gameExecutables = await getGameExecutables();
|
||||
|
||||
const findGamePathByProcess = (
|
||||
const findGamePathByProcess = async (
|
||||
processMap: Map<string, Set<string>>,
|
||||
winePrefixMap: Map<string, string>,
|
||||
gameId: string
|
||||
) => {
|
||||
const executables = gameExecutables[gameId];
|
||||
@@ -82,32 +80,26 @@ const findGamePathByProcess = (
|
||||
const pathSet = processMap.get(executable.exe);
|
||||
|
||||
if (pathSet) {
|
||||
pathSet.forEach(async (path) => {
|
||||
for (const path of pathSet) {
|
||||
if (path.toLowerCase().endsWith(executable.name)) {
|
||||
const gameKey = levelKeys.game("steam", gameId);
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
gamesSublevel.put(gameKey, {
|
||||
const updatedGame: Game = {
|
||||
...game,
|
||||
executablePath: path,
|
||||
});
|
||||
};
|
||||
|
||||
if (process.platform === "linux" && winePrefixMap.has(path)) {
|
||||
updatedGame.winePrefixPath = winePrefixMap.get(path)!;
|
||||
}
|
||||
|
||||
if (isLinuxPlatform) {
|
||||
exec(commands.findWineDir, (err, out) => {
|
||||
if (err) return;
|
||||
|
||||
if (game) {
|
||||
gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
winePrefixPath: out.trim().replace("/drive_c/windows", ""),
|
||||
});
|
||||
}
|
||||
});
|
||||
await gamesSublevel.put(gameKey, updatedGame);
|
||||
logger.info("Set game path", gameKey, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -117,50 +109,29 @@ const getSystemProcessMap = async () => {
|
||||
(await PythonRPC.rpc.get<ProcessPayload[] | null>("/process-list")).data ||
|
||||
[];
|
||||
|
||||
const map = new Map<string, Set<string>>();
|
||||
const processMap = new Map<string, Set<string>>();
|
||||
const winePrefixMap = new Map<string, string>();
|
||||
|
||||
processes.forEach((process) => {
|
||||
const key = process.name?.toLowerCase();
|
||||
const value = process.exe;
|
||||
const value =
|
||||
platform === "win32"
|
||||
? process.exe
|
||||
: path.join(process.cwd ?? "", process.name ?? "");
|
||||
|
||||
if (!key || !value) return;
|
||||
|
||||
const currentSet = map.get(key) ?? new Set();
|
||||
map.set(key, currentSet.add(value));
|
||||
});
|
||||
const STEAM_COMPAT_DATA_PATH = process.environ?.STEAM_COMPAT_DATA_PATH;
|
||||
|
||||
if (isLinuxPlatform) {
|
||||
await new Promise((res) => {
|
||||
exec(commands.findWineExecutables, (err, out) => {
|
||||
if (err) {
|
||||
res(null);
|
||||
return;
|
||||
if (STEAM_COMPAT_DATA_PATH) {
|
||||
winePrefixMap.set(value, STEAM_COMPAT_DATA_PATH);
|
||||
}
|
||||
|
||||
const pathSet = new Set(
|
||||
out
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((path) => path.trim())
|
||||
);
|
||||
|
||||
pathSet.forEach((path) => {
|
||||
if (path.startsWith("/usr")) return;
|
||||
|
||||
const key = path.slice(path.lastIndexOf("/") + 1).toLowerCase();
|
||||
|
||||
if (!key || !path) return;
|
||||
|
||||
const currentSet = map.get(key) ?? new Set();
|
||||
map.set(key, currentSet.add(path));
|
||||
const currentSet = processMap.get(key) ?? new Set();
|
||||
processMap.set(key, currentSet.add(value));
|
||||
});
|
||||
|
||||
res(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
return { processMap, winePrefixMap };
|
||||
};
|
||||
|
||||
export const watchProcesses = async () => {
|
||||
@@ -173,19 +144,20 @@ export const watchProcesses = async () => {
|
||||
|
||||
if (!games.length) return;
|
||||
|
||||
const processMap = await getSystemProcessMap();
|
||||
const { processMap, winePrefixMap } = await getSystemProcessMap();
|
||||
|
||||
for (const game of games) {
|
||||
const executablePath = game.executablePath;
|
||||
if (!executablePath) {
|
||||
if (gameExecutables[game.objectId]) {
|
||||
findGamePathByProcess(processMap, game.objectId);
|
||||
await findGamePathByProcess(processMap, winePrefixMap, game.objectId);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const executable = executablePath
|
||||
.slice(executablePath.lastIndexOf(isWindowsPlatform ? "\\" : "/") + 1)
|
||||
.slice(executablePath.lastIndexOf(platform === "win32" ? "\\" : "/") + 1)
|
||||
.toLowerCase();
|
||||
|
||||
const hasProcess = processMap.get(executable)?.has(executablePath);
|
||||
|
||||
Reference in New Issue
Block a user