diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-05 10:16:28 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-05 10:16:28 +0200 |
| commit | 41d015137d6d9c097e462cd0662e855c3253d001 (patch) | |
| tree | e1e5a4e6f1415509c031e079c2b753591975ed05 /examples/upload.py | |
| parent | fe64864e8cfd2cca3a70ae375a3ea16d537ae6be (diff) | |
| download | meshbay-41d015137d6d9c097e462cd0662e855c3253d001.tar.gz | |
docs: add Python examples for the hub, a node and its control API
examples/ holds small scripts that talk to MeshBay the way the application
does, with the values to change as variables at the top of each one:
- meshbay_session.py: hub sign-in, WebRTC to a node serving the group, and
the handshake (identity bundle, group key, proofs both ways)
- list_groups.py: the user's groups, up or down
- download.py: one file, decrypted chunk by chunk under a transfer lease
- upload.py: one file, sealed in 48 KiB chunks into a group folder
- create_group.py: creates a group on the hub and hosts it on the local node
through its loopback control API
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'examples/upload.py')
| -rw-r--r-- | examples/upload.py | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/examples/upload.py b/examples/upload.py new file mode 100644 index 0000000..518b5ec --- /dev/null +++ b/examples/upload.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Upload one file to a MeshBay group, as a member, without a browser.""" + +import asyncio +import math +import os + +from meshbay_common.groupbox import PURPOSE_UPLOAD, unseal +from meshbay_common.protocol import file_upload_wire +from meshbay_session import connect + +HUB = "https://meshbay.org" +USERNAME = "alice" +PASSPHRASE = "your passphrase" +GROUP = "Family Photos" # the group's name, as shown in the sidebar +NODE_ID = "" # empty: the first node online for this group +LOCAL_FILE = "beach.jpg" # the file to send +DEST_DIR = "shared/2024" # root name, then folders; the root must be writable + +CHUNK_SIZE = 48 * 1024 # what fits in one DataChannel message +WINDOW = 32 # chunks sent ahead of the node's acks + + +async def main() -> None: + session = await connect(HUB, USERNAME, PASSPHRASE, GROUP, NODE_ID) + + filename = os.path.basename(LOCAL_FILE) + size = os.path.getsize(LOCAL_FILE) + chunks = max(1, math.ceil(size / CHUNK_SIZE)) + tr = await session.open_transfer("upload", size, chunks) + upload_id = os.urandom(16).hex() + + # The file name, the folder and the bytes are sealed under the group key. + # Only the upload id and the chunk numbers stay in clear. + acked = 0 + ack = {} + with open(LOCAL_FILE, "rb") as f: + for i in range(chunks): + if i - acked >= WINDOW: + ack = await session.ch.recv("file_upload_ack") + acked += 1 + msg = file_upload_wire(session.gek, session.group_id, upload_id=upload_id, + chunk_index=i, total_chunks=chunks, filename=filename, + data=f.read(CHUNK_SIZE), dir=DEST_DIR) + session.ch.send({**msg, "tr": tr}) + print(f"\r{i + 1}/{chunks} chunks", end="", flush=True) + while acked < chunks: + ack = await session.ch.recv("file_upload_ack") + acked += 1 + session.close_transfer(tr) + + # The node never overwrites: on a name clash it picks a free one. + done = unseal(session.gek, PURPOSE_UPLOAD, "file_upload_ack", session.group_id, ack) + print(f"\nStored as {done['dir']}/{done['stored_as']} ({size} bytes)") + await session.close() + + +if __name__ == "__main__": + asyncio.run(main()) |