Files
juc500/dist-offline/juc500-offline-update-20260720-123250/juc500_xfer/peer_sync.py
T
2026-07-21 21:04:25 +09:00

190 lines
6.0 KiB
Python

"""Dev-only: push local project sources to the USB peer (manual sync_peer.sh)."""
from __future__ import annotations
import os
from pathlib import Path
from typing import Any, Callable, Optional
from .protocol import Session
_ROOT = Path(__file__).resolve().parent.parent
# Keep in sync with scripts/sync_peer.sh default set.
SYNC_XFER = (
"juc500_xfer/gui_server.py",
"juc500_xfer/usb_link.py",
"juc500_xfer/inputshare.py",
"juc500_xfer/protocol.py",
"juc500_xfer/peer_sync.py",
)
SYNC_STATIC = (
"juc500_xfer/static/app.js",
"juc500_xfer/static/index.html",
"juc500_xfer/static/styles.css",
)
def _use_mac_peer_paths() -> bool:
return os.environ.get("USE_MAC_PEER_PATHS", "").strip() in ("1", "true", "yes")
def peer_dest_dirs() -> dict[str, str]:
"""Destination directories on the peer filesystem."""
if _use_mac_peer_paths():
base = "dev/juc500/juc500"
return {
"xfer": f"{base}/juc500_xfer",
"static": f"{base}/juc500_xfer/static",
}
root = os.environ.get("JUC500_PEER_ROOT", "C:/dev/juc500").replace("\\", "/").rstrip("/")
alt = os.environ.get("JUC500_PEER_ROOT_ALT", "C:/dev/juc500/juc500").replace("\\", "/").rstrip("/")
return {
"xfer": f"{root}/juc500_xfer",
"static": f"{root}/juc500_xfer/static",
"xfer_alt": f"{alt}/juc500_xfer",
"static_alt": f"{alt}/juc500_xfer/static",
}
def _local_paths(rel_paths: tuple[str, ...]) -> list[Path]:
out: list[Path] = []
for rel in rel_paths:
p = (_ROOT / rel).resolve()
if p.is_file():
out.append(p)
return out
def _send_batch(
session: Session,
dest_dir: str,
files: list[Path],
*,
on_log: Callable[[str], None],
on_progress: Optional[Callable[[int, int, str], None]] = None,
) -> int:
if not files:
return 0
dest = dest_dir.replace("\\", "/").rstrip("/")
count = 0
for local in files:
remote_name = f"{dest}/{local.name}" if dest else local.name
size = local.stat().st_size
if on_progress:
on_progress(0, size, local.name)
session.on_progress = (
(lambda done, total, name=local.name: on_progress(done, total, name))
if on_progress
else None
)
timeout = 180.0 if size > 32 * 1024 * 1024 else 120.0
session.send_file(local, remote_name=remote_name, accept_timeout=timeout)
count += 1
on_log(f"상대 동기화: {local.name} → {dest}")
return count
def peer_ping(session: Session, *, timeout: float = 10.0) -> dict[str, Any]:
try:
result = session.rpc("ping", timeout=timeout)
return result if isinstance(result, dict) else {}
except Exception:
return {}
def local_build_ids() -> tuple[str, str]:
try:
from .inputshare import INPUTSHARE_BUILD
inputshare = INPUTSHARE_BUILD
except Exception:
inputshare = "unknown"
try:
from .usb_link import XFER_BUILD
xfer = XFER_BUILD
except Exception:
xfer = "unknown"
return inputshare, xfer
def peer_needs_restart(session: Session) -> bool:
ping = peer_ping(session)
local_inputshare, local_xfer = local_build_ids()
peer_inputshare = str(ping.get("inputshare_build") or "unknown")
peer_xfer = str(ping.get("xfer_build") or "unknown")
if peer_xfer == "unknown":
return True
return peer_inputshare != local_inputshare or peer_xfer != local_xfer
def request_peer_restart(session: Session, *, on_log: Callable[[str], None]) -> bool:
try:
session.rpc("restart_gui", timeout=12.0)
on_log("상대편 GUI 재시작 예약됨 — 프로세스가 새 코드를 로드합니다")
return True
except Exception as exc:
on_log(f"상대 재시작 RPC 실패: {exc} — 상대 PC에서 ./start.sh 수동 실행 필요")
return False
def push_sources_only(
session: Session,
*,
on_log: Callable[[str], None],
on_progress: Optional[Callable[[int, int, str], None]] = None,
) -> int:
xfer_files = _local_paths(SYNC_XFER)
static_files = _local_paths(SYNC_STATIC)
dests = peer_dest_dirs()
total = 0
for key in ("xfer", "static"):
files = xfer_files if key == "xfer" else static_files
dest = dests.get(key)
if dest:
total += _send_batch(session, dest, files, on_log=on_log, on_progress=on_progress)
for key, alt_key in (("xfer", "xfer_alt"), ("static", "static_alt")):
alt = dests.get(alt_key)
if not alt or alt == dests.get(key):
continue
files = xfer_files if key == "xfer" else static_files
total += _send_batch(session, alt, files, on_log=on_log, on_progress=on_progress)
return total
def sync_sources_to_peer(
session: Session,
*,
on_log: Callable[[str], None],
on_progress: Optional[Callable[[int, int, str], None]] = None,
restart_if_stale: bool = False,
) -> dict[str, Any]:
"""Send default source bundle to peer. Optionally restart peer to load new code."""
on_log("상대편 소스 자동 동기화 시작…")
total = push_sources_only(session, on_log=on_log, on_progress=on_progress)
if total <= 0:
return {"ok": False, "error": "동기화할 로컬 파일 없음", "count": 0}
local_inputshare, local_xfer = local_build_ids()
ping = peer_ping(session)
peer_inputshare = str(ping.get("inputshare_build") or "unknown")
peer_xfer = str(ping.get("xfer_build") or "unknown")
restart = False
if restart_if_stale and (
peer_xfer != local_xfer or peer_inputshare != local_inputshare
):
on_log(
f"상대 build 불일치 (xfer 로컬 {local_xfer} · 상대 {peer_xfer}) — 재시작 요청"
)
restart = request_peer_restart(session, on_log=on_log)
on_log(f"상대편 소스 동기화 완료 ({total}개 파일)")
return {
"ok": True,
"count": total,
"local_xfer_build": local_xfer,
"peer_xfer_build": peer_xfer,
"restart_requested": restart,
}