Added fetch mode to rncp
This commit is contained in:
parent
254c966159
commit
b477354235
|
@ -35,8 +35,9 @@ APP_NAME = "rncp"
|
||||||
allow_all = False
|
allow_all = False
|
||||||
allowed_identity_hashes = []
|
allowed_identity_hashes = []
|
||||||
|
|
||||||
def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, announce = False):
|
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, announce = False):
|
||||||
global allow_all, allowed_identity_hashes
|
global allow_all, allowed_identity_hashes
|
||||||
|
from tempfile import TemporaryFile
|
||||||
identity = None
|
identity = None
|
||||||
if announce < 0:
|
if announce < 0:
|
||||||
announce = False
|
announce = False
|
||||||
|
@ -86,7 +87,7 @@ def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_ident
|
||||||
allowed = ali
|
allowed = ali
|
||||||
else:
|
else:
|
||||||
allowed.extend(ali)
|
allowed.extend(ali)
|
||||||
if al == 1:
|
if len(ali) == 1:
|
||||||
ms = "y"
|
ms = "y"
|
||||||
else:
|
else:
|
||||||
ms = "ies"
|
ms = "ies"
|
||||||
|
@ -113,7 +114,44 @@ def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_ident
|
||||||
if len(allowed_identity_hashes) < 1 and not disable_auth:
|
if len(allowed_identity_hashes) < 1 and not disable_auth:
|
||||||
print("Warning: No allowed identities configured, rncp will not accept any files!")
|
print("Warning: No allowed identities configured, rncp will not accept any files!")
|
||||||
|
|
||||||
destination.set_link_established_callback(receive_link_established)
|
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
|
||||||
|
target_link = None
|
||||||
|
for link in RNS.Transport.active_links:
|
||||||
|
if link.link_id == link_id:
|
||||||
|
target_link = link
|
||||||
|
|
||||||
|
file_path = os.path.expanduser(data)
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
RNS.log("Client-requested file not found: "+str(file_path), RNS.LOG_VERBOSE)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if target_link != None:
|
||||||
|
RNS.log("Sending file "+str(file_path)+" to client", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
|
temp_file = TemporaryFile()
|
||||||
|
real_file = open(file_path, "rb")
|
||||||
|
filename_bytes = os.path.basename(file_path).encode("utf-8")
|
||||||
|
filename_len = len(filename_bytes)
|
||||||
|
|
||||||
|
if filename_len > 0xFFFF:
|
||||||
|
print("Filename exceeds max size, cannot send")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
print("Preparing file...", end=" ")
|
||||||
|
|
||||||
|
temp_file.write(filename_len.to_bytes(2, "big"))
|
||||||
|
temp_file.write(filename_bytes)
|
||||||
|
temp_file.write(real_file.read())
|
||||||
|
temp_file.seek(0)
|
||||||
|
|
||||||
|
fetch_resource = RNS.Resource(temp_file, target_link)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
destination.set_link_established_callback(client_link_established)
|
||||||
|
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
|
||||||
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
|
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
|
||||||
|
|
||||||
if announce >= 0:
|
if announce >= 0:
|
||||||
|
@ -129,7 +167,7 @@ def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_ident
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def receive_link_established(link):
|
def client_link_established(link):
|
||||||
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
|
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
|
||||||
link.set_remote_identified_callback(receive_sender_identified)
|
link.set_remote_identified_callback(receive_sender_identified)
|
||||||
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
|
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
|
||||||
|
@ -223,6 +261,220 @@ def sender_progress(resource):
|
||||||
resource_done = True
|
resource_done = True
|
||||||
|
|
||||||
link = None
|
link = None
|
||||||
|
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
|
||||||
|
global current_resource, resource_done, link, speed
|
||||||
|
targetloglevel = 3+verbosity-quietness
|
||||||
|
|
||||||
|
try:
|
||||||
|
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||||
|
if len(destination) != dest_len:
|
||||||
|
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
|
||||||
|
try:
|
||||||
|
destination_hash = bytes.fromhex(destination)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Invalid destination entered. Check your input.")
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||||
|
|
||||||
|
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
|
||||||
|
if os.path.isfile(identity_path):
|
||||||
|
identity = RNS.Identity.from_file(identity_path)
|
||||||
|
if identity == None:
|
||||||
|
RNS.log("Could not load identity for rncp. The identity file at \""+str(identity_path)+"\" may be corrupt or unreadable.", RNS.LOG_ERROR)
|
||||||
|
exit(2)
|
||||||
|
else:
|
||||||
|
identity = None
|
||||||
|
|
||||||
|
if identity == None:
|
||||||
|
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
|
||||||
|
identity = RNS.Identity()
|
||||||
|
identity.to_file(identity_path)
|
||||||
|
|
||||||
|
if not RNS.Transport.has_path(destination_hash):
|
||||||
|
RNS.Transport.request_path(destination_hash)
|
||||||
|
if silent:
|
||||||
|
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
|
||||||
|
else:
|
||||||
|
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||||
|
estab_timeout = time.time()+timeout
|
||||||
|
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
|
||||||
|
if not silent:
|
||||||
|
time.sleep(0.1)
|
||||||
|
print(("\b\b"+syms[i]+" "), end="")
|
||||||
|
sys.stdout.flush()
|
||||||
|
i = (i+1)%len(syms)
|
||||||
|
|
||||||
|
if not RNS.Transport.has_path(destination_hash):
|
||||||
|
if silent:
|
||||||
|
print("Path not found")
|
||||||
|
else:
|
||||||
|
print("\r \rPath not found")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
if silent:
|
||||||
|
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
|
||||||
|
else:
|
||||||
|
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
|
||||||
|
|
||||||
|
listener_identity = RNS.Identity.recall(destination_hash)
|
||||||
|
listener_destination = RNS.Destination(
|
||||||
|
listener_identity,
|
||||||
|
RNS.Destination.OUT,
|
||||||
|
RNS.Destination.SINGLE,
|
||||||
|
APP_NAME,
|
||||||
|
"receive"
|
||||||
|
)
|
||||||
|
|
||||||
|
link = RNS.Link(listener_destination)
|
||||||
|
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
|
||||||
|
if not silent:
|
||||||
|
time.sleep(0.1)
|
||||||
|
print(("\b\b"+syms[i]+" "), end="")
|
||||||
|
sys.stdout.flush()
|
||||||
|
i = (i+1)%len(syms)
|
||||||
|
|
||||||
|
if not RNS.Transport.has_path(destination_hash):
|
||||||
|
if silent:
|
||||||
|
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||||
|
else:
|
||||||
|
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
if silent:
|
||||||
|
print("Requesting file from remote...")
|
||||||
|
else:
|
||||||
|
print("\r \rRequesting file from remote ", end=" ")
|
||||||
|
|
||||||
|
link.identify(identity)
|
||||||
|
|
||||||
|
request_resolved = False
|
||||||
|
request_status = "unknown"
|
||||||
|
resource_resolved = False
|
||||||
|
resource_status = "unrequested"
|
||||||
|
current_resource = None
|
||||||
|
def request_response(request_receipt):
|
||||||
|
nonlocal request_resolved, request_status
|
||||||
|
if request_receipt.response == False:
|
||||||
|
request_status = "not_found"
|
||||||
|
elif request_receipt.response == None:
|
||||||
|
request_status = "remote_error"
|
||||||
|
else:
|
||||||
|
request_status = "found"
|
||||||
|
|
||||||
|
request_resolved = True
|
||||||
|
|
||||||
|
def request_failed(request_receipt):
|
||||||
|
nonlocal request_resolved, request_status
|
||||||
|
request_status = "unknown"
|
||||||
|
request_resolved = True
|
||||||
|
|
||||||
|
def fetch_resource_started(resource):
|
||||||
|
nonlocal resource_status
|
||||||
|
current_resource = resource
|
||||||
|
current_resource.progress_callback(sender_progress)
|
||||||
|
resource_status = "started"
|
||||||
|
|
||||||
|
def fetch_resource_concluded(resource):
|
||||||
|
nonlocal resource_resolved, resource_status
|
||||||
|
if resource.status == RNS.Resource.COMPLETE:
|
||||||
|
if resource.total_size > 4:
|
||||||
|
filename_len = int.from_bytes(resource.data.read(2), "big")
|
||||||
|
filename = resource.data.read(filename_len).decode("utf-8")
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
saved_filename = filename
|
||||||
|
while os.path.isfile(saved_filename):
|
||||||
|
counter += 1
|
||||||
|
saved_filename = filename+"."+str(counter)
|
||||||
|
|
||||||
|
file = open(saved_filename, "wb")
|
||||||
|
file.write(resource.data.read())
|
||||||
|
file.close()
|
||||||
|
resource_status = "completed"
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Invalid data received, ignoring resource")
|
||||||
|
resource_status = "invalid_data"
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Resource failed")
|
||||||
|
resource_status = "failed"
|
||||||
|
|
||||||
|
resource_resolved = True
|
||||||
|
|
||||||
|
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
||||||
|
link.set_resource_started_callback(fetch_resource_started)
|
||||||
|
link.set_resource_concluded_callback(fetch_resource_concluded)
|
||||||
|
link.request("fetch_file", data=file, response_callback=request_response, failed_callback=request_failed)
|
||||||
|
|
||||||
|
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||||
|
while not request_resolved:
|
||||||
|
if not silent:
|
||||||
|
time.sleep(0.1)
|
||||||
|
print(("\b\b"+syms[i]+" "), end="")
|
||||||
|
sys.stdout.flush()
|
||||||
|
i = (i+1)%len(syms)
|
||||||
|
|
||||||
|
if request_status == "not_found":
|
||||||
|
if not silent: print("\r \r", end="")
|
||||||
|
print("Fetch request failed, the file "+str(file)+" was not found on the remote")
|
||||||
|
link.teardown()
|
||||||
|
time.sleep(1)
|
||||||
|
exit(0)
|
||||||
|
elif request_status == "remote_error":
|
||||||
|
if not silent: print("\r \r", end="")
|
||||||
|
print("Fetch request failed due to an error on the remote system")
|
||||||
|
link.teardown()
|
||||||
|
time.sleep(1)
|
||||||
|
exit(0)
|
||||||
|
elif request_status == "unknown":
|
||||||
|
if not silent: print("\r \r", end="")
|
||||||
|
print("Fetch request failed due to an unknown error")
|
||||||
|
link.teardown()
|
||||||
|
time.sleep(1)
|
||||||
|
exit(0)
|
||||||
|
elif request_status == "found":
|
||||||
|
if not silent: print("\r \r", end="")
|
||||||
|
|
||||||
|
while not resource_resolved:
|
||||||
|
if not silent:
|
||||||
|
time.sleep(0.1)
|
||||||
|
if current_resource:
|
||||||
|
prg = current_resource.get_progress()
|
||||||
|
percent = round(prg * 100.0, 1)
|
||||||
|
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
|
||||||
|
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||||
|
else:
|
||||||
|
print("\r \rWaiting for transfer to start "+syms[i]+" ", end=" ")
|
||||||
|
sys.stdout.flush()
|
||||||
|
i = (i+1)%len(syms)
|
||||||
|
|
||||||
|
if current_resource.status != RNS.Resource.COMPLETE:
|
||||||
|
if silent:
|
||||||
|
print("The transfer failed")
|
||||||
|
else:
|
||||||
|
print("\r \rThe transfer failed")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
if silent:
|
||||||
|
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||||
|
else:
|
||||||
|
print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
|
||||||
|
link.teardown()
|
||||||
|
time.sleep(0.25)
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
link.teardown()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
|
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
|
||||||
global current_resource, resource_done, link, speed
|
global current_resource, resource_done, link, speed
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
|
@ -399,9 +651,8 @@ def main():
|
||||||
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
|
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
|
||||||
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
|
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
|
||||||
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
|
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
|
||||||
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
|
|
||||||
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
|
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
|
||||||
#parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener")
|
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener")
|
||||||
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
|
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
|
||||||
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
|
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
|
||||||
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept files from anyone")
|
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept files from anyone")
|
||||||
|
@ -413,7 +664,7 @@ def main():
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.listen or args.print_identity:
|
if args.listen or args.print_identity:
|
||||||
receive(
|
listen(
|
||||||
configdir = args.config,
|
configdir = args.config,
|
||||||
verbosity=args.verbose,
|
verbosity=args.verbose,
|
||||||
quietness=args.quiet,
|
quietness=args.quiet,
|
||||||
|
@ -424,6 +675,22 @@ def main():
|
||||||
announce=args.b,
|
announce=args.b,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif args.fetch:
|
||||||
|
if args.destination != None and args.file != None:
|
||||||
|
fetch(
|
||||||
|
configdir = args.config,
|
||||||
|
verbosity = args.verbose,
|
||||||
|
quietness = args.quiet,
|
||||||
|
destination = args.destination,
|
||||||
|
file = args.file,
|
||||||
|
timeout = args.w,
|
||||||
|
silent = args.silent,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("")
|
||||||
|
parser.print_help()
|
||||||
|
print("")
|
||||||
|
|
||||||
elif args.destination != None and args.file != None:
|
elif args.destination != None and args.file != None:
|
||||||
send(
|
send(
|
||||||
configdir = args.config,
|
configdir = args.config,
|
||||||
|
|
Loading…
Reference in New Issue