mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 14:54:26 +01:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5736012f2c | ||
|
|
bb98a512f3 | ||
|
|
ae0d4c6e0c | ||
|
|
2449b39f77 | ||
|
|
e022d469f8 | ||
|
|
a4f665e650 | ||
|
|
19d1d8504f | ||
|
|
276a5f56e1 | ||
|
|
89dd17ece5 | ||
|
|
82bb479957 | ||
|
|
082026ca1b | ||
|
|
df9fccf199 | ||
|
|
efd1d08399 | ||
|
|
5a7c84d2a4 | ||
|
|
a3bf538afe | ||
|
|
8d72a83411 | ||
|
|
0fb9e180ba | ||
|
|
2322a254a8 | ||
|
|
37ad5cc5d3 | ||
|
|
a921adbdbe | ||
|
|
5c1a2c3485 | ||
|
|
0cd1c9cf37 | ||
|
|
cfc0f4f9c1 |
@@ -4,7 +4,7 @@ Off-grid, resilient mesh communication with strong encryption, forward secrecy a
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Nomad Network Allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them. No signups, no agreements, no handover of any data, no permissions and gatekeepers.
|
Nomad Network allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them. No signups, no agreements, no handover of any data, no permissions and gatekeepers.
|
||||||
|
|
||||||
Nomad Network is build on [LXMF](https://github.com/markqvist/LXMF) and [Reticulum](https://github.com/markqvist/Reticulum), which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to fiber optics.
|
Nomad Network is build on [LXMF](https://github.com/markqvist/LXMF) and [Reticulum](https://github.com/markqvist/Reticulum), which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to fiber optics.
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class Conversation:
|
|||||||
|
|
||||||
self.__changed_callback = None
|
self.__changed_callback = None
|
||||||
|
|
||||||
if not RNS.Transport.has_path(bytes.fromhex(source_hash)):
|
if not RNS.Identity.recall(bytes.fromhex(self.source_hash)):
|
||||||
RNS.Transport.request_path(bytes.fromhex(source_hash))
|
RNS.Transport.request_path(bytes.fromhex(source_hash))
|
||||||
|
|
||||||
self.source_identity = RNS.Identity.recall(bytes.fromhex(self.source_hash))
|
self.source_identity = RNS.Identity.recall(bytes.fromhex(self.source_hash))
|
||||||
@@ -227,7 +227,7 @@ class Conversation:
|
|||||||
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def paper_output(self, content="", title=""):
|
def paper_output(self, content="", title="", mode="print_qr"):
|
||||||
if self.send_destination:
|
if self.send_destination:
|
||||||
try:
|
try:
|
||||||
dest = self.send_destination
|
dest = self.send_destination
|
||||||
@@ -235,18 +235,41 @@ class Conversation:
|
|||||||
desired_method = LXMF.LXMessage.PAPER
|
desired_method = LXMF.LXMessage.PAPER
|
||||||
|
|
||||||
lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method)
|
lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method)
|
||||||
qr_code = lxm.as_qr()
|
|
||||||
qr_tmp_path = self.app.tmpfilespath+"/"+str(RNS.hexrep(lxm.hash, delimit=False))
|
|
||||||
qr_code.save(qr_tmp_path)
|
|
||||||
|
|
||||||
print_result = self.app.print_file(qr_tmp_path)
|
if mode == "print_qr":
|
||||||
os.unlink(qr_tmp_path)
|
qr_code = lxm.as_qr()
|
||||||
|
qr_tmp_path = self.app.tmpfilespath+"/"+str(RNS.hexrep(lxm.hash, delimit=False))
|
||||||
|
qr_code.save(qr_tmp_path)
|
||||||
|
|
||||||
if print_result:
|
print_result = self.app.print_file(qr_tmp_path)
|
||||||
|
os.unlink(qr_tmp_path)
|
||||||
|
|
||||||
|
if print_result:
|
||||||
|
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||||
|
self.messages.append(ConversationMessage(message_path))
|
||||||
|
|
||||||
|
return print_result
|
||||||
|
|
||||||
|
elif mode == "save_qr":
|
||||||
|
qr_code = lxm.as_qr()
|
||||||
|
qr_save_path = self.app.downloads_path+"/LXM_"+str(RNS.hexrep(lxm.hash, delimit=False)+".png")
|
||||||
|
qr_code.save(qr_save_path)
|
||||||
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||||
self.messages.append(ConversationMessage(message_path))
|
self.messages.append(ConversationMessage(message_path))
|
||||||
|
return qr_save_path
|
||||||
|
|
||||||
return print_result
|
elif mode == "save_uri":
|
||||||
|
lxm_uri = lxm.as_uri()+"\n"
|
||||||
|
uri_save_path = self.app.downloads_path+"/LXM_"+str(RNS.hexrep(lxm.hash, delimit=False)+".txt")
|
||||||
|
with open(uri_save_path, "wb") as f:
|
||||||
|
f.write(lxm_uri.encode("utf-8"))
|
||||||
|
|
||||||
|
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||||
|
self.messages.append(ConversationMessage(message_path))
|
||||||
|
return uri_save_path
|
||||||
|
|
||||||
|
elif mode == "return_uri":
|
||||||
|
return lxm.as_uri()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error occurred while generating paper message, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("An error occurred while generating paper message, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class Directory:
|
|||||||
packed_list = []
|
packed_list = []
|
||||||
for source_hash in self.directory_entries:
|
for source_hash in self.directory_entries:
|
||||||
e = self.directory_entries[source_hash]
|
e = self.directory_entries[source_hash]
|
||||||
packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node, e.preferred_delivery, e.identify))
|
packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node, e.preferred_delivery, e.identify, e.sort_rank))
|
||||||
|
|
||||||
directory = {
|
directory = {
|
||||||
"entry_list": packed_list,
|
"entry_list": packed_list,
|
||||||
@@ -91,6 +91,9 @@ class Directory:
|
|||||||
entries = {}
|
entries = {}
|
||||||
for e in unpacked_list:
|
for e in unpacked_list:
|
||||||
|
|
||||||
|
if e[1] == None:
|
||||||
|
e[1] = "Undefined"
|
||||||
|
|
||||||
if len(e) > 3:
|
if len(e) > 3:
|
||||||
hosts_node = e[3]
|
hosts_node = e[3]
|
||||||
else:
|
else:
|
||||||
@@ -106,13 +109,17 @@ class Directory:
|
|||||||
else:
|
else:
|
||||||
identify = False
|
identify = False
|
||||||
|
|
||||||
entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node, preferred_delivery=preferred_delivery, identify_on_connect=identify)
|
if len(e) > 6:
|
||||||
|
sort_rank = e[6]
|
||||||
|
else:
|
||||||
|
sort_rank = None
|
||||||
|
|
||||||
|
entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node, preferred_delivery=preferred_delivery, identify_on_connect=identify, sort_rank=sort_rank)
|
||||||
|
|
||||||
self.directory_entries = entries
|
self.directory_entries = entries
|
||||||
|
|
||||||
self.announce_stream = unpacked_directory["announce_stream"]
|
self.announce_stream = unpacked_directory["announce_stream"]
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not load directory from disk. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Could not load directory from disk. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
@@ -220,7 +227,14 @@ class Directory:
|
|||||||
def simplest_display_str(self, source_hash):
|
def simplest_display_str(self, source_hash):
|
||||||
trust_level = self.trust_level(source_hash)
|
trust_level = self.trust_level(source_hash)
|
||||||
if trust_level == DirectoryEntry.WARNING or trust_level == DirectoryEntry.UNTRUSTED:
|
if trust_level == DirectoryEntry.WARNING or trust_level == DirectoryEntry.UNTRUSTED:
|
||||||
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
if source_hash in self.directory_entries:
|
||||||
|
dn = self.directory_entries[source_hash].display_name
|
||||||
|
if dn == None:
|
||||||
|
return RNS.prettyhexrep(source_hash)
|
||||||
|
else:
|
||||||
|
return dn+" <"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||||
|
else:
|
||||||
|
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||||
else:
|
else:
|
||||||
if source_hash in self.directory_entries:
|
if source_hash in self.directory_entries:
|
||||||
dn = self.directory_entries[source_hash].display_name
|
dn = self.directory_entries[source_hash].display_name
|
||||||
@@ -253,6 +267,18 @@ class Directory:
|
|||||||
else:
|
else:
|
||||||
return DirectoryEntry.UNKNOWN
|
return DirectoryEntry.UNKNOWN
|
||||||
|
|
||||||
|
def pn_trust_level(self, source_hash):
|
||||||
|
recalled_identity = RNS.Identity.recall(source_hash)
|
||||||
|
if recalled_identity != None:
|
||||||
|
associated_node = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", recalled_identity)
|
||||||
|
return self.trust_level(associated_node)
|
||||||
|
|
||||||
|
def sort_rank(self, source_hash):
|
||||||
|
if source_hash in self.directory_entries:
|
||||||
|
return self.directory_entries[source_hash].sort_rank
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def preferred_delivery(self, source_hash):
|
def preferred_delivery(self, source_hash):
|
||||||
if source_hash in self.directory_entries:
|
if source_hash in self.directory_entries:
|
||||||
return self.directory_entries[source_hash].preferred_delivery
|
return self.directory_entries[source_hash].preferred_delivery
|
||||||
@@ -312,6 +338,7 @@ class Directory:
|
|||||||
if e.hosts_node:
|
if e.hosts_node:
|
||||||
node_list.append(e)
|
node_list.append(e)
|
||||||
|
|
||||||
|
node_list.sort(key = lambda e: (e.sort_rank if e.sort_rank != None else 2^32, DirectoryEntry.TRUSTED-e.trust_level, e.display_name))
|
||||||
return node_list
|
return node_list
|
||||||
|
|
||||||
def number_of_known_nodes(self):
|
def number_of_known_nodes(self):
|
||||||
@@ -336,10 +363,11 @@ class DirectoryEntry:
|
|||||||
DIRECT = 0x01
|
DIRECT = 0x01
|
||||||
PROPAGATED = 0x02
|
PROPAGATED = 0x02
|
||||||
|
|
||||||
def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None, identify_on_connect=False):
|
def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None, identify_on_connect=False, sort_rank=None):
|
||||||
if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
||||||
self.source_hash = source_hash
|
self.source_hash = source_hash
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
|
self.sort_rank = sort_rank
|
||||||
|
|
||||||
if preferred_delivery == None:
|
if preferred_delivery == None:
|
||||||
self.preferred_delivery = DirectoryEntry.DIRECT
|
self.preferred_delivery = DirectoryEntry.DIRECT
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ class Node:
|
|||||||
self.identity = self.app.identity
|
self.identity = self.app.identity
|
||||||
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, "nomadnetwork", "node")
|
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, "nomadnetwork", "node")
|
||||||
self.last_announce = time.time()
|
self.last_announce = time.time()
|
||||||
|
self.last_file_refresh = time.time()
|
||||||
|
self.last_page_refresh = time.time()
|
||||||
self.announce_interval = self.app.node_announce_interval
|
self.announce_interval = self.app.node_announce_interval
|
||||||
|
self.page_refresh_interval = self.app.page_refresh_interval
|
||||||
|
self.file_refresh_interval = self.app.file_refresh_interval
|
||||||
self.job_interval = Node.JOB_INTERVAL
|
self.job_interval = Node.JOB_INTERVAL
|
||||||
self.should_run_jobs = True
|
self.should_run_jobs = True
|
||||||
self.app_data = None
|
self.app_data = None
|
||||||
@@ -223,6 +227,14 @@ class Node:
|
|||||||
if now > self.last_announce + self.announce_interval*60:
|
if now > self.last_announce + self.announce_interval*60:
|
||||||
self.announce()
|
self.announce()
|
||||||
|
|
||||||
|
if self.page_refresh_interval > 0:
|
||||||
|
if now > self.last_page_refresh + self.page_refresh_interval*60:
|
||||||
|
self.register_pages()
|
||||||
|
|
||||||
|
if self.file_refresh_interval > 0:
|
||||||
|
if now > self.last_file_refresh + self.file_refresh_interval*60:
|
||||||
|
self.register_files()
|
||||||
|
|
||||||
time.sleep(self.job_interval)
|
time.sleep(self.job_interval)
|
||||||
|
|
||||||
def peer_connected(self, link):
|
def peer_connected(self, link):
|
||||||
|
|||||||
@@ -115,13 +115,16 @@ class NomadNetworkApp:
|
|||||||
|
|
||||||
self.downloads_path = os.path.expanduser("~/Downloads")
|
self.downloads_path = os.path.expanduser("~/Downloads")
|
||||||
|
|
||||||
self.firstrun = False
|
self.firstrun = False
|
||||||
self.should_run_jobs = True
|
self.should_run_jobs = True
|
||||||
self.job_interval = 5
|
self.job_interval = 5
|
||||||
self.defer_jobs = 90
|
self.defer_jobs = 90
|
||||||
|
self.page_refresh_interval = 0
|
||||||
|
self.file_refresh_interval = 0
|
||||||
|
|
||||||
self.peer_announce_at_start = True
|
self.peer_announce_at_start = True
|
||||||
self.try_propagation_on_fail = True
|
self.try_propagation_on_fail = True
|
||||||
|
self.disable_propagation = False
|
||||||
|
|
||||||
self.periodic_lxmf_sync = True
|
self.periodic_lxmf_sync = True
|
||||||
self.lxmf_sync_interval = 360*60
|
self.lxmf_sync_interval = 360*60
|
||||||
@@ -308,15 +311,27 @@ class NomadNetworkApp:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.message_router.enable_propagation()
|
if self.disable_propagation:
|
||||||
try:
|
if os.path.isfile(self.pnannouncedpath):
|
||||||
with open(self.pnannouncedpath, "wb") as pnf:
|
try:
|
||||||
pnf.write(msgpack.packb(time.time()))
|
RNS.log("Sending indication to peered LXMF Propagation Node that this node is no longer participating", RNS.LOG_DEBUG)
|
||||||
pnf.close()
|
self.message_router.disable_propagation()
|
||||||
except Exception as e:
|
os.unlink(self.pnannouncedpath)
|
||||||
RNS.log("An error ocurred while writing Propagation Node announce timestamp. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
except Exception as e:
|
||||||
|
RNS.log("An error ocurred while indicating that this LXMF Propagation Node is no longer participating. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
else:
|
||||||
|
self.message_router.enable_propagation()
|
||||||
|
try:
|
||||||
|
with open(self.pnannouncedpath, "wb") as pnf:
|
||||||
|
pnf.write(msgpack.packb(time.time()))
|
||||||
|
pnf.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("An error ocurred while writing Propagation Node announce timestamp. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
if not self.disable_propagation:
|
||||||
|
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
||||||
|
|
||||||
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
|
||||||
self.node = nomadnet.Node(self)
|
self.node = nomadnet.Node(self)
|
||||||
else:
|
else:
|
||||||
self.node = None
|
self.node = None
|
||||||
@@ -414,16 +429,24 @@ class NomadNetworkApp:
|
|||||||
return "Receiving messages"
|
return "Receiving messages"
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_RESPONSE_RECEIVED:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_RESPONSE_RECEIVED:
|
||||||
return "Messages received"
|
return "Messages received"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_PATH:
|
||||||
|
return "No path to node"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_LINK_FAILED:
|
||||||
|
return "Link establisment failed"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_TRANSFER_FAILED:
|
||||||
|
return "Sync request failed"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_IDENTITY_RCVD:
|
||||||
|
return "Remote got no identity"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_ACCESS:
|
||||||
|
return "Node rejected request"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_FAILED:
|
||||||
|
return "Sync failed"
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||||
new_msgs = self.message_router.propagation_transfer_last_result
|
new_msgs = self.message_router.propagation_transfer_last_result
|
||||||
if new_msgs == 0:
|
if new_msgs == 0:
|
||||||
return "Done, no new messages"
|
return "Done, no new messages"
|
||||||
else:
|
else:
|
||||||
return "Downloaded "+str(new_msgs)+" new messages"
|
return "Downloaded "+str(new_msgs)+" new messages"
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_IDENTITY_RCVD:
|
|
||||||
return "Node did not receive identification"
|
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_ACCESS:
|
|
||||||
return "Node did not allow request"
|
|
||||||
else:
|
else:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
@@ -788,6 +811,11 @@ class NomadNetworkApp:
|
|||||||
else:
|
else:
|
||||||
self.node_name = self.config["node"]["node_name"]
|
self.node_name = self.config["node"]["node_name"]
|
||||||
|
|
||||||
|
if not "disable_propagation" in self.config["node"]:
|
||||||
|
self.disable_propagation = False
|
||||||
|
else:
|
||||||
|
self.disable_propagation = self.config["node"].as_bool("disable_propagation")
|
||||||
|
|
||||||
if not "announce_at_start" in self.config["node"]:
|
if not "announce_at_start" in self.config["node"]:
|
||||||
self.node_announce_at_start = False
|
self.node_announce_at_start = False
|
||||||
else:
|
else:
|
||||||
@@ -805,9 +833,27 @@ class NomadNetworkApp:
|
|||||||
if "pages_path" in self.config["node"]:
|
if "pages_path" in self.config["node"]:
|
||||||
self.pagespath = self.config["node"]["pages_path"]
|
self.pagespath = self.config["node"]["pages_path"]
|
||||||
|
|
||||||
|
if not "page_refresh_interval" in self.config["node"]:
|
||||||
|
self.page_refresh_interval = 0
|
||||||
|
else:
|
||||||
|
value = self.config["node"].as_int("page_refresh_interval")
|
||||||
|
if value < 0:
|
||||||
|
value = 0
|
||||||
|
self.page_refresh_interval = value
|
||||||
|
|
||||||
|
|
||||||
if "files_path" in self.config["node"]:
|
if "files_path" in self.config["node"]:
|
||||||
self.filespath = self.config["node"]["files_path"]
|
self.filespath = self.config["node"]["files_path"]
|
||||||
|
|
||||||
|
if not "file_refresh_interval" in self.config["node"]:
|
||||||
|
self.file_refresh_interval = 0
|
||||||
|
else:
|
||||||
|
value = self.config["node"].as_int("file_refresh_interval")
|
||||||
|
if value < 0:
|
||||||
|
value = 0
|
||||||
|
self.file_refresh_interval = value
|
||||||
|
|
||||||
|
|
||||||
if "prioritise_destinations" in self.config["node"]:
|
if "prioritise_destinations" in self.config["node"]:
|
||||||
self.prioritised_lxmf_destinations = self.config["node"].as_list("prioritise_destinations")
|
self.prioritised_lxmf_destinations = self.config["node"].as_list("prioritise_destinations")
|
||||||
else:
|
else:
|
||||||
@@ -1003,6 +1049,14 @@ announce_interval = 360
|
|||||||
# Whether to announce when the node starts.
|
# Whether to announce when the node starts.
|
||||||
announce_at_start = Yes
|
announce_at_start = Yes
|
||||||
|
|
||||||
|
# By default, when Nomad Network is hosting a
|
||||||
|
# node, it will also act as an LXMF propagation
|
||||||
|
# node. If there is already a large amount of
|
||||||
|
# propagation nodes on the network, or you
|
||||||
|
# simply want to run a pageserving-only node,
|
||||||
|
# you can disable running a propagation node.
|
||||||
|
# disable_propagation = False
|
||||||
|
|
||||||
# The maximum amount of storage to use for
|
# The maximum amount of storage to use for
|
||||||
# the LXMF Propagation Node message store,
|
# the LXMF Propagation Node message store,
|
||||||
# specified in megabytes. When this limit
|
# specified in megabytes. When this limit
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.3.9"
|
__version__ = "0.4.3"
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ class Browser:
|
|||||||
self.status = Browser.PATH_REQUESTED
|
self.status = Browser.PATH_REQUESTED
|
||||||
self.update_display()
|
self.update_display()
|
||||||
|
|
||||||
pr_time = time.time()
|
pr_time = time.time()+RNS.Transport.first_hop_timeout(self.destination_hash)
|
||||||
while not RNS.Transport.has_path(self.destination_hash):
|
while not RNS.Transport.has_path(self.destination_hash):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now > pr_time+self.timeout:
|
if now > pr_time+self.timeout:
|
||||||
@@ -770,7 +770,7 @@ class Browser:
|
|||||||
self.status = Browser.PATH_REQUESTED
|
self.status = Browser.PATH_REQUESTED
|
||||||
self.update_display()
|
self.update_display()
|
||||||
|
|
||||||
pr_time = time.time()
|
pr_time = time.time()+RNS.Transport.first_hop_timeout(self.destination_hash)
|
||||||
while not RNS.Transport.has_path(self.destination_hash):
|
while not RNS.Transport.has_path(self.destination_hash):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now > pr_time+self.timeout:
|
if now > pr_time+self.timeout:
|
||||||
@@ -976,7 +976,10 @@ class Browser:
|
|||||||
try:
|
try:
|
||||||
file_name = request_receipt.response[0]
|
file_name = request_receipt.response[0]
|
||||||
file_data = request_receipt.response[1]
|
file_data = request_receipt.response[1]
|
||||||
file_destination = self.app.downloads_path+"/"+file_name
|
file_destination_name = file_name.split("/")
|
||||||
|
file_destination_name = file_destination_name[len(file_destination_name)-1]
|
||||||
|
file_destination = self.app.downloads_path+"/"+file_destination_name
|
||||||
|
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
while os.path.isfile(file_destination):
|
while os.path.isfile(file_destination):
|
||||||
|
|||||||
@@ -894,7 +894,7 @@ class ConversationWidget(urwid.WidgetWrap):
|
|||||||
if allowed:
|
if allowed:
|
||||||
self.frame.contents["footer"] = (self.minimal_editor, None)
|
self.frame.contents["footer"] = (self.minimal_editor, None)
|
||||||
else:
|
else:
|
||||||
warning = urwid.AttrMap(urwid.Padding(urwid.Text("\n"+g["info"]+"\n\nYou cannot currently message this peer, since it's identity keys are not known.\n\nWait for an announce to arrive from the peer, or query the network for it.\n\nTo query the network, select this conversation in the conversation list, press Ctrl-E, and use the query button.\n", align="center")), "msg_header_caution")
|
warning = urwid.AttrMap(urwid.Padding(urwid.Text("\n"+g["info"]+"\n\nYou cannot currently message this peer, since it's identity keys are not known. The keys have been requested from the network and should arrive shortly, if available. Close this conversation and reopen it to try again.\n\nTo query the network manually, select this conversation in the conversation list, press Ctrl-E, and use the query button.\n", align="center")), "msg_header_caution")
|
||||||
self.frame.contents["footer"] = (warning, None)
|
self.frame.contents["footer"] = (warning, None)
|
||||||
|
|
||||||
def toggle_focus_area(self):
|
def toggle_focus_area(self):
|
||||||
@@ -974,7 +974,27 @@ class ConversationWidget(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def paper_message(self):
|
def paper_message_saved(self, path):
|
||||||
|
g = self.app.ui.glyphs
|
||||||
|
def dismiss_dialog(sender):
|
||||||
|
self.dialog_open = False
|
||||||
|
self.conversation_changed(None)
|
||||||
|
|
||||||
|
dialog = DialogLineBox(
|
||||||
|
urwid.Pile([
|
||||||
|
urwid.Text("The paper message was saved to:\n\n"+str(path)+"\n", align="center"),
|
||||||
|
urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
]), title=g["papermsg"].replace(" ", "")
|
||||||
|
)
|
||||||
|
dialog.delegate = self
|
||||||
|
bottom = self.messagelist
|
||||||
|
|
||||||
|
overlay = urwid.Overlay(dialog, bottom, align="center", width=60, valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
self.frame.contents["body"] = (overlay, self.frame.options())
|
||||||
|
self.frame.set_focus("body")
|
||||||
|
|
||||||
|
def print_paper_message_qr(self):
|
||||||
content = self.content_editor.get_edit_text()
|
content = self.content_editor.get_edit_text()
|
||||||
title = self.title_editor.get_edit_text()
|
title = self.title_editor.get_edit_text()
|
||||||
if not content == "":
|
if not content == "":
|
||||||
@@ -983,6 +1003,67 @@ class ConversationWidget(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
self.paper_message_failed()
|
self.paper_message_failed()
|
||||||
|
|
||||||
|
def save_paper_message_qr(self):
|
||||||
|
content = self.content_editor.get_edit_text()
|
||||||
|
title = self.title_editor.get_edit_text()
|
||||||
|
if not content == "":
|
||||||
|
output_result = self.conversation.paper_output(content, title, mode="save_qr")
|
||||||
|
if output_result != False:
|
||||||
|
self.clear_editor()
|
||||||
|
self.paper_message_saved(output_result)
|
||||||
|
else:
|
||||||
|
self.paper_message_failed()
|
||||||
|
|
||||||
|
def save_paper_message_uri(self):
|
||||||
|
content = self.content_editor.get_edit_text()
|
||||||
|
title = self.title_editor.get_edit_text()
|
||||||
|
if not content == "":
|
||||||
|
output_result = self.conversation.paper_output(content, title, mode="save_uri")
|
||||||
|
if output_result != False:
|
||||||
|
self.clear_editor()
|
||||||
|
self.paper_message_saved(output_result)
|
||||||
|
else:
|
||||||
|
self.paper_message_failed()
|
||||||
|
|
||||||
|
def paper_message(self):
|
||||||
|
def dismiss_dialog(sender):
|
||||||
|
self.dialog_open = False
|
||||||
|
self.conversation_changed(None)
|
||||||
|
|
||||||
|
def print_qr(sender):
|
||||||
|
dismiss_dialog(self)
|
||||||
|
self.print_paper_message_qr()
|
||||||
|
|
||||||
|
def save_qr(sender):
|
||||||
|
dismiss_dialog(self)
|
||||||
|
self.save_paper_message_qr()
|
||||||
|
|
||||||
|
def save_uri(sender):
|
||||||
|
dismiss_dialog(self)
|
||||||
|
self.save_paper_message_uri()
|
||||||
|
|
||||||
|
dialog = DialogLineBox(
|
||||||
|
urwid.Pile([
|
||||||
|
urwid.Text("Select the desired paper message output method.\nSaved files will be written to:\n\n"+str(self.app.downloads_path)+"\n", align="center"),
|
||||||
|
urwid.Columns([
|
||||||
|
("weight", 0.5, urwid.Button("Print QR", on_press=print_qr)),
|
||||||
|
("weight", 0.1, urwid.Text("")),
|
||||||
|
("weight", 0.5, urwid.Button("Save QR", on_press=save_qr)),
|
||||||
|
("weight", 0.1, urwid.Text("")),
|
||||||
|
("weight", 0.5, urwid.Button("Save URI", on_press=save_uri)),
|
||||||
|
("weight", 0.1, urwid.Text("")),
|
||||||
|
("weight", 0.5, urwid.Button("Cancel", on_press=dismiss_dialog))
|
||||||
|
])
|
||||||
|
]), title="Create Paper Message"
|
||||||
|
)
|
||||||
|
dialog.delegate = self
|
||||||
|
bottom = self.messagelist
|
||||||
|
|
||||||
|
overlay = urwid.Overlay(dialog, bottom, align="center", width=60, valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
self.frame.contents["body"] = (overlay, self.frame.options())
|
||||||
|
self.frame.set_focus("body")
|
||||||
|
|
||||||
def paper_message_failed(self):
|
def paper_message_failed(self):
|
||||||
def dismiss_dialog(sender):
|
def dismiss_dialog(sender):
|
||||||
self.dialog_open = False
|
self.dialog_open = False
|
||||||
|
|||||||
@@ -217,6 +217,15 @@ The different sections of the program has a number of keyboard shortcuts mapped,
|
|||||||
- Ctrl-W Close conversation
|
- Ctrl-W Close conversation
|
||||||
|
|
||||||
>>`!Network Window`!
|
>>`!Network Window`!
|
||||||
|
>>>Browser
|
||||||
|
- Ctrl-D Back
|
||||||
|
- Ctrl-F Forward
|
||||||
|
- Ctrl-R Reload page
|
||||||
|
- Ctrl-U Open URL entry dialog
|
||||||
|
- Ctrl-S Save connected node
|
||||||
|
- Ctrl-G Toggle fullscreen browser window
|
||||||
|
- Ctrl-W Disconnect from node
|
||||||
|
|
||||||
>>>Announce Stream
|
>>>Announce Stream
|
||||||
- Ctrl-L Switch to Known Nodes list
|
- Ctrl-L Switch to Known Nodes list
|
||||||
- Ctrl-X Delete selected announce
|
- Ctrl-X Delete selected announce
|
||||||
@@ -227,14 +236,10 @@ The different sections of the program has a number of keyboard shortcuts mapped,
|
|||||||
- Ctrl-X Delete selected node entry
|
- Ctrl-X Delete selected node entry
|
||||||
- Ctrl-P Display peered LXMF Propagation Nodes
|
- Ctrl-P Display peered LXMF Propagation Nodes
|
||||||
|
|
||||||
>>>Browser
|
>>>Peered LXMF Propagation Nodes
|
||||||
- Ctrl-D Back
|
- Ctrl-L Switch to Announce Stream or Known Nodes
|
||||||
- Ctrl-F Forward
|
- Ctrl-X Break peering with selected node entry
|
||||||
- Ctrl-R Reload page
|
- Ctrl-R Request immediate delivery sync of unhandled LXMs
|
||||||
- Ctrl-U Open URL entry dialog
|
|
||||||
- Ctrl-S Save connected node
|
|
||||||
- Ctrl-G Toggle fullscreen browser window
|
|
||||||
- Ctrl-W Disconnect from node
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
TOPIC_CONCEPTS = '''>Concepts and Terminology
|
TOPIC_CONCEPTS = '''>Concepts and Terminology
|
||||||
@@ -302,11 +307,13 @@ By default, no content is defined, apart from a short placeholder home page. To
|
|||||||
|
|
||||||
>>Distributed Message Store
|
>>Distributed Message Store
|
||||||
|
|
||||||
All nodes on the network automatically form a distributed message store that allows users to exchange messages, even when they are not available at the same time.
|
All nodes on the network will automatically participate in a distributed message store that allows users to exchange messages, even when they are not connected to the network at the same time.
|
||||||
|
|
||||||
When Nomad Network is configured to host a node, it also configures itself as an LXMF Propagation Node, and automatically discovers and peers with other propagation nodes on the network. This process is completely automatic and requires no configuration from the node operator.
|
When Nomad Network is configured to host a node, by default it also configures itself as an LXMF Propagation Node, and automatically discovers and peers with other propagation nodes on the network. This process is completely automatic and requires no configuration from the node operator.
|
||||||
|
|
||||||
To view LXMF Propagation nodes that are currently peered with your node, go to the `![ Network ]`! part of the program and press `!Ctrl-P`!.
|
If there is already an abundance of Propagation Nodes on the network, or the operator simply wishes to host a pageserving-only node, Propagation Node hosting can be disabled in the configuration file.
|
||||||
|
|
||||||
|
To view LXMF Propagation nodes that are currently peered with your node, go to the `![ Network ]`! part of the program and press `!Ctrl-P`!. In the list of peered Propagation Nodes, it is possible to break peering with a node by pressing `!Ctrl-X`!. It is also possible to request an immediate delivery sync of all unhandled messages for a node, by pressing `!Ctrl-R`!.
|
||||||
|
|
||||||
The distributed message store is resilient to intermittency, and will remain functional as long as at least one node remains on the network. Nodes that were offline for a time will automatically be synced up to date when they regain connectivity.
|
The distributed message store is resilient to intermittency, and will remain functional as long as at least one node remains on the network. Nodes that were offline for a time will automatically be synced up to date when they regain connectivity.
|
||||||
|
|
||||||
@@ -614,12 +621,30 @@ Determines how often, in minutes, your node is announced on the network. Default
|
|||||||
Determines where the node server will look for hosted pages. Must be a readable filesystem path.
|
Determines where the node server will look for hosted pages. Must be a readable filesystem path.
|
||||||
<
|
<
|
||||||
|
|
||||||
|
>>>
|
||||||
|
`!page_refresh_interval = 0`!
|
||||||
|
>>>>
|
||||||
|
Determines the interval in seconds for rescanning the hosted pages path. By default, this option is disabled, and the pages path will only be scanned on startup.
|
||||||
|
<
|
||||||
|
|
||||||
>>>
|
>>>
|
||||||
`!files_path = ~/.nomadnetwork/storage/files`!
|
`!files_path = ~/.nomadnetwork/storage/files`!
|
||||||
>>>>
|
>>>>
|
||||||
Determines where the node server will look for downloadable files. Must be a readable filesystem path.
|
Determines where the node server will look for downloadable files. Must be a readable filesystem path.
|
||||||
<
|
<
|
||||||
|
|
||||||
|
>>>
|
||||||
|
`!file_refresh_interval = 0`!
|
||||||
|
>>>>
|
||||||
|
Determines the interval in seconds for rescanning the hosted files path. By default, this option is disabled, and the files path will only be scanned on startup.
|
||||||
|
<
|
||||||
|
|
||||||
|
>>>
|
||||||
|
`!disable_propagation = no`!
|
||||||
|
>>>>
|
||||||
|
By default, when Nomad Network is hosting a node, it will also run an LXMF propagation node. If there is already a large amount of propagation nodes on the network, or you simply want to run a pageserving-only node, you can disable running a propagation node.
|
||||||
|
<
|
||||||
|
|
||||||
>>>
|
>>>
|
||||||
`!message_storage_limit = 2000`!
|
`!message_storage_limit = 2000`!
|
||||||
>>>>
|
>>>>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import RNS
|
|||||||
import urwid
|
import urwid
|
||||||
import nomadnet
|
import nomadnet
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from nomadnet.Directory import DirectoryEntry
|
from nomadnet.Directory import DirectoryEntry
|
||||||
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY
|
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY
|
||||||
@@ -480,6 +481,12 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
trust_level = self.app.directory.trust_level(source_hash)
|
trust_level = self.app.directory.trust_level(source_hash)
|
||||||
trust_str = ""
|
trust_str = ""
|
||||||
node_entry = self.app.directory.find(source_hash)
|
node_entry = self.app.directory.find(source_hash)
|
||||||
|
sort_str = self.app.directory.sort_rank(source_hash)
|
||||||
|
if sort_str == None:
|
||||||
|
sort_str = "None"
|
||||||
|
else:
|
||||||
|
sort_str = str(sort_str)
|
||||||
|
|
||||||
if node_entry == None:
|
if node_entry == None:
|
||||||
display_str = self.app.directory.simplest_display_str(source_hash)
|
display_str = self.app.directory.simplest_display_str(source_hash)
|
||||||
else:
|
else:
|
||||||
@@ -540,6 +547,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
r_trusted = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_selected)
|
r_trusted = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_selected)
|
||||||
|
|
||||||
e_name = urwid.Edit(caption="Name : ",edit_text=display_str)
|
e_name = urwid.Edit(caption="Name : ",edit_text=display_str)
|
||||||
|
e_sort = urwid.Edit(caption="Sort Rank : ",edit_text=sort_str)
|
||||||
|
|
||||||
node_ident = RNS.Identity.recall(source_hash)
|
node_ident = RNS.Identity.recall(source_hash)
|
||||||
op_hash = None
|
op_hash = None
|
||||||
@@ -604,8 +612,16 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
trust_level = DirectoryEntry.TRUSTED
|
trust_level = DirectoryEntry.TRUSTED
|
||||||
|
|
||||||
display_str = e_name.get_edit_text()
|
display_str = e_name.get_edit_text()
|
||||||
|
sort_rank = e_sort.get_edit_text()
|
||||||
|
try:
|
||||||
|
if int(sort_rank) >= 0:
|
||||||
|
sort_rank = int(sort_rank)
|
||||||
|
else:
|
||||||
|
sort_rank = None
|
||||||
|
except:
|
||||||
|
sort_rank = None
|
||||||
|
|
||||||
node_entry = DirectoryEntry(source_hash, display_name=display_str, trust_level=trust_level, hosts_node=True, identify_on_connect=connect_identify_checkbox.get_state())
|
node_entry = DirectoryEntry(source_hash, display_name=display_str, trust_level=trust_level, hosts_node=True, identify_on_connect=connect_identify_checkbox.get_state(), sort_rank=sort_rank)
|
||||||
self.app.directory.remember(node_entry)
|
self.app.directory.remember(node_entry)
|
||||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||||
|
|
||||||
@@ -626,6 +642,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
urwid.Text("Type : "+type_string, align="left"),
|
urwid.Text("Type : "+type_string, align="left"),
|
||||||
e_name,
|
e_name,
|
||||||
urwid.Text("Node Addr : "+addr_str, align="left"),
|
urwid.Text("Node Addr : "+addr_str, align="left"),
|
||||||
|
e_sort,
|
||||||
urwid.Divider(g["divider1"]),
|
urwid.Divider(g["divider1"]),
|
||||||
urwid.Text(lxmf_addr_str, align="center"),
|
urwid.Text(lxmf_addr_str, align="center"),
|
||||||
urwid.Divider(g["divider1"]),
|
urwid.Divider(g["divider1"]),
|
||||||
@@ -961,12 +978,12 @@ class NodeStorageStats(urwid.WidgetWrap):
|
|||||||
|
|
||||||
def update_stat(self):
|
def update_stat(self):
|
||||||
self.stat_string = "None"
|
self.stat_string = "None"
|
||||||
if self.app.node != None:
|
if self.app.node != None and not self.app.disable_propagation:
|
||||||
|
|
||||||
limit = self.app.message_router.message_storage_limit
|
limit = self.app.message_router.message_storage_limit
|
||||||
used = self.app.message_router.message_storage_size()
|
used = self.app.message_router.message_storage_size()
|
||||||
|
|
||||||
if limit != None:
|
if limit != None and used != None:
|
||||||
pct = round((used/limit)*100, 1)
|
pct = round((used/limit)*100, 1)
|
||||||
pct_str = str(pct)+"%, "
|
pct_str = str(pct)+"%, "
|
||||||
limit_str = " of "+RNS.prettysize(limit)
|
limit_str = " of "+RNS.prettysize(limit)
|
||||||
@@ -1297,12 +1314,35 @@ class NodeInfo(urwid.WidgetWrap):
|
|||||||
connect_button = urwid.Button("Browse", on_press=connect_query)
|
connect_button = urwid.Button("Browse", on_press=connect_query)
|
||||||
reset_button = urwid.Button("Rst Stats", on_press=stats_query)
|
reset_button = urwid.Button("Rst Stats", on_press=stats_query)
|
||||||
|
|
||||||
pile = urwid.Pile([
|
if not self.app.disable_propagation:
|
||||||
|
pile = urwid.Pile([
|
||||||
|
t_id,
|
||||||
|
e_name,
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
e_lxmf,
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
self.t_last_announce,
|
||||||
|
self.t_storage_stats,
|
||||||
|
self.t_active_links,
|
||||||
|
self.t_total_connections,
|
||||||
|
self.t_total_pages,
|
||||||
|
self.t_total_files,
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
urwid.Columns([
|
||||||
|
("weight", 5, urwid.Button("Back", on_press=show_peer_info)),
|
||||||
|
("weight", 0.5, urwid.Text("")),
|
||||||
|
("weight", 6, connect_button),
|
||||||
|
("weight", 0.5, urwid.Text("")),
|
||||||
|
("weight", 8, reset_button),
|
||||||
|
("weight", 0.5, urwid.Text("")),
|
||||||
|
("weight", 7, announce_button),
|
||||||
|
])
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
pile = urwid.Pile([
|
||||||
t_id,
|
t_id,
|
||||||
e_name,
|
e_name,
|
||||||
urwid.Divider(g["divider1"]),
|
urwid.Divider(g["divider1"]),
|
||||||
e_lxmf,
|
|
||||||
urwid.Divider(g["divider1"]),
|
|
||||||
self.t_last_announce,
|
self.t_last_announce,
|
||||||
self.t_storage_stats,
|
self.t_storage_stats,
|
||||||
self.t_active_links,
|
self.t_active_links,
|
||||||
@@ -1587,6 +1627,8 @@ class LXMFPeers(urwid.WidgetWrap):
|
|||||||
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
||||||
elif key == "ctrl x":
|
elif key == "ctrl x":
|
||||||
self.delete_selected_entry()
|
self.delete_selected_entry()
|
||||||
|
elif key == "ctrl r":
|
||||||
|
self.sync_selected_entry()
|
||||||
|
|
||||||
return super(LXMFPeers, self).keypress(size, key)
|
return super(LXMFPeers, self).keypress(size, key)
|
||||||
|
|
||||||
@@ -1602,6 +1644,43 @@ class LXMFPeers(urwid.WidgetWrap):
|
|||||||
self.delegate.reinit_lxmf_peers()
|
self.delegate.reinit_lxmf_peers()
|
||||||
self.delegate.show_peers()
|
self.delegate.show_peers()
|
||||||
|
|
||||||
|
def sync_selected_entry(self):
|
||||||
|
sync_grace = 10
|
||||||
|
si = self.ilb.get_selected_item()
|
||||||
|
if si != None:
|
||||||
|
destination_hash = si.original_widget.destination_hash
|
||||||
|
if destination_hash in self.app.message_router.peers:
|
||||||
|
peer = self.app.message_router.peers[destination_hash]
|
||||||
|
if time.time() > peer.last_sync_attempt+sync_grace:
|
||||||
|
peer.next_sync_attempt = time.time()-1
|
||||||
|
|
||||||
|
def job():
|
||||||
|
peer.sync()
|
||||||
|
threading.Thread(target=job, daemon=True).start()
|
||||||
|
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
def dismiss_dialog(sender):
|
||||||
|
self.close_list_dialogs()
|
||||||
|
|
||||||
|
dialog = ListDialogLineBox(
|
||||||
|
urwid.Pile([
|
||||||
|
urwid.Text("A delivery sync of all unhandled LXMs was manually requested for the selected node\n", align="center"),
|
||||||
|
urwid.Columns([("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("OK", on_press=dismiss_dialog))])
|
||||||
|
]), title="!"
|
||||||
|
)
|
||||||
|
dialog.delegate = self.delegate
|
||||||
|
bottom = self
|
||||||
|
|
||||||
|
overlay = urwid.Overlay(dialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
|
||||||
|
|
||||||
|
options = self.delegate.left_pile.options("weight", 1)
|
||||||
|
self.delegate.left_pile.contents[0] = (overlay, options)
|
||||||
|
|
||||||
|
|
||||||
|
def close_list_dialogs(self):
|
||||||
|
self.delegate.reinit_lxmf_peers()
|
||||||
|
self.delegate.show_peers()
|
||||||
|
|
||||||
def rebuild_widget_list(self):
|
def rebuild_widget_list(self):
|
||||||
self.peer_list = self.app.message_router.peers
|
self.peer_list = self.app.message_router.peers
|
||||||
@@ -1615,17 +1694,18 @@ class LXMFPeers(urwid.WidgetWrap):
|
|||||||
|
|
||||||
def make_peer_widgets(self):
|
def make_peer_widgets(self):
|
||||||
widget_list = []
|
widget_list = []
|
||||||
sorted_peers = sorted(self.peer_list, key=lambda pid: self.peer_list[pid].link_establishment_rate, reverse=True)
|
sorted_peers = sorted(self.peer_list, key=lambda pid: (self.app.directory.pn_trust_level(pid), self.peer_list[pid].link_establishment_rate), reverse=True)
|
||||||
for peer_id in sorted_peers:
|
for peer_id in sorted_peers:
|
||||||
peer = self.peer_list[peer_id]
|
peer = self.peer_list[peer_id]
|
||||||
pe = LXMFPeerEntry(self.app, peer, self)
|
trust_level = self.app.directory.pn_trust_level(peer_id)
|
||||||
|
pe = LXMFPeerEntry(self.app, peer, self, trust_level)
|
||||||
pe.destination_hash = peer.destination_hash
|
pe.destination_hash = peer.destination_hash
|
||||||
widget_list.append(pe)
|
widget_list.append(pe)
|
||||||
|
|
||||||
return widget_list
|
return widget_list
|
||||||
|
|
||||||
class LXMFPeerEntry(urwid.WidgetWrap):
|
class LXMFPeerEntry(urwid.WidgetWrap):
|
||||||
def __init__(self, app, peer, delegate):
|
def __init__(self, app, peer, delegate, trust_level):
|
||||||
destination_hash = peer.destination_hash
|
destination_hash = peer.destination_hash
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
@@ -1647,16 +1727,18 @@ class LXMFPeerEntry(urwid.WidgetWrap):
|
|||||||
if hasattr(peer, "alive"):
|
if hasattr(peer, "alive"):
|
||||||
if peer.alive:
|
if peer.alive:
|
||||||
alive_string = "Available"
|
alive_string = "Available"
|
||||||
style = "list_normal"
|
if trust_level == DirectoryEntry.TRUSTED:
|
||||||
focus_style = "list_focus"
|
style = "list_trusted"
|
||||||
|
focus_style = "list_focus_trusted"
|
||||||
|
else:
|
||||||
|
style = "list_normal"
|
||||||
|
focus_style = "list_focus"
|
||||||
else:
|
else:
|
||||||
alive_string = "Unresponsive"
|
alive_string = "Unresponsive"
|
||||||
style = "list_unresponsive"
|
style = "list_unresponsive"
|
||||||
focus_style = "list_focus_unresponsive"
|
focus_style = "list_focus_unresponsive"
|
||||||
|
|
||||||
widget = ListEntry(sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs, "+RNS.prettysize(peer.link_establishment_rate/8, "b")+"/s LER")
|
widget = ListEntry(sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))+"\n "+str(len(peer.unhandled_messages))+" unhandled LXMs, "+RNS.prettysize(peer.link_establishment_rate/8, "b")+"/s LER")
|
||||||
# urwid.connect_signal(widget, "click", delegate.connect_node, node)
|
|
||||||
|
|
||||||
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||||
self.display_widget.destination_hash = destination_hash
|
self.display_widget.destination_hash = destination_hash
|
||||||
urwid.WidgetWrap.__init__(self, self.display_widget)
|
urwid.WidgetWrap.__init__(self, self.display_widget)
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -30,6 +30,6 @@ setuptools.setup(
|
|||||||
entry_points= {
|
entry_points= {
|
||||||
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
||||||
},
|
},
|
||||||
install_requires=["rns>=0.6.2", "lxmf>=0.3.4", "urwid==2.1.2", "qrcode"],
|
install_requires=["rns>=0.6.4", "lxmf>=0.3.8", "urwid==2.1.2", "qrcode"],
|
||||||
python_requires=">=3.6",
|
python_requires=">=3.6",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user