Compare commits

...

15 Commits
0.3.5 ... 0.3.8

Author SHA1 Message Date
Mark Qvist
c52efbfb75 Temporarily fixed urwid at 2.1.2 due to breaking changes in 2.2.0 2023-09-21 22:58:00 +02:00
Mark Qvist
da348c3b42 Improved propagation peer list display 2023-09-19 14:46:52 +02:00
Mark Qvist
92c3c55e03 Updated version and dependencies 2023-09-19 11:56:24 +02:00
Mark Qvist
8d9f4956db Improved request failure handling 2023-09-19 11:56:04 +02:00
Mark Qvist
062e31964a Updated install section 2023-08-15 11:26:48 +02:00
Mark Qvist
f36018632f Updated install section 2023-08-15 11:25:46 +02:00
Mark Qvist
81f65e3453 Updated install section 2023-08-15 11:13:31 +02:00
Mark Qvist
34b3987ded Added error handling to micron parser mouse event translator. Fixes #32. 2023-08-14 18:02:39 +02:00
Mark Qvist
22a7acf259 Added compact announce stream option 2023-08-13 21:54:35 +02:00
Mark Qvist
919a146da1 Added alternative save node hotkey. Added save hotkey to hotkeys list. Closes #31. 2023-08-13 20:55:35 +02:00
Mark Qvist
f0a4efa28b Added alternative save node hotkey. Added save hotkey to hotkeys list. 2023-08-13 20:54:29 +02:00
Mark Qvist
3d0043499c Added error handling to lxmf.delivery announce handler. Fixes #30. Hopefully. 2023-08-13 20:37:44 +02:00
Mark Qvist
b6e6c4bd3d Updated version and dependencies 2023-06-13 19:57:49 +02:00
markqvist
d06e1d3f1b Merge pull request #27 from Swissbandit/patch-1
Update Guide.py
2023-04-02 10:21:37 +02:00
Swissbandit
02f9a5a760 Update Guide.py
fixed typo redundant .mu
2023-03-29 16:44:37 +02:00
10 changed files with 173 additions and 52 deletions

View File

