summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/src/meshbay_common
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-15 12:19:22 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-15 12:19:22 +0200
commit8cd7e467ebec987f66c4fe93a8d87dfbc57304d2 (patch)
tree0ebd406d893b49ebdf9290ea1a4ac3474d01b7bd /packages/meshbay-common/src/meshbay_common
parent0503682c0e2add135b88c2a1fadfe07455680a71 (diff)
downloadmeshbay-8cd7e467ebec987f66c4fe93a8d87dfbc57304d2.tar.gz
feat(files): download a folder as a zip, and remove an empty one
Two things a Files panel needs and did not have. **Removing a directory** is privileged, where creating one is not: it acts on a name other members are using, on the operator's disk. It is refused unless the directory is empty, and that rule is the safety property — whatever the browser sends, this cannot destroy content. The check runs twice, once before the challenge and once after the signature comes back, because a file can land during the round trip. A file also accepts its uploader's key; a directory has no uploader, so only the operator's key will do. **Downloading a folder** produces a zip built in the browser, written straight to disk as the chunks arrive. An archive of a group folder is routinely tens of gigabytes, so nothing is held: peak memory is one chunk plus a small record per file. The node is not involved at all — it serves the same encrypted chunks as any other download, holds no temporary files, and cannot be asked to compress anything. zipstream.js is store-only. Group content is video and images, already compressed, so deflate would spend CPU on every byte to save nothing, in the thread that is also decrypting. Sizes and CRCs go in a data descriptor after each file because a stream cannot seek back to patch a header, and zip64 kicks in per entry past 4 GiB and for the archive itself. Because none of that can be checked from the Python side of the house, test_zipstream.py runs the real module under Node and reads what it produces with zipfile — CRCs, UTF-8 names, zip64 records and all. The archives also pass `unzip -t`. Firefox and Safari have no File System Access API, so there is nowhere to stream to: the fallback builds the archive in memory and says so, with the size, before starting rather than after failing. One mistake worth recording: the first version of deleteDirectory passed the node's own answer as the value to check the challenge against, which turns the comparison into a tautology. It checks the path we asked for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-common/src/meshbay_common')
-rw-r--r--packages/meshbay-common/src/meshbay_common/adminop.py4
-rw-r--r--packages/meshbay-common/src/meshbay_common/protocol.py2
2 files changed, 5 insertions, 1 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/adminop.py b/packages/meshbay-common/src/meshbay_common/adminop.py
index 2d90102..8b301db 100644
--- a/packages/meshbay-common/src/meshbay_common/adminop.py
+++ b/packages/meshbay-common/src/meshbay_common/adminop.py
@@ -34,6 +34,7 @@ ADMIN_TRANSCRIPT_PREFIX = b"meshbay:admin:v1"
# Operations that require node-operator authority.
OP_FILE_DELETE = "file_delete"
+OP_DIR_DELETE = "dir_delete"
OP_INVITE_CREATE = "invite_create"
# OP_GEK_BUNDLE_STORE is gone. Members no longer hand the node key material at
# all: the node holds the GEK and wraps it itself, for a key the recipient proved
@@ -58,7 +59,8 @@ def admin_transcript(
Build the exact byte string signed for an admin operation.
`subject` identifies what is being acted on: a file_id for OP_FILE_DELETE, the
- invitee's user_id for OP_INVITE_CREATE.
+ path relative to the shared root for OP_DIR_DELETE, the invitee's user_id for
+ OP_INVITE_CREATE.
"""
fields = [
op.encode(),
diff --git a/packages/meshbay-common/src/meshbay_common/protocol.py b/packages/meshbay-common/src/meshbay_common/protocol.py
index 08dc47b..62d8847 100644
--- a/packages/meshbay-common/src/meshbay_common/protocol.py
+++ b/packages/meshbay-common/src/meshbay_common/protocol.py
@@ -37,6 +37,8 @@ class MNP:
FILE_UPLOAD_ACK = "file_upload_ack" # node acknowledges chunk receipt
DIR_CREATE = "dir_create" # client → node: make a directory
DIR_CREATE_ACK = "dir_create_ack" # node → client: created
+ DIR_DELETE = "dir_delete" # client → node: remove an empty directory
+ DIR_DELETE_ACK = "dir_delete_ack" # node → client: removed
FILE_DELETE = "file_delete" # client requests file deletion
FILE_DELETE_ACK = "file_delete_ack" # node confirms deletion
STREAM_REQUEST = "stream_req" # client requests MSE video stream