Compare commits

...

24 Commits

Author SHA1 Message Date
Mark Qvist
aa4feeb29c Updated version
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-04-15 20:24:48 +02:00
Mark Qvist
ad985a2552 Updated dependencies 2025-04-15 20:23:24 +02:00
Mark Qvist
42fdc30cf8 Cleanup configobj
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-04-08 02:05:02 +02:00
Mark Qvist
e79689010e Fixed directory sorting bug 2025-04-08 02:03:34 +02:00
Mark Qvist
e4f57d6260 Updated dependencies 2025-03-31 15:03:44 +02:00
Mark Qvist
03d1b22b8f Updated dependencies
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-02-18 21:20:50 +01:00
Mark Qvist
f8775adbab Increased announce stream length 2025-02-18 20:27:33 +01:00
markqvist
c0dac0eadb
Merge pull request #68 from byte-diver/network_tabs
Add network tabs
2025-02-18 20:10:40 +01:00
byte-diver
c198d516da cleaning and sort 2025-02-07 00:23:43 +01:00
byte-diver
d9886980fa fix docker, add tabs 2025-02-07 00:10:44 +01:00
Mark Qvist
7ced4c3659 Fixed missing attribute check
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-01-29 14:45:19 +01:00
Mark Qvist
c3c8f99131 Updated logging
Some checks are pending
Create and publish a Docker image / build-and-push-image (push) Waiting to run
2025-01-29 01:41:25 +01:00
Mark Qvist
9f00ffbae6 Updated version 2025-01-29 01:25:33 +01:00
Mark Qvist
a26edd21db Added acceptance rate to PN peer stats 2025-01-29 01:24:29 +01:00
Mark Qvist
46f3a4127c Updated dependencies 2025-01-29 01:24:08 +01:00
Mark Qvist
eeb15dcb43 Use lock on announce stream updates 2025-01-28 23:04:22 +01:00
Mark Qvist
9ef34fc774 Maintain PN list position 2025-01-28 22:45:32 +01:00
Mark Qvist
704a4ea828 Updated issue template
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-01-27 10:26:55 +01:00
Mark Qvist
5f31aeb3c1 Fixed missing none check 2025-01-27 10:26:39 +01:00
Mark Qvist
f000073594 Updated versions
Some checks failed
Create and publish a Docker image / build-and-push-image (push) Has been cancelled
2025-01-22 12:06:05 +01:00
Mark Qvist
e332be6211 Added ability configure max_peers and static_peers 2025-01-22 12:05:08 +01:00
Mark Qvist
9ee052a122 Sort PNs according to STR 2025-01-22 12:04:34 +01:00
Mark Qvist
11fda51017 Updated guide 2025-01-22 12:03:49 +01:00
Mark Qvist
d5cf34f9d9 Get PN message counts faster
Some checks are pending
Create and publish a Docker image / build-and-push-image (push) Waiting to run
2025-01-21 17:01:06 +01:00
9 changed files with 216 additions and 2603 deletions

View File

@ -12,7 +12,7 @@ Before creating a bug report on this issue tracker, you **must** read the [Contr
- The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**. - The issue tracker is used by developers of this project. **Do not use it to ask general questions, or for support requests**.
- Ideas and feature requests can be made on the [Discussions](https://github.com/markqvist/Reticulum/discussions). **Only** feature requests accepted by maintainers and developers are tracked and included on the issue tracker. **Do not post feature requests here**. - Ideas and feature requests can be made on the [Discussions](https://github.com/markqvist/Reticulum/discussions). **Only** feature requests accepted by maintainers and developers are tracked and included on the issue tracker. **Do not post feature requests here**.
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), delete this section from your bug report. - After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), **delete this section only** (*"Read the Contribution Guidelines"*) from your bug report, **and fill in all the other sections**.
**Describe the Bug** **Describe the Bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.

View File

