juc500 정상동작
This commit is contained in:
@@ -55,6 +55,15 @@ NAME_HINTS = (
|
||||
# Do not kill ourselves
|
||||
SELF_HINTS = ("juc500", "juc500_xfer", "juc500-portable")
|
||||
|
||||
# Other copies of this app also hold Interface 5 exclusively
|
||||
# Keep markers specific — do not match scripts that merely import juc500_xfer.
|
||||
SELF_PROCESS_MARKERS = (
|
||||
"juc500_app.py",
|
||||
"juc500-portable.exe",
|
||||
"juc500_portable.exe",
|
||||
"juc500-portable",
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcInfo:
|
||||
@@ -225,6 +234,105 @@ def _summary(before: list[ProcInfo], killed: list[dict], after: list[ProcInfo])
|
||||
return f"일부 종료되지 않음: {rem} (관리자 권한으로 다시 시도하거나 작업 관리자에서 종료)"
|
||||
|
||||
|
||||
def list_other_juc500_processes() -> list[ProcInfo]:
|
||||
"""Other juc500 GUI/CLI instances (excludes this PID). They hold IF5."""
|
||||
my_pid = os.getpid()
|
||||
out: list[ProcInfo] = []
|
||||
if _IS_WIN:
|
||||
try:
|
||||
ps = (
|
||||
"Get-CimInstance Win32_Process | "
|
||||
"Where-Object { $_.CommandLine -match 'juc500_app\\.py|juc500-portable|juc500_portable|-m juc500_xfer' } | "
|
||||
"Select-Object ProcessId, Name, CommandLine | ConvertTo-Json -Compress"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
["powershell", "-NoProfile", "-Command", ps],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15,
|
||||
check=False,
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||
)
|
||||
raw = (proc.stdout or "").strip()
|
||||
if not raw:
|
||||
return []
|
||||
import json
|
||||
|
||||
data = json.loads(raw)
|
||||
if isinstance(data, dict):
|
||||
data = [data]
|
||||
for d in data:
|
||||
pid = int(d.get("ProcessId") or 0)
|
||||
if not pid or pid == my_pid:
|
||||
continue
|
||||
cmd = str(d.get("CommandLine") or "")
|
||||
name = str(d.get("Name") or "python.exe")
|
||||
if any(m in cmd.lower() for m in SELF_PROCESS_MARKERS):
|
||||
out.append(ProcInfo(pid=pid, name=name, path=cmd[:240]))
|
||||
except Exception:
|
||||
return []
|
||||
return _dedupe(out)
|
||||
|
||||
# POSIX: scan ps
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
["ps", "-ax", "-o", "pid=,command="],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=15,
|
||||
check=False,
|
||||
)
|
||||
except Exception:
|
||||
return []
|
||||
for line in (proc.stdout or "").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
pid_s, cmd = line.split(None, 1)
|
||||
pid = int(pid_s)
|
||||
except ValueError:
|
||||
continue
|
||||
if pid == my_pid:
|
||||
continue
|
||||
low = cmd.lower()
|
||||
if any(m in low for m in SELF_PROCESS_MARKERS):
|
||||
out.append(ProcInfo(pid=pid, name=os.path.basename(cmd.split()[0]), path=cmd[:240]))
|
||||
return _dedupe(out)
|
||||
|
||||
|
||||
def kill_other_juc500_processes(*, force: bool = True) -> dict[str, Any]:
|
||||
"""Terminate other juc500 instances so Interface 5 can be claimed."""
|
||||
before = list_other_juc500_processes()
|
||||
killed: list[dict[str, Any]] = []
|
||||
errors: list[str] = []
|
||||
for info in before:
|
||||
try:
|
||||
_kill_one(info.pid, force=force)
|
||||
killed.append(info.to_dict())
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"{info.name}({info.pid}): {exc}")
|
||||
if killed:
|
||||
time.sleep(0.6)
|
||||
after = list_other_juc500_processes()
|
||||
return {
|
||||
"ok": len(after) == 0,
|
||||
"found": [p.to_dict() for p in before],
|
||||
"killed": killed,
|
||||
"remaining": [p.to_dict() for p in after],
|
||||
"errors": errors,
|
||||
"message": (
|
||||
"다른 JUC500 인스턴스가 없습니다."
|
||||
if not before
|
||||
else (
|
||||
f"다른 JUC500 종료: {', '.join(str(k.get('pid')) for k in killed)}"
|
||||
if not after
|
||||
else f"일부 JUC500이 남음: {[p.pid for p in after]}"
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _kill_one(pid: int, *, force: bool) -> None:
|
||||
if _IS_WIN:
|
||||
args = ["taskkill", "/PID", str(pid)]
|
||||
|
||||
Reference in New Issue
Block a user