mirror of
https://github.com/markqvist/NomadNet.git
synced 2024-10-01 01:26:07 -04:00
Implemented basic LXMF propagation support and controls
This commit is contained in:
parent
7043f33dd8
commit
df7692d7c6
@ -196,9 +196,18 @@ class Conversation:
|
|||||||
if self.send_destination:
|
if self.send_destination:
|
||||||
dest = self.send_destination
|
dest = self.send_destination
|
||||||
source = self.app.lxmf_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_delivery_callback(self.message_notification)
|
||||||
lxm.register_failed_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)
|
self.app.message_router.handle_outbound(lxm)
|
||||||
|
|
||||||
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
message_path = Conversation.ingest(lxm, self.app, originator=True)
|
||||||
@ -210,6 +219,15 @@ class Conversation:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def message_notification(self, message):
|
def message_notification(self, message):
|
||||||
|
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)
|
message_path = Conversation.ingest(message, self.app, originator=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -17,6 +17,7 @@ class Directory:
|
|||||||
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
|
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.directory.node_announce_received(destination_hash, app_data, associated_peer)
|
||||||
|
app.autoselect_propagation_node()
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
@ -31,7 +32,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))
|
packed_list.append((e.source_hash, e.display_name, e.trust_level, e.hosts_node, e.preferred_delivery))
|
||||||
|
|
||||||
directory = {
|
directory = {
|
||||||
"entry_list": packed_list,
|
"entry_list": packed_list,
|
||||||
@ -59,7 +60,12 @@ class Directory:
|
|||||||
else:
|
else:
|
||||||
hosts_node = False
|
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
|
self.directory_entries = entries
|
||||||
|
|
||||||
@ -130,6 +136,12 @@ class Directory:
|
|||||||
else:
|
else:
|
||||||
return DirectoryEntry.UNKNOWN
|
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):
|
def remember(self, entry):
|
||||||
self.directory_entries[entry.source_hash] = entry
|
self.directory_entries[entry.source_hash] = entry
|
||||||
|
|
||||||
@ -189,13 +201,21 @@ class DirectoryEntry:
|
|||||||
UNKNOWN = 0x02
|
UNKNOWN = 0x02
|
||||||
TRUSTED = 0xFF
|
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:
|
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
|
||||||
if display_name == None:
|
if display_name == None:
|
||||||
display_name = source_hash
|
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.trust_level = trust_level
|
||||||
self.hosts_node = hosts_node
|
self.hosts_node = hosts_node
|
||||||
else:
|
else:
|
||||||
|
@ -6,6 +6,8 @@ import RNS
|
|||||||
import LXMF
|
import LXMF
|
||||||
import nomadnet
|
import nomadnet
|
||||||
|
|
||||||
|
from nomadnet.Directory import DirectoryEntry
|
||||||
|
|
||||||
import RNS.vendor.umsgpack as msgpack
|
import RNS.vendor.umsgpack as msgpack
|
||||||
|
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
@ -60,6 +62,7 @@ class NomadNetworkApp:
|
|||||||
self.firstrun = False
|
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):
|
if not os.path.isdir(self.storagepath):
|
||||||
os.makedirs(self.storagepath)
|
os.makedirs(self.storagepath)
|
||||||
@ -154,9 +157,9 @@ class NomadNetworkApp:
|
|||||||
nomadnet.panic()
|
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.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"])
|
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))
|
RNS.log("LXMF Router ready to receive on: "+RNS.prettyhexrep(self.lxmf_destination.hash))
|
||||||
|
|
||||||
self.directory = nomadnet.Directory(self)
|
|
||||||
|
|
||||||
if self.enable_node:
|
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)
|
self.node = nomadnet.Node(self)
|
||||||
else:
|
else:
|
||||||
self.node = None
|
self.node = None
|
||||||
@ -181,9 +184,13 @@ class NomadNetworkApp:
|
|||||||
RNS.Transport.register_announce_handler(nomadnet.Conversation)
|
RNS.Transport.register_announce_handler(nomadnet.Conversation)
|
||||||
RNS.Transport.register_announce_handler(nomadnet.Directory)
|
RNS.Transport.register_announce_handler(nomadnet.Directory)
|
||||||
|
|
||||||
|
self.autoselect_propagation_node()
|
||||||
|
|
||||||
if self.peer_announce_at_start:
|
if self.peer_announce_at_start:
|
||||||
self.announce_now()
|
self.announce_now()
|
||||||
|
|
||||||
|
atexit.register(self.exit_handler)
|
||||||
|
|
||||||
nomadnet.ui.spawn(self.uimode)
|
nomadnet.ui.spawn(self.uimode)
|
||||||
|
|
||||||
def set_display_name(self, display_name):
|
def set_display_name(self, display_name):
|
||||||
@ -202,6 +209,30 @@ class NomadNetworkApp:
|
|||||||
self.peer_settings["last_announce"] = time.time()
|
self.peer_settings["last_announce"] = time.time()
|
||||||
self.save_peer_settings()
|
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):
|
def save_peer_settings(self):
|
||||||
file = open(self.peersettingspath, "wb")
|
file = open(self.peersettingspath, "wb")
|
||||||
file.write(msgpack.packb(self.peer_settings))
|
file.write(msgpack.packb(self.peer_settings))
|
||||||
@ -286,6 +317,10 @@ class NomadNetworkApp:
|
|||||||
value = self.config["client"].as_bool(option)
|
value = self.config["client"].as_bool(option)
|
||||||
self.peer_announce_at_start = value
|
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":
|
if option == "user_interface":
|
||||||
value = value.lower()
|
value = value.lower()
|
||||||
if value == "none":
|
if value == "none":
|
||||||
@ -430,6 +465,12 @@ downloads_path = ~/Downloads
|
|||||||
# to let other peers reach it immediately.
|
# to let other peers reach it immediately.
|
||||||
announce_at_start = yes
|
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]
|
[textui]
|
||||||
|
|
||||||
# Amount of time to show intro screen
|
# Amount of time to show intro screen
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = "0.1.1"
|
__version__ = "0.1.2"
|
@ -120,6 +120,7 @@ GLYPHS = {
|
|||||||
("decoration_menu", " +", " +", " \uf93a"),
|
("decoration_menu", " +", " +", " \uf93a"),
|
||||||
("unread_menu", " !", " \u2709", urm_char),
|
("unread_menu", " !", " \u2709", urm_char),
|
||||||
("globe", "", "", "\uf484"),
|
("globe", "", "", "\uf484"),
|
||||||
|
("sent", "/\\", "\u2191", "\ufbf4")
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextUI:
|
class TextUI:
|
||||||
|
@ -144,6 +144,9 @@ class ConversationsDisplay():
|
|||||||
unknown_selected = True
|
unknown_selected = True
|
||||||
trusted_selected = False
|
trusted_selected = False
|
||||||
|
|
||||||
|
direct_selected = True
|
||||||
|
propagated_selected = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.app.directory.find(bytes.fromhex(source_hash_text)):
|
if self.app.directory.find(bytes.fromhex(source_hash_text)):
|
||||||
trust_level = self.app.directory.trust_level(bytes.fromhex(source_hash_text))
|
trust_level = self.app.directory.trust_level(bytes.fromhex(source_hash_text))
|
||||||
@ -159,6 +162,11 @@ class ConversationsDisplay():
|
|||||||
untrusted_selected = False
|
untrusted_selected = False
|
||||||
unknown_selected = False
|
unknown_selected = False
|
||||||
trusted_selected = True
|
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:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -167,6 +175,10 @@ class ConversationsDisplay():
|
|||||||
r_unknown = urwid.RadioButton(trust_button_group, "Unknown", state=unknown_selected)
|
r_unknown = urwid.RadioButton(trust_button_group, "Unknown", state=unknown_selected)
|
||||||
r_trusted = urwid.RadioButton(trust_button_group, "Trusted", state=trusted_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):
|
def dismiss_dialog(sender):
|
||||||
self.update_conversation_list()
|
self.update_conversation_list()
|
||||||
self.dialog_open = False
|
self.dialog_open = False
|
||||||
@ -181,7 +193,11 @@ class ConversationsDisplay():
|
|||||||
elif r_trusted.state == True:
|
elif r_trusted.state == True:
|
||||||
trust_level = DirectoryEntry.TRUSTED
|
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.app.directory.remember(entry)
|
||||||
self.update_conversation_list()
|
self.update_conversation_list()
|
||||||
self.dialog_open = False
|
self.dialog_open = False
|
||||||
@ -216,6 +232,9 @@ class ConversationsDisplay():
|
|||||||
r_untrusted,
|
r_untrusted,
|
||||||
r_unknown,
|
r_unknown,
|
||||||
r_trusted,
|
r_trusted,
|
||||||
|
urwid.Divider(g["divider1"]),
|
||||||
|
r_direct,
|
||||||
|
r_propagated,
|
||||||
known_section,
|
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))])
|
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:
|
elif message.lxm.state == LXMF.LXMessage.FAILED:
|
||||||
header_style = "msg_header_failed"
|
header_style = "msg_header_failed"
|
||||||
title_string = g["cross"]+" "+title_string
|
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:
|
else:
|
||||||
header_style = "msg_header_sent"
|
header_style = "msg_header_sent"
|
||||||
title_string = g["arrow_r"]+" "+title_string
|
title_string = g["arrow_r"]+" "+title_string
|
||||||
|
3
nomadnet/vendor/quotes.py
vendored
3
nomadnet/vendor/quotes.py
vendored
@ -1,5 +1,6 @@
|
|||||||
quotes = [
|
quotes = [
|
||||||
("I want the wisdom that wise men revere. I want more.", "Faithless"),
|
("I want the wisdom that wise men revere. I want more.", "Faithless"),
|
||||||
("That's enough entropy for you my friend", "Unknown"),
|
("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")
|
||||||
]
|
]
|
2
setup.py
2
setup.py
@ -23,6 +23,6 @@ setuptools.setup(
|
|||||||
entry_points= {
|
entry_points= {
|
||||||
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
|
'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',
|
python_requires='>=3.6',
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user