@ -16,10 +16,6 @@ ENV PATH="/home/myuser/.local/bin:${PATH}"
################### BEGIN NomadNet ########################################### ################### BEGIN NomadNet ###########################################
COPY --chown=myuser:myuser requirements.txt requirements.txt
RUN pip install --user -r requirements.txt
COPY --chown=myuser:myuser . . COPY --chown=myuser:myuser . .

View File

@ -3,6 +3,7 @@ import RNS
import LXMF import LXMF
import time import time
import nomadnet import nomadnet
import threading
import RNS.vendor.umsgpack as msgpack import RNS.vendor.umsgpack as msgpack
class PNAnnounceHandler: class PNAnnounceHandler:
@ -29,7 +30,7 @@ class PNAnnounceHandler:
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG) RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
class Directory: class Directory:
ANNOUNCE_STREAM_MAXLENGTH = 64 ANNOUNCE_STREAM_MAXLENGTH = 256
aspect_filter = "nomadnetwork.node" aspect_filter = "nomadnetwork.node"
@staticmethod @staticmethod
@ -55,6 +56,7 @@ class Directory:
self.directory_entries = {} self.directory_entries = {}
self.announce_stream = [] self.announce_stream = []
self.app = app self.app = app
self.announce_lock = threading.Lock()
self.load_from_disk() self.load_from_disk()
self.pn_announce_handler = PNAnnounceHandler(self) self.pn_announce_handler = PNAnnounceHandler(self)
@ -124,90 +126,94 @@ class Directory:
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)
def lxmf_announce_received(self, source_hash, app_data): def lxmf_announce_received(self, source_hash, app_data):
if app_data != None: with self.announce_lock:
if self.app.compact_stream: if app_data != None:
try: if self.app.compact_stream:
remove_announces = [] try:
for announce in self.announce_stream: remove_announces = []
if announce[1] == source_hash: for announce in self.announce_stream:
remove_announces.append(announce) if announce[1] == source_hash:
remove_announces.append(announce)
for a in remove_announces: for a in remove_announces:
self.announce_stream.remove(a) self.announce_stream.remove(a)
except Exception as e: except Exception as e:
RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR) RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR)
timestamp = time.time() timestamp = time.time()
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "peer")) self.announce_stream.insert(0, (timestamp, source_hash, app_data, "peer"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH: while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
self.announce_stream.pop() self.announce_stream.pop()
if hasattr(self.app.ui, "main_display"): if hasattr(self.app, "ui") and self.app.ui != None:
self.app.ui.main_display.sub_displays.network_display.directory_change_callback() if hasattr(self.app.ui, "main_display"):
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
def node_announce_received(self, source_hash, app_data, associated_peer): def node_announce_received(self, source_hash, app_data, associated_peer):
if app_data != None: with self.announce_lock:
if self.app.compact_stream: if app_data != None:
try: if self.app.compact_stream:
remove_announces = [] try:
for announce in self.announce_stream: remove_announces = []
if announce[1] == source_hash: for announce in self.announce_stream:
remove_announces.append(announce) if announce[1] == source_hash:
remove_announces.append(announce)
for a in remove_announces: for a in remove_announces:
self.announce_stream.remove(a) self.announce_stream.remove(a)
except Exception as e:
RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR)
timestamp = time.time()
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "node"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
self.announce_stream.pop()
if self.trust_level(associated_peer) == DirectoryEntry.TRUSTED:
existing_entry = self.find(source_hash)
if not existing_entry:
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
self.remember(node_entry)
except Exception as e: if hasattr(self.app.ui, "main_display"):
RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR) self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
timestamp = time.time()
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "node"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
self.announce_stream.pop()
if self.trust_level(associated_peer) == DirectoryEntry.TRUSTED:
existing_entry = self.find(source_hash)
if not existing_entry:
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
self.remember(node_entry)
if hasattr(self.app.ui, "main_display"):
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
def pn_announce_received(self, source_hash, app_data, associated_peer, associated_node): def pn_announce_received(self, source_hash, app_data, associated_peer, associated_node):
found_node = None with self.announce_lock:
for sh in self.directory_entries: found_node = None
if sh == associated_node: for sh in self.directory_entries:
found_node = True if sh == associated_node:
break found_node = True
break
for e in self.announce_stream: for e in self.announce_stream:
if e[1] == associated_node: if e[1] == associated_node:
found_node = True found_node = True
break break
if not found_node: if not found_node:
if self.app.compact_stream: if self.app.compact_stream:
try: try:
remove_announces = [] remove_announces = []
for announce in self.announce_stream: for announce in self.announce_stream:
if announce[1] == source_hash: if announce[1] == source_hash:
remove_announces.append(announce) remove_announces.append(announce)
for a in remove_announces: for a in remove_announces:
self.announce_stream.remove(a) self.announce_stream.remove(a)
except Exception as e:
RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR)
timestamp = time.time()
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "pn"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
self.announce_stream.pop()
except Exception as e: if hasattr(self.app, "ui") and hasattr(self.app.ui, "main_display"):
RNS.log("An error occurred while compacting the announce stream. The contained exception was:"+str(e), RNS.LOG_ERROR) self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
timestamp = time.time()
self.announce_stream.insert(0, (timestamp, source_hash, app_data, "pn"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
self.announce_stream.pop()
if hasattr(self.app.ui, "main_display"):
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
def remove_announce_with_timestamp(self, timestamp): def remove_announce_with_timestamp(self, timestamp):
selected_announce = None selected_announce = None
@ -339,7 +345,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)) 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 if e.display_name != None else "_"))
return node_list return node_list
def number_of_known_nodes(self): def number_of_known_nodes(self):

