mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-07-19 06:38:10 -07:00
Work on bundles
This commit is contained in:
+138
-28
@@ -5,30 +5,41 @@ import os.path
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
class Bundle:
|
||||
APP_NAME = "rnsbundle"
|
||||
|
||||
NO_CUSTODY = 0x00;
|
||||
TAKING_CUSTODY = 0x01;
|
||||
FULL_CUSTODY = 0x02;
|
||||
REMOVED = 0xFF;
|
||||
|
||||
CHUNK_SIZE = RNS.Resource.MAX_EFFICIENT_SIZE / 4
|
||||
|
||||
def __init__(self, destination = None, data = None, filepath = None, advertised_id = None):
|
||||
self.destination = destination
|
||||
self.state = None
|
||||
self.data_file = None
|
||||
self.meta_file = None
|
||||
self.id = None
|
||||
self.storagepath = None
|
||||
self.size = None
|
||||
self.chunks = 0
|
||||
self.heartbeat = time.time()
|
||||
self.resources = []
|
||||
def __init__(self, destination_hash = None, data = None, filepath = None, advertisement_data = None):
|
||||
self.destination_hash = None
|
||||
self.is_originator = False
|
||||
self.state = None
|
||||
self.data_file = None
|
||||
self.meta_file = None
|
||||
self.data_hash = None
|
||||
self.id = None
|
||||
self.storagepath = None
|
||||
self.size = None
|
||||
self.chunks = 0
|
||||
self.created = time.time()
|
||||
self.heartbeat = created
|
||||
self.transferring = False
|
||||
|
||||
self.chunk_request_destination = None
|
||||
|
||||
try:
|
||||
if data != None or filepath != None:
|
||||
self.destination_hash = destination_hash
|
||||
self.is_originator = True
|
||||
self.id = RNS.Identity.getRandomHash()
|
||||
|
||||
if filepath == None and data != None:
|
||||
try:
|
||||
self.id = RNS.Identity.fullHash(data)
|
||||
self.data_hash = RNS.Identity.fullHash(data)
|
||||
self.storagepath = Reticulum.bundlepath+"/"+self.id.hex()
|
||||
self.datapath = self.storagepath+"/data"
|
||||
self.metadatapath = self.storagepath+"/metadata"
|
||||
@@ -50,7 +61,7 @@ class Bundle:
|
||||
elif data == None and filepath != None:
|
||||
try:
|
||||
input_file = open(filepath, "rb")
|
||||
self.id = RNS.Identity.fullHash(input_file.read())
|
||||
self.data_hash = RNS.Identity.fullHash(input_file.read())
|
||||
input_file.seek(0)
|
||||
|
||||
self.storagepath = RNS.Reticulum.bundlepath+"/"+self.id.hex()
|
||||
@@ -76,42 +87,141 @@ class Bundle:
|
||||
else:
|
||||
raise ValueError("Bundle cannot be created from data and file path at the same time")
|
||||
|
||||
elif advertised_id != None:
|
||||
# Prepare file handles and metadata
|
||||
self.size = os.stat(self.datapath).st_size
|
||||
if self.size < 1:
|
||||
raise IOError("Bundle data is empty")
|
||||
self.data_file = open(self.datapath, "rb")
|
||||
|
||||
elif advertisement_data != None:
|
||||
# Incoming bundle transfer
|
||||
self.id = advertisement_data[1]
|
||||
self.destination_hash = advertisement_data[0]
|
||||
self.state = Bundle.TAKING_CUSTODY
|
||||
|
||||
self.storagepath = Reticulum.bundlepath+"/"+self.id.hex()
|
||||
self.datapath = self.storagepath+"/data"
|
||||
self.metadatapath = self.storagepath+"/metadata"
|
||||
|
||||
if not os.path.isdir(self.storagepath):
|
||||
os.makedirs(self.storagepath)
|
||||
else:
|
||||
RNS.log("Warning, bundle already exists in storage location, recreating", RNS.LOG_DEBUG)
|
||||
|
||||
self.data_file = open(self.datapath, "wb")
|
||||
self.data_file.close()
|
||||
|
||||
self.size = advertisement_data[2]
|
||||
self.data_file = open(self.datapath, "wb")
|
||||
|
||||
else:
|
||||
raise ValueError("No source of data specified for bundle initialisation")
|
||||
|
||||
# Prepare file handles and metadata
|
||||
self.size = os.stat(self.datapath).st_size
|
||||
if self.size < 1:
|
||||
raise IOError("Bundle data is empty")
|
||||
|
||||
self.chunks = ((self.size-1)//Bundle.CHUNK_SIZE)+1
|
||||
self.data_file = open(self.datapath, "rb")
|
||||
self.flush_metadata()
|
||||
|
||||
RNS.Transport.register_bundle(self)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while initialising bundle. The contained exception was:", RNS.LOG_ERROR)
|
||||
RNS.log(str(e), RNS.LOG_ERROR)
|
||||
# TODO: Remove
|
||||
raise e
|
||||
|
||||
def get_packed_metadata(self):
|
||||
metadata = {
|
||||
"destination": self.destination,
|
||||
"heartbeat": self.heartbeat,
|
||||
"size": self.size,
|
||||
"is_originator": self.is_originator
|
||||
"state": self.state}
|
||||
|
||||
return umsgpack.packb(metadata)
|
||||
|
||||
def flush_metadata(self):
|
||||
try:
|
||||
metadata = {
|
||||
"destination": self.destination,
|
||||
"heartbeat": self.heartbeat,
|
||||
"size": self.size,
|
||||
"chunks": self.chunks,
|
||||
"state": self.state}
|
||||
|
||||
self.meta_file = open(self.metadatapath, "wb")
|
||||
self.meta_file.write(umsgpack.packb(metadata))
|
||||
self.meta_file.write(self.get_packed_metadata())
|
||||
self.meta_file.close()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while flushing metadata for bundle "+RNS.prettyhexrep(self.id), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def register_destinations(self, destination):
|
||||
self.chunk_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "chunk", "request")
|
||||
self.chunk_request_destination.link_established_callback(requester_connected)
|
||||
|
||||
def advertise(self, advertise_to):
|
||||
advertisement = [
|
||||
self.destination,
|
||||
self.id,
|
||||
self.size,
|
||||
self.chunks]
|
||||
|
||||
advertisement_data = umsgpack.packb(advertisement)
|
||||
advertisement_packet = RNS.Packet(advertise_to, advertisement_data)
|
||||
advertisement.packet.send()
|
||||
|
||||
def requester_connected(self, link):
|
||||
RNS.log("Requester connected to bundle "+RNS.prettyhexrep(self.id), RNS.LOG_DEBUG)
|
||||
link.packet_callback(chunk_request)
|
||||
|
||||
def chunk_request(self, data, packet):
|
||||
chunk_index = data[0]
|
||||
RNS.log("Request for chunk "+str(chunk_index)+"/"+str(self.chunks)+" of bundle "+RNS.prettyhexrep(self.id), RNS.LOG_DEBUG)
|
||||
if chunk_index < self.chunks:
|
||||
self.emit_resource(packet.link, chunk_index)
|
||||
else:
|
||||
RNS.log("Bundle transfer client requested chunk index out of range, tearing down link.", RNS.LOG_ERROR)
|
||||
packet.link.teardown()
|
||||
|
||||
def emit_resource(self, link, chunk_index):
|
||||
if not self.transferring:
|
||||
chunk_max = self.size-1
|
||||
chunk_start = chunk_index*CHUNK_SIZE
|
||||
chunk_end = (chunk_index+1)*CHUNK_SIZE-1
|
||||
if chunk_end > chunk_max:
|
||||
chunk_end = chunk_max
|
||||
read_size = chunk_end - chunk_start
|
||||
|
||||
try:
|
||||
file = open(self.datapath, "rb")
|
||||
file.seek(chunk_start)
|
||||
data = file.read(read_size)
|
||||
chunk_resource = RNS.Resource(data, link, callback=resource_concluded)
|
||||
chunk_resource.chunk_index = chunk_index
|
||||
except Exception as e:
|
||||
RNS.log("Could not read bundle data from storage, the contained exception was:", RNS.LOG_ERROR)
|
||||
RNS.log(str(e))
|
||||
link.teardown()
|
||||
else:
|
||||
RNS.log("Bundle chunk "+str(chunk_index)+" for "+RNS.prettyhexrep(self.id)+" was requested while a transfer was already in progress", RNS.LOG_ERROR)
|
||||
|
||||
def resource_concluded(self, resource):
|
||||
RNS.log("Concluded transferring chunk "+str(resource.chunk_index)+"/"+str(self.chunks)+" of bundle "+RNS.prettyhexrep(self.id), RNS.LOG_DEBUG)
|
||||
self.transferring = False
|
||||
|
||||
def resign_custody(self):
|
||||
self.state = Bundle.NO_CUSTODY
|
||||
self.heartbeat = time.time()
|
||||
|
||||
def custody_proof(self, proof):
|
||||
pass
|
||||
|
||||
def remove(self):
|
||||
try:
|
||||
self.state = Bundle.REMOVED
|
||||
RNS.Transport.deregister_destination(self.chunk_request_destination)
|
||||
os.unlink(self.datapath)
|
||||
os.unlink(self.metadatapath)
|
||||
os.rmdir(self.storagepath)
|
||||
except Exception as e:
|
||||
RNS.log("Error while removing bundle from storage, the contained exception was:", RNS.LOG_ERROR)
|
||||
RNS.log(str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
|
||||
|
||||
class BundleAdvertisement:
|
||||
pass
|
||||
Reference in New Issue
Block a user