import json import os import subprocess import sys from .config import PROJECT_DIR class PortalCaptureError(RuntimeError): def __init__(self, message, used_restore_token=False): super().__init__(message) self.used_restore_token = used_restore_token def portal_helper_path(): return os.path.join(PROJECT_DIR, "portal_capture_frame") def require_portal_helper(): helper = portal_helper_path() if not os.path.exists(helper): sys.exit(f"missing {helper}; run `make` in {PROJECT_DIR}") if not os.access(helper, os.X_OK): sys.exit(f"portal helper is not executable: {helper}") return helper def read_portal_restore_token(path): try: with open(path, "r", encoding="utf-8") as handle: return handle.read().strip() except FileNotFoundError: return "" except OSError as exc: sys.exit(f"failed to read portal restore token {path}: {exc}") def write_portal_restore_token(path, token): directory = os.path.dirname(path) if directory: os.makedirs(directory, exist_ok=True) temp_path = f"{path}.tmp" try: with open(temp_path, "w", encoding="utf-8") as handle: handle.write(token) handle.write("\n") os.replace(temp_path, path) except OSError as exc: sys.exit(f"failed to write portal restore token {path}: {exc}") def capture_portal_window(output, restore_token_path, reselect, timeout): helper = require_portal_helper() restore_token = "" if restore_token_path and not reselect: restore_token = read_portal_restore_token(restore_token_path) command = [helper, "--window", output, "--timeout", str(timeout)] if restore_token_path: command.append("--persist") if restore_token: command.extend(["--restore-token", restore_token]) try: result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) except subprocess.CalledProcessError as exc: message = exc.stderr.strip() if exc.stderr else str(exc) raise PortalCaptureError(message, used_restore_token=bool(restore_token)) from exc capture_info = {} if result.stdout.strip(): try: capture_info = json.loads(result.stdout) except json.JSONDecodeError as exc: raise PortalCaptureError(f"failed to parse portal capture metadata: {exc}: {result.stdout!r}") from exc new_restore_token = str(capture_info.get("restore_token", "") or "") if restore_token_path and new_restore_token: write_portal_restore_token(restore_token_path, new_restore_token) return capture_info