View File

@ -19,7 +19,7 @@ from datetime import datetime
import RNS.vendor.umsgpack as msgpack import RNS.vendor.umsgpack as msgpack
from ._version import __version__ from ._version import __version__
from .vendor.configobj import ConfigObj from RNS.vendor.configobj import ConfigObj
class NomadNetworkApp: class NomadNetworkApp:
time_format = "%Y-%m-%d %H:%M:%S" time_format = "%Y-%m-%d %H:%M:%S"
@ -290,9 +290,20 @@ class NomadNetworkApp:
self.directory = nomadnet.Directory(self) self.directory = nomadnet.Directory(self)
static_peers = []
for static_peer in self.static_peers:
try:
dh = bytes.fromhex(static_peer)
if len(dh) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
raise ValueError("Invalid destination length")
static_peers.append(dh)
except Exception as e:
RNS.log(f"Could not decode static peer destination hash {static_peer}: {e}", RNS.LOG_ERROR)
self.message_router = LXMF.LXMRouter( self.message_router = LXMF.LXMRouter(
identity = self.identity, storagepath = self.storagepath, autopeer = True, identity = self.identity, storagepath = self.storagepath, autopeer = True,
propagation_limit = self.lxmf_max_propagation_size, delivery_limit = self.lxmf_max_incoming_size, propagation_limit = self.lxmf_max_propagation_size, delivery_limit = self.lxmf_max_incoming_size,
max_peers = self.max_peers, static_peers = static_peers,
) )
self.message_router.register_delivery_callback(self.lxmf_delivery) self.message_router.register_delivery_callback(self.lxmf_delivery)
@ -526,7 +537,7 @@ class NomadNetworkApp:
RNS.log("Could not autoselect a propagation node! LXMF propagation will not be available until a trusted node announces on the network, or a propagation node is manually selected.", RNS.LOG_WARNING) RNS.log("Could not autoselect a propagation node! LXMF propagation will not be available until a trusted node announces on the network, or a propagation node is manually selected.", RNS.LOG_WARNING)
else: else:
pn_name_str = "" pn_name_str = ""
RNS.log("Selecting "+RNS.prettyhexrep(selected_node)+pn_name_str+" as default LXMF propagation node", RNS.LOG_INFO) RNS.log("Selecting "+RNS.prettyhexrep(selected_node)+pn_name_str+" as default LXMF propagation node", RNS.LOG_DEBUG)
self.message_router.set_outbound_propagation_node(selected_node) self.message_router.set_outbound_propagation_node(selected_node)
def get_user_selected_propagation_node(self): def get_user_selected_propagation_node(self):
@ -920,6 +931,19 @@ class NomadNetworkApp:
else: else:
self.prioritised_lxmf_destinations = [] self.prioritised_lxmf_destinations = []
if "static_peers" in self.config["node"]:
self.static_peers = self.config["node"].as_list("static_peers")
else:
self.static_peers = []
if not "max_peers" in self.config["node"]:
self.max_peers = None
else:
value = self.config["node"].as_int("max_peers")
if value < 0:
value = 0
self.max_peers = value
if not "message_storage_limit" in self.config["node"]: if not "message_storage_limit" in self.config["node"]:
self.message_storage_limit = 2000 self.message_storage_limit = 2000
else: else:
@ -1178,6 +1202,17 @@ max_transfer_size = 256
# and generally you do not need to use it. # and generally you do not need to use it.
# prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf # prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
# You can configure the maximum number of other
# propagation nodes that this node will peer
# with automatically. The default is 50.
# max_peers = 25
# You can configure a list of static propagation
# node peers, that this node will always be
# peered with, by specifying a list of
# destination hashes.
# static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4
# You can specify the interval in minutes for # You can specify the interval in minutes for
# rescanning the hosted pages path. By default, # rescanning the hosted pages path. By default,
# this option is disabled, and the pages path # this option is disabled, and the pages path

