aboutsummaryrefslogtreecommitdiffstats
path: root/examples/upload.py
blob: 518b5ece9288307f317b08f3edfc97e6db481185 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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())