Enforce single instance Local Agent supervisor worker and dashboard
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
import hashlib
|
||||
import os
|
||||
import atexit
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -16,6 +20,63 @@ DATA_DIR = INSTALL_ROOT / "data"
|
||||
SUPERVISOR_LOCK = DATA_DIR / "supervisor.lock"
|
||||
SUPERVISOR_SCRIPT = INSTALL_ROOT / "ERPAgentSupervisor.pyw"
|
||||
URL = "http://127.0.0.1:8788"
|
||||
ERROR_ALREADY_EXISTS = 183
|
||||
|
||||
|
||||
def _dashboard_mutex_name() -> str:
|
||||
normalized = str(INSTALL_ROOT.resolve()).casefold().encode("utf-8", errors="ignore")
|
||||
suffix = hashlib.sha256(normalized).hexdigest()[:20]
|
||||
return f"Local\\ARRR_ERP_Local_Agent_DASHBOARD_{suffix}"
|
||||
|
||||
|
||||
def _acquire_dashboard_mutex():
|
||||
if os.name != "nt":
|
||||
return object()
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
kernel32.CreateMutexW.argtypes = (ctypes.c_void_p, wintypes.BOOL, wintypes.LPCWSTR)
|
||||
kernel32.CreateMutexW.restype = wintypes.HANDLE
|
||||
ctypes.set_last_error(0)
|
||||
handle = kernel32.CreateMutexW(None, False, _dashboard_mutex_name())
|
||||
if not handle:
|
||||
raise ctypes.WinError(ctypes.get_last_error())
|
||||
if ctypes.get_last_error() == ERROR_ALREADY_EXISTS:
|
||||
kernel32.CloseHandle(handle)
|
||||
return None
|
||||
return handle
|
||||
|
||||
|
||||
def _close_dashboard_mutex(handle) -> None:
|
||||
if os.name != "nt" or handle is None or not isinstance(handle, int):
|
||||
return
|
||||
try:
|
||||
ctypes.WinDLL("kernel32", use_last_error=True).CloseHandle(handle)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _focus_existing_dashboard() -> None:
|
||||
if os.name != "nt":
|
||||
return
|
||||
try:
|
||||
user32 = ctypes.WinDLL("user32", use_last_error=True)
|
||||
EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
||||
|
||||
@EnumWindowsProc
|
||||
def callback(hwnd, _lparam):
|
||||
length = user32.GetWindowTextLengthW(hwnd)
|
||||
if length <= 0:
|
||||
return True
|
||||
buf = ctypes.create_unicode_buffer(length + 1)
|
||||
user32.GetWindowTextW(hwnd, buf, length + 1)
|
||||
if "ERP Local Agent Dashboard" in buf.value:
|
||||
user32.ShowWindow(hwnd, 9) # SW_RESTORE
|
||||
user32.SetForegroundWindow(hwnd)
|
||||
return False
|
||||
return True
|
||||
|
||||
user32.EnumWindows(callback, 0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _create_no_window() -> int:
|
||||
@@ -115,6 +176,11 @@ def _wait_ready(seconds: int = 75) -> bool:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
dashboard_mutex = _acquire_dashboard_mutex()
|
||||
if dashboard_mutex is None:
|
||||
_focus_existing_dashboard()
|
||||
return
|
||||
atexit.register(_close_dashboard_mutex, dashboard_mutex)
|
||||
try:
|
||||
_ensure_supervisor()
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user