View File

@ -1 +1 @@
__version__ = "0.5.7" __version__ = "0.6.2"

View File

@ -666,9 +666,9 @@ Determines the interval in minutes for rescanning the hosted files path. By defa
< <
>>> >>>
`!disable_propagation = no`! `!disable_propagation = yes`!
>>>> >>>>
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. When Nomad Network is hosting a node, it can 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.
< <
>>> >>>
@ -689,6 +689,18 @@ The maximum accepted transfer size per incoming propagation transfer, in kilobyt
Configures the LXMF Propagation Node to prioritise storing messages for certain destinations. If the message store reaches the specified limit, LXMF will prioritise keeping messages for destinations specified with this option. This setting is optional, and generally you do not need to use it. Configures the LXMF Propagation Node to prioritise storing messages for certain destinations. If the message store reaches the specified limit, LXMF will prioritise keeping messages for destinations specified with this option. This setting is optional, and generally you do not need to use it.
< <
>>>
`!max_peers = 25`!
>>>>
Configures the maximum number of other nodes the LXMF Propagation Node will automatically peer with. The default is 50, but can be lowered or increased according to available resources.
<
>>>
`!static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4`!
>>>>
Configures the LXMF Propagation Node to always maintain propagation node peering with the specified list of destination hashes.
<
>> Printing Section >> Printing Section
This section holds configuration directives related to printing. It is delimited by the `![printing]`! header in the configuration file. Available directives, along with example values, are as follows: This section holds configuration directives related to printing. It is delimited by the `![printing]`! header in the configuration file. Available directives, along with example values, are as follows:

View File