@@ -30,7 +30,36 @@ The easiest way to install Nomad Network is via pip:
```bash
# Install Nomad Network and dependencies
pip3 install nomadnet
pip install nomadnet
# Run the client
nomadnet
# Or alternatively run as a daemon, with no user interface
nomadnet --daemon
# List options
nomadnet --help
```
If you are using an operating system that blocks normal user package installation via `pip`, you can return `pip` to normal behaviour by editing the `~/.config/pip/pip.conf` file, and adding the following directive in the `[global]` section:
```text
[global]
break-system-packages = true
```
Alternatively, you can use the `pipx` tool to install Nomad Network in an isolated environment:
```bash
# Install Nomad Network
pipx install nomadnet
# Optionally install Reticulum utilities
pipx install rns
# Optionally install standalone LXMF utilities
pipx install lxmf
# Run the client
nomadnet

View File

@@ -34,16 +34,21 @@ class Directory:
aspect_filter = "nomadnetwork.node"
@staticmethod
def received_announce(destination_hash, announced_identity, app_data):
app = nomadnet.NomadNetworkApp.get_shared_instance()
try:
app = nomadnet.NomadNetworkApp.get_shared_instance()
if not destination_hash in app.ignored_list:
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
if not destination_hash in app.ignored_list:
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()
else:
RNS.log("Ignored announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
app.directory.node_announce_received(destination_hash, app_data, associated_peer)
app.autoselect_propagation_node()
else:
RNS.log("Ignored announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while evaluating LXMF destination announce, ignoring announce.", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
def __init__(self, app):
@@ -113,6 +118,19 @@ class Directory:
def lxmf_announce_received(self, source_hash, app_data):
if app_data != None:
if self.app.compact_stream:
try:
remove_announces = []
for announce in self.announce_stream:
if announce[1] == source_hash:
remove_announces.append(announce)
for a in remove_announces:
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, "peer"))
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
@@ -123,6 +141,19 @@ class Directory:
def node_announce_received(self, source_hash, app_data, associated_peer):
if app_data != None:
if self.app.compact_stream:
try:
remove_announces = []
for announce in self.announce_stream:
if announce[1] == source_hash:
remove_announces.append(announce)
for a in remove_announces:
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:
@@ -150,6 +181,19 @@ class Directory:
break
if not found_node:
if self.app.compact_stream:
try:
remove_announces = []
for announce in self.announce_stream:
if announce[1] == source_hash:
remove_announces.append(announce)
for a in remove_announces:
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:

View File

@@ -126,6 +126,7 @@ class NomadNetworkApp:
self.periodic_lxmf_sync = True
self.lxmf_sync_interval = 360*60
self.lxmf_sync_limit = 8
self.compact_stream = False
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
@@ -698,6 +699,10 @@ class NomadNetworkApp:
else:
self.lxmf_sync_limit = None
if option == "compact_announce_stream":
value = self.config["client"].as_bool(option)
self.compact_stream = value
if option == "user_interface":
value = value.lower()
if value == "none":
@@ -929,6 +934,12 @@ lxmf_sync_interval = 360
# the limit, and download everything every time.
lxmf_sync_limit = 8
# The announce stream will only show one entry
# per destination or node by default. You can
# change this to show as many announces as have
# been received, for every destination.
compact_announce_stream = yes
[textui]
# Amount of time to show intro screen

View File

@@ -1 +1 @@
__version__ = "0.3.5"
__version__ = "0.3.8"

View File

@@ -45,6 +45,8 @@ THEMES = {
("list_normal", "dark gray", "default", "default", "#bbb", "default"),
("list_untrusted", "dark red", "default", "default", "#a22", "default"),
("list_focus_untrusted", "black", "light gray", "standout", "#810", "#aaa"),
("list_unresponsive", "yellow", "default", "default", "#b92", "default"),
("list_focus_unresponsive", "black", "light gray", "standout", "#530", "#aaa"),
("topic_list_normal", "light gray", "default", "default", "#ddd", "default"),
("browser_controls", "light gray", "default", "default", "#bbb", "default"),
("progress_full", "black", "light gray", "standout", "#111", "#bbb"),
@@ -78,6 +80,8 @@ THEMES = {
("list_normal", "dark gray", "default", "default", "#444", "default"),
("list_untrusted", "dark red", "default", "default", "#a22", "default"),
("list_focus_untrusted", "black", "dark gray", "standout", "#810", "#aaa"),
("list_unresponsive", "yellow", "default", "default", "#b92", "default"),
("list_focus_unresponsive", "black", "light gray", "standout", "#530", "#aaa"),
("topic_list_normal", "dark gray", "default", "default", "#222", "default"),
("browser_controls", "dark gray", "default", "default", "#444", "default"),
("progress_full", "black", "dark gray", "standout", "#111", "#bbb"),

View File

@@ -23,6 +23,8 @@ class BrowserFrame(urwid.Frame):
self.delegate.url_dialog()
elif key == "ctrl s":
self.delegate.save_node_dialog()
elif key == "ctrl b":
self.delegate.save_node_dialog()
elif key == "ctrl g":
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.sub_displays.network_display.toggle_fullscreen()
elif self.get_focus() == "body":
@@ -754,7 +756,7 @@ class Browser:
def __load(self):
# If an established link exists, but it doesn't match the target
# destination, we close and clear it.
if self.link != None and self.link.destination.hash != self.destination_hash:
if self.link != None and (self.link.destination.hash != self.destination_hash or self.link.status != RNS.Link.ACTIVE):
self.link.teardown()
self.link = None
@@ -1003,6 +1005,11 @@ class Browser:
self.response_transfer_size = None
self.update_display()
if self.link != None:
try:
self.link.teardown()
except Exception as e:
pass
else:
self.status = Browser.REQUEST_FAILED
self.response_progress = 0
@@ -1010,6 +1017,11 @@ class Browser:
self.response_transfer_size = None
self.update_display()
if self.link != None:
try:
self.link.teardown()
except Exception as e:
pass
def request_timeout(self, request_receipt=None):
@@ -1019,6 +1031,11 @@ class Browser:
self.response_transfer_size = None
self.update_display()
if self.link != None:
try:
self.link.teardown()
except Exception as e:
pass
def response_progressed(self, request_receipt):

View File

@@ -335,7 +335,7 @@ By default, you can find the examples in `!~/.nomadnetwork/examples`!. If you bu
Sometimes, you don't want everyone to be able to view certain pages or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests.
To enable authentication for any page, simply add a new file to your pages directory with ".allowed" added to the file-name of the page. If your page is named "secret_page.mu", just add a file named "secret_page.mu.allowed".
To enable authentication for any page, simply add a new file to your pages directory with ".allowed" added to the file-name of the page. If your page is named "secret_page.mu", just add a file named "secret_page.allowed".
For each user allowed to access the page, add a line to this file, containing the hash of that users primary identity. Users can find their own identity hash in the `![ Network ]`! part of the program, under `!Local Peer Info`!. If you want to allow access for three different users, your file would look like this:
@@ -506,6 +506,12 @@ The number of minutes between each automatic sync. The default is equal to 6 hou
On low-bandwidth networks, it can be useful to limit the amount of messages downloaded in each sync. The default is 8. Set to 0 to download all available messages every time a sync occurs.
<
>>>
`!compact_announce_stream = yes`!
>>>>
With this option enabled, Nomad Network will only display one entry in the announce stream per destination. Older announces are culled when a new one arrives.
<
>> Text UI Section
This section hold configuration directives related to the look and feel of the text-based user interface of the program. It is delimited by the `![textui]`! header in the configuration file. Available directives, along with their default values, are as follows:

View File

@@ -751,41 +751,45 @@ class LinkableText(urwid.Text):
return x, y
def mouse_event(self, size, event, button, x, y, focus):
if button != 1 or not is_mouse_press(event):
return False
else:
(maxcol,) = size
translation = self.get_line_translation(maxcol)
line_offset = 0
if self.align == "center":
line_offset = translation[y][1][1]-translation[y][0][0]
if x < translation[y][0][0]:
x = translation[y][0][0]
if x > translation[y][1][0]+translation[y][0][0]:
x = translation[y][1][0]+translation[y][0][0]
elif self.align == "right":
line_offset = translation[y][1][1]-translation[y][0][0]
if x < translation[y][0][0]:
x = translation[y][0][0]
try:
if button != 1 or not is_mouse_press(event):
return False
else:
line_offset = translation[y][0][1]
if x > translation[y][0][0]:
x = translation[y][0][0]
(maxcol,) = size
translation = self.get_line_translation(maxcol)
line_offset = 0
pos = line_offset+x
if self.align == "center":
line_offset = translation[y][1][1]-translation[y][0][0]
if x < translation[y][0][0]:
x = translation[y][0][0]
self._cursor_position = pos
item = self.find_item_at_pos(self._cursor_position)
if x > translation[y][1][0]+translation[y][0][0]:
x = translation[y][1][0]+translation[y][0][0]
if item != None:
if isinstance(item, LinkSpec):
self.handle_link(item.link_target, item.link_fields)
elif self.align == "right":
line_offset = translation[y][1][1]-translation[y][0][0]
if x < translation[y][0][0]:
x = translation[y][0][0]
self._invalidate()
self._emit("change")
else:
line_offset = translation[y][0][1]
if x > translation[y][0][0]:
x = translation[y][0][0]
pos = line_offset+x
self._cursor_position = pos
item = self.find_item_at_pos(self._cursor_position)
if item != None:
if isinstance(item, LinkSpec):
self.handle_link(item.link_target, item.link_fields)
self._invalidate()
self._emit("change")
return True
return True
except Exception as e:
return False

View File

@@ -13,9 +13,7 @@ class NetworkDisplayShortcuts():
self.app = app
g = app.ui.glyphs
self.widget = urwid.AttrMap(urwid.Text("[C-l] Nodes/Announces [C-x] Remove [C-w] Disconnect [C-d] Back [C-f] Forward [C-r] Reload [C-u] URL [C-g] Fullscreen"), "shortcutbar")
# "[C-"+g["arrow_u"]+g["arrow_d"]+"] Navigate Lists"
self.widget = urwid.AttrMap(urwid.Text("[C-l] Nodes/Announces [C-x] Remove [C-w] Disconnect [C-d] Back [C-f] Forward [C-r] Reload [C-u] URL [C-g] Fullscreen [C-s / C-b] Save Node"), "shortcutbar")
class DialogLineBox(urwid.LineBox):
def keypress(self, size, key):
@@ -1578,7 +1576,11 @@ class LXMFPeers(urwid.WidgetWrap):
self.pile = urwid.Pile([urwid.Text(("warning_text", g["info"]+"\n"), align="center"), SelectText(("warning_text", "Currently, no LXMF nodes are peered\n\n"), align="center")])
self.display_widget = urwid.Filler(self.pile, valign="top", height="pack")
urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="LXMF Propagation Peers"), widget_style))
if hasattr(self, "peer_list") and self.peer_list:
pl = len(self.peer_list)
else:
pl = 0
urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title=f"LXMF Propagation Peers ({pl})"), widget_style))
def keypress(self, size, key):
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
@@ -1613,13 +1615,13 @@ class LXMFPeers(urwid.WidgetWrap):
def make_peer_widgets(self):
widget_list = []
for peer_id in self.peer_list:
sorted_peers = sorted(self.peer_list, key=lambda pid: self.peer_list[pid].link_establishment_rate, reverse=True)
for peer_id in sorted_peers:
peer = self.peer_list[peer_id]
pe = LXMFPeerEntry(self.app, peer, self)
pe.destination_hash = peer.destination_hash
widget_list.append(pe)
# TODO: Sort list
return widget_list
class LXMFPeerEntry(urwid.WidgetWrap):
@@ -1635,7 +1637,7 @@ class LXMFPeerEntry(urwid.WidgetWrap):
node_hash = RNS.Destination.hash_from_name_and_identity("nomadnetwork.node", node_identity)
display_name = self.app.directory.alleged_display_str(node_hash)
if display_name != None:
display_str += " "+str(display_name)
display_str = str(display_name)+"\n "+display_str
sym = g["sent"]
style = "list_unknown"
@@ -1645,8 +1647,12 @@ class LXMFPeerEntry(urwid.WidgetWrap):
if hasattr(peer, "alive"):
if peer.alive:
alive_string = "Available"
style = "list_normal"
focus_style = "list_focus"
else:
alive_string = "Unresponsive"
style = "list_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")
# urwid.connect_signal(widget, "click", delegate.connect_node, node)

View File

@@ -30,6 +30,6 @@ setuptools.setup(
entry_points= {
'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
},
install_requires=["rns>=0.4.9", "lxmf>=0.3.1", "urwid>=2.1.2", "qrcode"],
install_requires=["rns>=0.5.9", "lxmf>=0.3.3", "urwid==2.1.2", "qrcode"],
python_requires=">=3.6",
)