""" Wire shapes shared by every node transport. `file_chunk` lives in `meshbay_common.protocol` — it is pure crypto and shape, so a client can use the same encoder. This module is for the messages that also need the node's own view of its disk, which `meshbay-common` cannot see. Why it exists at all: `index_sync` was built twice, and the two copies did not agree. WebRTC sent `{group_id, version, entries, dirs, roots}` — the shape the shipping client reads — while QUIC sent `{index_b64}`, a signed, compressed, GEK-encrypted envelope produced by `GroupIndex.serialize()`. Same message type, two encodings, one consumer each and nothing asserting they matched. Same failure mode as the two `file_chunk` encoders, and the same fix: one builder, used by both. `GroupIndex.serialize()`/`deserialize()` are unchanged and still tested — they remain a correct signed index envelope — but they no longer describe any MNP message. Read them as an at-rest/interchange format, not as a wire contract. """ from __future__ import annotations from meshbay_common import MNP_VERSION from meshbay_common.protocol import MNP, index_entry_wire from meshbay_node.roots import RootSet # A group with a deep tree can hold more directories than anyone will navigate in one # sitting, and the whole list rides on one message. MAX_DIRS = 2000 def list_dirs(roots: RootSet | None) -> list[str]: """ Every directory in the group, as members address them, sorted. Each root appears as a directory in its own right, so a root holding no files yet is still somewhere a member can navigate to and upload into. An unavailable root is listed too — its content is frozen, not gone, and hiding it would look exactly like deletion. """ if not roots: return [] out: list[str] = [] for root in roots: out.append(root.name) if not root.available: continue try: for path in sorted(root.path.rglob("*")): if path.is_dir() and not path.name.startswith("."): rel = path.relative_to(root.path) if not any(part.startswith(".") for part in rel.parts): out.append(f"{root.name}/{rel.as_posix()}") except OSError: continue return sorted(out)[:MAX_DIRS] def index_sync_message(index, roots: RootSet | None) -> dict: """ The full `index_sync` message for one group. `dirs` and `roots` are here because directories are not index entries: without them a folder someone just created, or one they emptied, does not exist as far as a client is concerned, and a member cannot tell "the drive is unplugged" from "it is all still there". """ return { "type": MNP.INDEX_SYNC, "v": MNP_VERSION, "group_id": index.group_id, "version": index.version, "entries": [index_entry_wire(e) for e in index.entries], "dirs": list_dirs(roots), "roots": roots.describe() if roots else [], }