"""Platform-specific paths and tool resolution for meshbay-node.""" import asyncio import os import shutil import sys from pathlib import Path # ── Event loop ─────────────────────────────────────────────────────────────── def use_compatible_event_loop() -> None: """ aiortc's ICE stack (aioice) does not run on Windows' default ProactorEventLoop — a DataChannel handshake never completes and the connection hangs. Select the SelectorEventLoop before the loop is created. Cost: SelectorEventLoop cannot spawn subprocesses on Windows, so ffmpeg streaming (asyncio.create_subprocess_exec in webrtc_server.py) does not work under it. The video path needs a thread-based runner on Windows — tracked for the port. No effect off Windows. """ if sys.platform == "win32": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # ── Directories ────────────────────────────────────────────────────────────── def config_dir() -> Path: if sys.platform == "win32": return Path(os.environ.get("LOCALAPPDATA") or Path.home()) / "meshbay" return Path.home() / ".config" / "meshbay" def data_dir() -> Path: if sys.platform == "win32": return Path(os.environ.get("LOCALAPPDATA") or Path.home()) / "meshbay" / "data" return Path.home() / ".local" / "share" / "meshbay" def state_dir() -> Path: if sys.platform == "win32": return Path(os.environ.get("LOCALAPPDATA") or Path.home()) / "meshbay" / "state" return Path.home() / ".local" / "state" / "meshbay" # ── File permissions ───────────────────────────────────────────────────────── def chmod_private(path: Path, *, mode: int = 0o600) -> None: """Set restrictive permissions on a file. No-op on Windows (NTFS ignores mode bits).""" if sys.platform != "win32": path.chmod(mode) # ── Media tools ────────────────────────────────────────────────────────────── _ffmpeg_path: str = "ffmpeg" _ffprobe_path: str = "ffprobe" def check_media_tools( ffmpeg: str = "ffmpeg", ffprobe: str = "ffprobe", ) -> None: """Resolve ffmpeg/ffprobe at daemon startup. Raises RuntimeError if not found.""" global _ffmpeg_path, _ffprobe_path resolved = shutil.which(ffmpeg) if not resolved: raise RuntimeError( f"{ffmpeg!r} not found in PATH. " "Install ffmpeg or set [node] ffmpeg_path in node.toml." ) _ffmpeg_path = resolved resolved = shutil.which(ffprobe) if not resolved: raise RuntimeError( f"{ffprobe!r} not found in PATH. " "Install ffmpeg or set [node] ffprobe_path in node.toml." ) _ffprobe_path = resolved def ffmpeg_cmd() -> str: return _ffmpeg_path def ffprobe_cmd() -> str: return _ffprobe_path