#!/usr/bin/env python3 """Losslessly package the Room Server page for browser gzip decoding. RoomWebHtml.h remains the reviewed page source and native UI fixture. Only Room Server's ESP32 WebConfig callback references the compressed asset. Other roles and nRF52 builds do not include either page in their firmware. """ from __future__ import annotations import argparse import gzip import hashlib from pathlib import Path import re import zlib SOURCE = "src/helpers/esp32/RoomWebHtml.h" OUTPUT = "src/helpers/esp32/RoomWebHtmlGzip.h" MARKER = "// room-page-source-sha256: " def source_page(header: str) -> bytes: matches = re.findall( r'static const char ROOM_WEB_HTML\[\] PROGMEM = R"roomhtml\(([\s\S]*?)\)roomhtml";', header, ) if len(matches) != 1: raise ValueError("expected one RoomWebHtml.h page source") return matches[0].encode("ascii") def page_bytes(header: str) -> bytes: matches = re.findall( r"static const uint8_t ROOM_WEB_HTML_GZ\[\] PROGMEM = \{([\s\S]*?)\};", header ) if len(matches) != 1: raise ValueError("expected one compressed Room page") return bytes(int(value, 16) for value in re.findall(r"0x([0-9a-f]{2})", matches[0])) def render(raw: bytes, encoded: bytes) -> str: if gzip.decompress(encoded) != raw: raise ValueError("compressed Room page differs from source") digest = hashlib.sha256(raw).hexdigest() lines = [ "// Auto-generated by scripts/generate_room_web_html.py; DO NOT EDIT.", "// Lossless gzip of RoomWebHtml.h, decoded by the browser.", MARKER + digest, "#pragma once", "#include ", "#include ", "", "static const uint8_t ROOM_WEB_HTML_GZ[] PROGMEM = {", ] for index in range(0, len(encoded), 16): lines.append(" " + "".join("0x%02x," % value for value in encoded[index:index + 16])) lines.extend(["};", ""]) return "\n".join(lines) def generate(root: Path, *, check: bool = False) -> bool: raw = source_page((root / SOURCE).read_text(encoding="ascii")) destination = root / OUTPUT existing = destination.read_text(encoding="ascii") if destination.exists() else "" # Validate the actual bytes rather than trusting a marker or file mtime. try: encoded = page_bytes(existing) intact = gzip.decompress(encoded) == raw and render(raw, encoded) == existing except (ValueError, OSError, EOFError, zlib.error): intact = False if intact: return False if check: raise ValueError("compressed Room page is missing, stale or invalid; regenerate it") from zopfli_compress import gzip_compress encoded = gzip_compress(raw) destination.write_text(render(raw, encoded), encoding="ascii") print("Room Web HTML: %d source bytes -> %d gzip bytes" % (len(raw), len(encoded))) return True def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true") arguments = parser.parse_args() root = Path(__file__).resolve().parents[1] if "__file__" in globals() else Path.cwd() generate(root, check=arguments.check) if __name__ == "__main__": main() elif "Import" in globals(): generate(Path.cwd())