diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/wire.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/wire.py | 58 |
1 files changed, 44 insertions, 14 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/transport/wire.py b/packages/meshbay-node/src/meshbay_node/transport/wire.py index 258edb5..77c5d62 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/wire.py +++ b/packages/meshbay-node/src/meshbay_node/transport/wire.py @@ -27,11 +27,14 @@ message we have not yet authenticated. from __future__ import annotations +import os +from pathlib import Path + from meshbay_common import MNP_VERSION from meshbay_common.groupbox import PURPOSE_INDEX, seal from meshbay_common.protocol import MNP, index_entry_wire -from meshbay_node.roots import RootSet +from meshbay_node.roots import RootSet, off_disk # 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. @@ -47,25 +50,49 @@ def list_dirs(roots: RootSet | None) -> list[str]: is listed too — its content is frozen, not gone, and hiding it would look exactly like deletion. """ + return _walk_dirs(_dir_roots(roots)) if roots else [] + + +async def list_dirs_off_loop(roots: RootSet | None) -> list[str]: + """ + `list_dirs`, on the thread that serves `roots`. + + The walk is a syscall per directory over every root of the group, and it ran + on the event loop for each full index: on a library of several large roots + that was seconds in which the node answered nobody, not its members and not + the desktop client asking whether it was there. Found live: a tester adding a + sixth directory saw "Add a directory" vanish because the node missed a 3 s + check while it indexed the other five. + """ if not roots: return [] + return await off_disk(roots, _walk_dirs, _dir_roots(roots)) + + +def _dir_roots(roots: RootSet) -> list[tuple[str, Path, bool]]: + """What the walk needs from each root, read on the loop that owns them.""" + return [(root.name, root.path, root.available) for root in roots] + + +def _walk_dirs(dir_roots: list[tuple[str, Path, bool]]) -> list[str]: + """Blocking. Directories only: `os.walk` reads each one once and never + stats a file, which `rglob("*")` and an `is_dir()` per entry did.""" 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: + for name, path, available in dir_roots: + out.append(name) + if not available: continue + for top, subdirs, _files in os.walk(path): + # Pruned in place: nothing under a hidden directory is listed either. + subdirs[:] = [d for d in subdirs if not d.startswith(".")] + rel = Path(top).relative_to(path) + for d in subdirs: + out.append(f"{name}/{(rel / d).as_posix()}") return sorted(out)[:MAX_DIRS] -def index_sync_message(index, roots: RootSet | None, hidden=()) -> dict: +def index_sync_message(index, roots: RootSet | None, hidden=(), + dirs: list[str] | None = None) -> dict: """ The full `index_sync` message for one group. @@ -76,11 +103,14 @@ def index_sync_message(index, roots: RootSet | None, hidden=()) -> dict: `hidden` is the content blocklist in a public group (`meshbay_node.blocklist`): those entries are not sent at all. + + `dirs` is `list_dirs_off_loop(roots)`, awaited by a caller on the event loop; + left out, the walk runs here, where the caller is. """ payload = { "version": index.version, "entries": [index_entry_wire(e) for e in index.entries if e.id not in hidden], - "dirs": list_dirs(roots), + "dirs": list_dirs(roots) if dirs is None else dirs, "roots": roots.describe() if roots else [], } return { |