diff --git a/nomadnet/Conversation.py b/nomadnet/Conversation.py index a6d3b59..7cb2a8f 100644 --- a/nomadnet/Conversation.py +++ b/nomadnet/Conversation.py @@ -196,9 +196,18 @@ class Conversation: if self.send_destination: dest = self.send_destination source = self.app.lxmf_destination - lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=LXMF.LXMessage.DIRECT) + desired_method = LXMF.LXMessage.DIRECT + if self.app.directory.preferred_delivery(dest.hash) == DirectoryEntry.PROPAGATED: + if self.app.message_router.get_outbound_propagation_node() != None: + desired_method = LXMF.LXMessage.PROPAGATED + + lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method) lxm.register_delivery_callback(self.message_notification) lxm.register_failed_callback(self.message_notification) + + if self.app.message_router.get_outbound_propagation_node() != None: + lxm.try_propagation_on_fail = self.app.try_propagation_on_fail + self.app.message_router.handle_outbound(lxm) message_path = Conversation.ingest(lxm, self.app, originator=True) @@ -210,7 +219,16 @@ class Conversation: return False def message_notification(self, message): - message_path = Conversation.ingest(message, self.app, originator=True) + if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail: + RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE) + message.try_propagation_on_fail = None + message.delivery_attempts = 0 + del message.next_delivery_attempt + message.packed = None + message.desired_method = LXMF.LXMessage.PROPAGATED + self.app.message_router.handle_outbound(message) + else: + message_path = Conversation.ingest(message, self.app, originator=True) def __str__(self): string = self.source_hash diff --git a/nomadnet/Directory.py b/nomadnet/Directory.py index 6468f04..f64b4f7 100644 --- a/nomadnet/Directory.py +++ b/nomadnet/Directory.py @@ -17,6 +17,7 @@ class Directory: associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity) app.directory.node_announce_received(destination_hash, app_data, associated_peer) + app.autoselect_propagation_node() def __init__(self, app): @@ -31,7 +32,7 @@ class Directory: packed_list = [] for source_hash in self.directory_entries: e = self.directory_entries[source_hash] - packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node)) + packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node, e.preferred_delivery)) directory = { "entry_list": packed_list, @@ -59,7 +60,12 @@ class Directory: else: hosts_node = False - entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node) + if len(e) > 4: + preferred_delivery = e[4] + else: + preferred_delivery = None + + entries[e[0]] = DirectoryEntry(e[0], e[1], e[2], hosts_node, preferred_delivery=preferred_delivery) self.directory_entries = entries @@ -130,6 +136,12 @@ class Directory: else: return DirectoryEntry.UNKNOWN + def preferred_delivery(self, source_hash): + if source_hash in self.directory_entries: + return self.directory_entries[source_hash].preferred_delivery + else: + return DirectoryEntry.DIRECT + def remember(self, entry): self.directory_entries[entry.source_hash] = entry @@ -189,13 +201,21 @@ class DirectoryEntry: UNKNOWN = 0x02 TRUSTED = 0xFF - def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False): + DIRECT = 0x01 + PROPAGATED = 0x02 + + def __init__(self, source_hash, display_name=None, trust_level=UNKNOWN, hosts_node=False, preferred_delivery=None): if len(source_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8: self.source_hash = source_hash self.display_name = display_name if display_name == None: display_name = source_hash + if preferred_delivery == None: + self.preferred_delivery = DirectoryEntry.DIRECT + else: + self.preferred_delivery = preferred_delivery + self.trust_level = trust_level self.hosts_node = hosts_node else: diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py index 862b50c..c55719f 100644 --- a/nomadnet/NomadNetworkApp.py +++ b/nomadnet/NomadNetworkApp.py @@ -6,6 +6,8 @@ import RNS import LXMF import nomadnet +from nomadnet.Directory import DirectoryEntry + import RNS.vendor.umsgpack as msgpack from ._version import __version__ @@ -59,7 +61,8 @@ class NomadNetworkApp: self.firstrun = False - self.peer_announce_at_start = True + self.peer_announce_at_start = True + self.try_propagation_on_fail = True if not os.path.isdir(self.storagepath): os.makedirs(self.storagepath) @@ -154,9 +157,9 @@ class NomadNetworkApp: nomadnet.panic() - atexit.register(self.exit_handler) + self.directory = nomadnet.Directory(self) - self.message_router = LXMF.LXMRouter() + self.message_router = LXMF.LXMRouter(identity = self.identity, autopeer = True) self.message_router.register_delivery_callback(self.lxmf_delivery) self.lxmf_destination = self.message_router.register_delivery_identity(self.identity, display_name=self.peer_settings["display_name"]) @@ -171,9 +174,9 @@ class NomadNetworkApp: RNS.log("LXMF Router ready to receive on: "+RNS.prettyhexrep(self.lxmf_destination.hash)) - self.directory = nomadnet.Directory(self) - if self.enable_node: + self.message_router.enable_propagation(self.storagepath) + RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash)) self.node = nomadnet.Node(self) else: self.node = None @@ -181,9 +184,13 @@ class NomadNetworkApp: RNS.Transport.register_announce_handler(nomadnet.Conversation) RNS.Transport.register_announce_handler(nomadnet.Directory) + self.autoselect_propagation_node() + if self.peer_announce_at_start: self.announce_now() + atexit.register(self.exit_handler) + nomadnet.ui.spawn(self.uimode) def set_display_name(self, display_name): @@ -202,6 +209,30 @@ class NomadNetworkApp: self.peer_settings["last_announce"] = time.time() self.save_peer_settings() + def autoselect_propagation_node(self): + nodes = self.directory.known_nodes() + trusted_nodes = [] + + selected_node = None + best_hops = RNS.Transport.PATHFINDER_M+1 + + for node in nodes: + if node.trust_level == DirectoryEntry.TRUSTED: + hops = RNS.Transport.hops_to(node.source_hash) + + if hops < best_hops: + best_hops = hops + selected_node = node + + if selected_node == None: + RNS.log("Could not autoselect a prepagation node! LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING) + else: + node_identity = RNS.Identity.recall(selected_node.source_hash) + propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity) + RNS.log("Selecting "+selected_node.display_name+" "+RNS.prettyhexrep(propagation_hash)+" as default LXMF propagation node", RNS.LOG_INFO) + self.message_router.set_outbound_propagation_node(propagation_hash) + + def save_peer_settings(self): file = open(self.peersettingspath, "wb") file.write(msgpack.packb(self.peer_settings)) @@ -286,6 +317,10 @@ class NomadNetworkApp: value = self.config["client"].as_bool(option) self.peer_announce_at_start = value + if option == "try_propagation_on_send_fail": + value = self.config["client"].as_bool(option) + self.try_propagation_on_fail = value + if option == "user_interface": value = value.lower() if value == "none": @@ -430,6 +465,12 @@ downloads_path = ~/Downloads # to let other peers reach it immediately. announce_at_start = yes +# By default, the client will try to deliver a +# message via the LXMF propagation network, if +# a direct delivery to the recipient is not +# possible. +try_propagation_on_send_fail = yes + [textui] # Amount of time to show intro screen diff --git a/nomadnet/_version.py b/nomadnet/_version.py index d1f2e39..48fef32 100644 --- a/nomadnet/_version.py +++ b/nomadnet/_version.py @@ -1 +1 @@ -__version__ = "0.1.1" \ No newline at end of file +__version__ = "0.1.2" \ No newline at end of file diff --git a/nomadnet/ui/TextUI.py b/nomadnet/ui/TextUI.py index 9b499af..3ace2c0 100644 --- a/nomadnet/ui/TextUI.py +++ b/nomadnet/ui/TextUI.py @@ -117,9 +117,10 @@ GLYPHS = { ("node", "[N]", "\u24c3 ", "\uf502"), ("page", "", "\u25a4", "\uf719 "), ("speed", "", "\u25F7", "\uf9c4"), - ("decoration_menu", " +", " +", " \uf93a"), - ("unread_menu", " !", " \u2709", urm_char), + ("decoration_menu", " +", " +", " \uf93a"), + ("unread_menu", " !", " \u2709", urm_char), ("globe", "", "", "\uf484"), + ("sent", "/\\", "\u2191", "\ufbf4") } class TextUI: diff --git a/nomadnet/ui/textui/Conversations.py b/nomadnet/ui/textui/Conversations.py index 6d5ac1c..6cb0123 100644 --- a/nomadnet/ui/textui/Conversations.py +++ b/nomadnet/ui/textui/Conversations.py @@ -140,9 +140,12 @@ class ConversationsDisplay(): selected_id_widget = t_id - untrusted_selected = False - unknown_selected = True - trusted_selected = False + untrusted_selected = False + unknown_selected = True + trusted_selected = False + + direct_selected = True + propagated_selected = False try: if self.app.directory.find(bytes.fromhex(source_hash_text)): @@ -159,6 +162,11 @@ class ConversationsDisplay(): untrusted_selected = False unknown_selected = False trusted_selected = True + + if self.app.directory.preferred_delivery(bytes.fromhex(source_hash_text)) == DirectoryEntry.PROPAGATED: + direct_selected = False + propagated_selected = True + except Exception as e: pass @@ -167,6 +175,10 @@ class ConversationsDisplay(): r_unknown = urwid.RadioButton(trust_button_group, "Unknown", state=unknown_selected) r_trusted = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_selected) + method_button_group = [] + r_direct = urwid.RadioButton(method_button_group, "Deliver directly", state=direct_selected) + r_propagated = urwid.RadioButton(method_button_group, "Use propagation nodes", state=propagated_selected) + def dismiss_dialog(sender): self.update_conversation_list() self.dialog_open = False @@ -181,7 +193,11 @@ class ConversationsDisplay(): elif r_trusted.state == True: trust_level = DirectoryEntry.TRUSTED - entry = DirectoryEntry(source_hash, display_name, trust_level) + delivery = DirectoryEntry.DIRECT + if r_propagated.state == True: + delivery = DirectoryEntry.PROPAGATED + + entry = DirectoryEntry(source_hash, display_name, trust_level, preferred_delivery=delivery) self.app.directory.remember(entry) self.update_conversation_list() self.dialog_open = False @@ -216,6 +232,9 @@ class ConversationsDisplay(): r_untrusted, r_unknown, r_trusted, + urwid.Divider(g["divider1"]), + r_direct, + r_propagated, known_section, urwid.Columns([("weight", 0.45, urwid.Button("Save", on_press=confirmed)), ("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("Back", on_press=dismiss_dialog))]) ]) @@ -718,6 +737,9 @@ class LXMessageWidget(urwid.WidgetWrap): elif message.lxm.state == LXMF.LXMessage.FAILED: header_style = "msg_header_failed" title_string = g["cross"]+" "+title_string + elif message.lxm.state == LXMF.LXMessage.SENT: + header_style = "msg_header_sent" + title_string = g["sent"]+" "+title_string else: header_style = "msg_header_sent" title_string = g["arrow_r"]+" "+title_string diff --git a/nomadnet/vendor/quotes.py b/nomadnet/vendor/quotes.py index 90dbecd..ee142f7 100644 --- a/nomadnet/vendor/quotes.py +++ b/nomadnet/vendor/quotes.py @@ -1,5 +1,6 @@ quotes = [ ("I want the wisdom that wise men revere. I want more.", "Faithless"), ("That's enough entropy for you my friend", "Unknown"), - ("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier") + ("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier"), + ("The landscape of the future is set, but how one will march across it is not determined", "Terence McKenna") ] \ No newline at end of file diff --git a/setup.py b/setup.py index 0d89182..5c967b5 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,6 @@ setuptools.setup( entry_points= { 'console_scripts': ['nomadnet=nomadnet.nomadnet:main'] }, - install_requires=['rns>=0.2.6', 'lxmf>=0.0.9', 'urwid>=2.1.2'], + install_requires=['rns>=0.2.7', 'lxmf>=0.1.0', 'urwid>=2.1.2'], python_requires='>=3.6', ) \ No newline at end of file