From c60584c613c2e0e18e15260c24155f34e01f6c61 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Sat, 31 May 2025 19:01:32 +0100 Subject: [PATCH] ci: bumping version --- package.json | 2 +- python_rpc/main.py | 2 +- src/main/index.ts | 9 ++- src/main/main.ts | 3 + src/main/services/download/types.ts | 2 + src/main/services/index.ts | 1 + src/main/services/lock.ts | 39 ++++++++++ src/main/services/process-watcher.ts | 108 ++++++++++----------------- 8 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 src/main/services/lock.ts diff --git a/package.json b/package.json index 2850454d..248fe7fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.5.2", + "version": "3.5.3", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", diff --git a/python_rpc/main.py b/python_rpc/main.py index 8a64e7c3..43972afa 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -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"]) diff --git a/src/main/index.ts b/src/main/index.ts index de88a548..af197a6b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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 */ diff --git a/src/main/main.ts b/src/main/main.ts index 54191b9f..b52f0c66 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -15,9 +15,12 @@ import { uploadGamesBatch, startMainLoop, Ludusavi, + Lock, } from "@main/services"; export const loadState = async () => { + await Lock.acquireLock(); + const userPreferences = await db.get( levelKeys.userPreferences, { diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index 0e868318..7cecb103 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -31,6 +31,8 @@ export interface ProcessPayload { exe: string | null; pid: number; name: string; + environ: Record | null; + cwd: string | null; } export interface PauseSeedingPayload { diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 11d97810..727805c7 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -16,3 +16,4 @@ export * from "./ws"; export * from "./system-path"; export * from "./library-sync"; export * from "./wine"; +export * from "./lock"; diff --git a/src/main/services/lock.ts b/src/main/services/lock.ts new file mode 100644 index 00000000..44b0d55e --- /dev/null +++ b/src/main/services/lock.ts @@ -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((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((resolve, reject) => { + fs.unlink(this.lockFilePath, (err) => { + if (err) { + logger.error("Error releasing the lock", err); + reject(err); + } + + logger.info("Released the lock"); + resolve(); + }); + }); + } +} diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index deb93f4e..7a2433bc 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -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,18 +44,20 @@ 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 - ? executable.name.replace(/\//g, "\\") - : executable.name, + name: + platform === "win32" + ? executable.name.replace(/\//g, "\\") + : executable.name, os: executable.os, exe: executable.name.slice(executable.name.lastIndexOf("/") + 1), }; @@ -72,8 +69,9 @@ const getGameExecutables = async () => { const gameExecutables = await getGameExecutables(); -const findGamePathByProcess = ( +const findGamePathByProcess = async ( processMap: Map>, + winePrefixMap: Map, 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 (isLinuxPlatform) { - exec(commands.findWineDir, (err, out) => { - if (err) return; + if (process.platform === "linux" && winePrefixMap.has(path)) { + updatedGame.winePrefixPath = winePrefixMap.get(path)!; + } - 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("/process-list")).data || []; - const map = new Map>(); + const processMap = new Map>(); + const winePrefixMap = new Map(); 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 (STEAM_COMPAT_DATA_PATH) { + winePrefixMap.set(value, STEAM_COMPAT_DATA_PATH); + } + + const currentSet = processMap.get(key) ?? new Set(); + processMap.set(key, currentSet.add(value)); }); - if (isLinuxPlatform) { - await new Promise((res) => { - exec(commands.findWineExecutables, (err, out) => { - if (err) { - res(null); - return; - } - - 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)); - }); - - 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);