#!/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())