juc500 정상동작

This commit is contained in:
Macbook
2026-07-21 21:04:25 +09:00
parent a423db0556
commit bb9874b931
2917 changed files with 708025 additions and 668 deletions
+108
View File
@@ -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)]