aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/transport/wire.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/transport/wire.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/transport/wire.py58
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 {