@ -141,7 +141,7 @@ class AnnounceInfo(urwid.WidgetWrap):
if is_node: if is_node:
try: try:
existing_conversations = nomadnet.Conversation.conversation_list(self.app) existing_conversations = nomadnet.Conversation.conversation_list(self.app)
source_hash_text = RNS.hexrep(op_hash, delimit=False) source_hash_text = RNS.hexrep(op_hash, delimit=False)
display_name = op_str display_name = op_str
@ -162,7 +162,7 @@ class AnnounceInfo(urwid.WidgetWrap):
show_announce_stream(None) show_announce_stream(None)
try: try:
existing_conversations = nomadnet.Conversation.conversation_list(self.app) existing_conversations = nomadnet.Conversation.conversation_list(self.app)
source_hash_text = RNS.hexrep(source_hash, delimit=False) source_hash_text = RNS.hexrep(source_hash, delimit=False)
display_name = data_str display_name = data_str
@ -316,7 +316,7 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
info_widget = AnnounceInfo(announce, parent, self.app) info_widget = AnnounceInfo(announce, parent, self.app)
options = parent.left_pile.options(height_type=urwid.WEIGHT, height_amount=1) options = parent.left_pile.options(height_type=urwid.WEIGHT, height_amount=1)
parent.left_pile.contents[0] = (info_widget, options) parent.left_pile.contents[0] = (info_widget, options)
except KeyError as e: except KeyError as e:
def dismiss_dialog(sender): def dismiss_dialog(sender):
self.delegate.parent.close_list_dialogs() self.delegate.parent.close_list_dialogs()
@ -368,6 +368,10 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
def timestamp(self): def timestamp(self):
return self.timestamp return self.timestamp
class TabButton(urwid.Button):
button_left = urwid.Text("[")
button_right = urwid.Text("]")
class AnnounceStream(urwid.WidgetWrap): class AnnounceStream(urwid.WidgetWrap):
def __init__(self, app, parent): def __init__(self, app, parent):
self.app = app self.app = app
@ -376,11 +380,24 @@ class AnnounceStream(urwid.WidgetWrap):
self.timeout = self.app.config["textui"]["animation_interval"]*2 self.timeout = self.app.config["textui"]["animation_interval"]*2
self.ilb = None self.ilb = None
self.no_content = True self.no_content = True
self.current_tab = "nodes"
self.added_entries = [] self.added_entries = []
self.widget_list = [] self.widget_list = []
self.update_widget_list() self.update_widget_list()
# Create tab buttons
self.tab_nodes = TabButton("Nodes", on_press=self.show_nodes_tab)
self.tab_peers = TabButton("Peers", on_press=self.show_peers_tab)
self.tab_pn = TabButton("Propagation Nodes", on_press=self.show_pn_tab)
# Create tab bar with proportional widths
self.tab_bar = urwid.Columns([
('weight', 1, self.tab_nodes),
('weight', 1, self.tab_peers),
('weight', 3, self.tab_pn),
], dividechars=1) # Add 1 character spacing between tabs
self.ilb = ExceptionHandlingListBox( self.ilb = ExceptionHandlingListBox(
self.widget_list, self.widget_list,
on_selection_change=self.list_selection, on_selection_change=self.list_selection,
@ -389,7 +406,13 @@ class AnnounceStream(urwid.WidgetWrap):
#highlight_offFocus="list_off_focus" #highlight_offFocus="list_off_focus"
) )
self.display_widget = self.ilb # Combine tab bar and list box
self.pile = urwid.Pile([
('pack', self.tab_bar),
('weight', 1, self.ilb),
])
self.display_widget = self.pile
super().__init__(urwid.LineBox(self.display_widget, title="Announce Stream")) super().__init__(urwid.LineBox(self.display_widget, title="Announce Stream"))
def keypress(self, size, key): def keypress(self, size, key):
@ -397,7 +420,7 @@ class AnnounceStream(urwid.WidgetWrap):
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header" nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
elif key == "ctrl x": elif key == "ctrl x":
self.delete_selected_entry() self.delete_selected_entry()
return super(AnnounceStream, self).keypress(size, key) return super(AnnounceStream, self).keypress(size, key)
def delete_selected_entry(self): def delete_selected_entry(self):
@ -412,28 +435,45 @@ class AnnounceStream(urwid.WidgetWrap):
self.update_widget_list() self.update_widget_list()
def update_widget_list(self): def update_widget_list(self):
self.widget_list = []
new_entries = [] new_entries = []
for e in self.app.directory.announce_stream: for e in self.app.directory.announce_stream:
if not e[0] in self.added_entries: announce_type = e[3]
self.added_entries.insert(0, e[0])
new_entries.insert(0, e) # Filter based on current tab
if self.current_tab == "nodes" and (announce_type == "node" or announce_type == True):
new_entries.append(e)
elif self.current_tab == "peers" and (announce_type == "peer" or announce_type == False):
new_entries.append(e)
elif self.current_tab == "pn" and announce_type == "pn":
new_entries.append(e)
for e in new_entries: for e in new_entries:
nw = AnnounceStreamEntry(self.app, e, self) nw = AnnounceStreamEntry(self.app, e, self)
nw.timestamp = e[0] nw.timestamp = e[0]
self.widget_list.insert(0, nw) self.widget_list.append(nw)
if len(new_entries) > 0: if len(new_entries) > 0:
self.no_content = False self.no_content = False
if self.ilb != None:
self.ilb.set_body(self.widget_list)
else: else:
if len(self.widget_list) == 0: self.no_content = True
self.no_content = True self.widget_list = [urwid.Text(f"No {self.current_tab} announces", align='center')]
if self.ilb != None:
self.ilb.set_body(self.widget_list)
if self.ilb:
self.ilb.set_body(self.widget_list)
def show_nodes_tab(self, button):
self.current_tab = "nodes"
self.update_widget_list()
def show_peers_tab(self, button):
self.current_tab = "peers"
self.update_widget_list()
def show_pn_tab(self, button):
self.current_tab = "pn"
self.update_widget_list()
def list_selection(self, arg1, arg2): def list_selection(self, arg1, arg2):
pass pass
@ -596,7 +636,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
if node_ident != None: if node_ident != None:
try: try:
existing_conversations = nomadnet.Conversation.conversation_list(self.app) existing_conversations = nomadnet.Conversation.conversation_list(self.app)
source_hash_text = RNS.hexrep(op_hash, delimit=False) source_hash_text = RNS.hexrep(op_hash, delimit=False)
display_name = op_str display_name = op_str
@ -632,7 +672,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
trust_level = DirectoryEntry.UNTRUSTED trust_level = DirectoryEntry.UNTRUSTED
if r_unknown.get_state() == True: if r_unknown.get_state() == True:
trust_level = DirectoryEntry.UNKNOWN trust_level = DirectoryEntry.UNKNOWN
if r_trusted.get_state() == True: if r_trusted.get_state() == True:
trust_level = DirectoryEntry.TRUSTED trust_level = DirectoryEntry.TRUSTED
@ -699,7 +739,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
pile_widgets.insert(4, operator_entry) pile_widgets.insert(4, operator_entry)
pile = urwid.Pile(pile_widgets) pile = urwid.Pile(pile_widgets)
pile.focus_position = len(pile.contents)-1 pile.focus_position = len(pile.contents)-1
button_columns.focus_position = 0 button_columns.focus_position = 0
@ -733,7 +773,7 @@ class KnownNodes(urwid.WidgetWrap):
g = self.app.ui.glyphs g = self.app.ui.glyphs
self.widget_list = self.make_node_widgets() self.widget_list = self.make_node_widgets()
self.ilb = ExceptionHandlingListBox( self.ilb = ExceptionHandlingListBox(
self.widget_list, self.widget_list,
on_selection_change=self.node_list_selection, on_selection_change=self.node_list_selection,
@ -767,7 +807,7 @@ class KnownNodes(urwid.WidgetWrap):
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header" nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
elif key == "ctrl x": elif key == "ctrl x":
self.delete_selected_entry() self.delete_selected_entry()
return super(KnownNodes, self).keypress(size, key) return super(KnownNodes, self).keypress(size, key)
@ -910,7 +950,7 @@ class NodeEntry(urwid.WidgetWrap):
focus_style = "list_focus_untrusted" focus_style = "list_focus_untrusted"
type_symbol = g["node"] type_symbol = g["node"]
widget = ListEntry(type_symbol+" "+display_str) widget = ListEntry(type_symbol+" "+display_str)
urwid.connect_signal(widget, "click", delegate.connect_node, node) urwid.connect_signal(widget, "click", delegate.connect_node, node)
@ -1209,7 +1249,7 @@ class LocalPeer(urwid.WidgetWrap):
#overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=urwid.RELATIVE_100, valign=urwid.MIDDLE, height=urwid.PACK, left=4, right=4) #overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=urwid.RELATIVE_100, valign=urwid.MIDDLE, height=urwid.PACK, left=4, right=4)
overlay = dialog overlay = dialog
self.dialog_open = True self.dialog_open = True
options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None) options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
self.parent.left_pile.contents[1] = (overlay, options) self.parent.left_pile.contents[1] = (overlay, options)
@ -1226,7 +1266,7 @@ class LocalPeer(urwid.WidgetWrap):
self.t_last_announce.update_time() self.t_last_announce.update_time()
announce_button = urwid.Button("Announce Now", on_press=announce_query) announce_button = urwid.Button("Announce Now", on_press=announce_query)
self.display_widget = urwid.Pile( self.display_widget = urwid.Pile(
[ [
t_id, t_id,
@ -1270,13 +1310,13 @@ class NodeInfo(urwid.WidgetWrap):
def show_peer_info(sender): def show_peer_info(sender):
options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None) options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
self.parent.left_pile.contents[1] = (LocalPeer(self.app, self.parent), options) self.parent.left_pile.contents[1] = (LocalPeer(self.app, self.parent), options)
if self.app.enable_node: if self.app.enable_node:
if self.app.node != None: if self.app.node != None:
display_name = self.app.node.name display_name = self.app.node.name
else: else:
display_name = None display_name = None
if display_name == None: if display_name == None:
display_name = "" display_name = ""
@ -1308,7 +1348,7 @@ class NodeInfo(urwid.WidgetWrap):
#overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=urwid.RELATIVE_100, valign=urwid.MIDDLE, height=urwid.PACK, left=4, right=4) #overlay = urwid.Overlay(dialog, bottom, align=urwid.CENTER, width=urwid.RELATIVE_100, valign=urwid.MIDDLE, height=urwid.PACK, left=4, right=4)
overlay = dialog overlay = dialog
self.dialog_open = True self.dialog_open = True
options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None) options = self.parent.left_pile.options(height_type=urwid.PACK, height_amount=None)
self.parent.left_pile.contents[1] = (overlay, options) self.parent.left_pile.contents[1] = (overlay, options)
@ -1596,12 +1636,12 @@ class NetworkDisplay():
selected_node_entry = parent.known_nodes_display.ilb.get_selected_item() selected_node_entry = parent.known_nodes_display.ilb.get_selected_item()
if selected_node_entry is not None: if selected_node_entry is not None:
selected_node_hash = selected_node_entry.base_widget.display_widget.source_hash selected_node_hash = selected_node_entry.base_widget.display_widget.source_hash
if selected_node_hash is not None: if selected_node_hash is not None:
info_widget = KnownNodeInfo(selected_node_hash) info_widget = KnownNodeInfo(selected_node_hash)
options = parent.left_pile.options(height_type=urwid.WEIGHT, height_amount=1) options = parent.left_pile.options(height_type=urwid.WEIGHT, height_amount=1)
parent.left_pile.contents[0] = (info_widget, options) parent.left_pile.contents[0] = (info_widget, options)
def focus_lists(self): def focus_lists(self):
self.columns.focus_position = 0 self.columns.focus_position = 0
@ -1612,9 +1652,15 @@ class NetworkDisplay():
self.announce_stream_display.rebuild_widget_list() self.announce_stream_display.rebuild_widget_list()
def reinit_lxmf_peers(self): def reinit_lxmf_peers(self):
if self.lxmf_peers_display:
si = self.lxmf_peers_display.ilb.get_selected_position()
else:
si = None
self.lxmf_peers_display = LXMFPeers(self.app) self.lxmf_peers_display = LXMFPeers(self.app)
self.lxmf_peers_display.delegate = self self.lxmf_peers_display.delegate = self
self.close_list_dialogs() self.close_list_dialogs()
if si != None:
self.lxmf_peers_display.ilb.select_item(si)
def close_list_dialogs(self): def close_list_dialogs(self):
if self.list_display == 0: if self.list_display == 0:
@ -1683,7 +1729,7 @@ class LXMFPeers(urwid.WidgetWrap):
self.delete_selected_entry() self.delete_selected_entry()
elif key == "ctrl r": elif key == "ctrl r":
self.sync_selected_entry() self.sync_selected_entry()
return super(LXMFPeers, self).keypress(size, key) return super(LXMFPeers, self).keypress(size, key)
@ -1707,7 +1753,7 @@ class LXMFPeers(urwid.WidgetWrap):
peer = self.app.message_router.peers[destination_hash] peer = self.app.message_router.peers[destination_hash]
if time.time() > peer.last_sync_attempt+sync_grace: if time.time() > peer.last_sync_attempt+sync_grace:
peer.next_sync_attempt = time.time()-1 peer.next_sync_attempt = time.time()-1
def job(): def job():
peer.sync() peer.sync()
threading.Thread(target=job, daemon=True).start() threading.Thread(target=job, daemon=True).start()
@ -1753,7 +1799,7 @@ 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.app.directory.pn_trust_level(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].sync_transfer_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]
trust_level = self.app.directory.pn_trust_level(peer_id) trust_level = self.app.directory.pn_trust_level(peer_id)
@ -1772,7 +1818,7 @@ class LXMFPeerEntry(urwid.WidgetWrap):
node_identity = RNS.Identity.recall(destination_hash) node_identity = RNS.Identity.recall(destination_hash)
display_str = RNS.prettyhexrep(destination_hash) display_str = RNS.prettyhexrep(destination_hash)
if node_identity != None: if node_identity != None:
node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", node_identity) node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", node_identity)
display_name = self.app.directory.alleged_display_str(node_hash) display_name = self.app.directory.alleged_display_str(node_hash)
if display_name != None: if display_name != None:
@ -1801,10 +1847,11 @@ class LXMFPeerEntry(urwid.WidgetWrap):
txfer_limit = RNS.prettysize(peer.propagation_transfer_limit*1000) txfer_limit = RNS.prettysize(peer.propagation_transfer_limit*1000)
else: else:
txfer_limit = "No" txfer_limit = "No"
ar = round(peer.acceptance_rate*100, 2)
peer_info_str = sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard)) peer_info_str = sym+" "+display_str+"\n "+alive_string+", last heard "+pretty_date(int(peer.last_heard))
peer_info_str += "\n "+str(len(peer.unhandled_messages))+f" unhandled LXMs, {txfer_limit} sync limit\n" peer_info_str += "\n "+str(peer.unhandled_message_count)+f" unhandled LXMs, {txfer_limit} sync limit\n"
peer_info_str += f" {RNS.prettyspeed(peer.sync_transfer_rate)} STR, " peer_info_str += f" {RNS.prettyspeed(peer.sync_transfer_rate)} STR, "
peer_info_str += f"{RNS.prettyspeed(peer.link_establishment_rate)} LER\n" peer_info_str += f"{RNS.prettyspeed(peer.link_establishment_rate)} LER, {ar}% AR\n"
widget = ListEntry(peer_info_str) widget = ListEntry(peer_info_str)
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

File diff suppressed because it is too large Load Diff

View File

@ -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.9.1", "lxmf>=0.6.0", "urwid>=2.6.16", "qrcode"], install_requires=["rns>=0.9.4", "lxmf>=0.6.3", "urwid>=2.6.16", "qrcode"],
python_requires=">=3.7", python_requires=">=3.7",
) )