mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 14:54:26 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58c118c7ef | ||
|
|
d1633e0771 | ||
|
|
b86c811bde | ||
|
|
9824a8f7ea | ||
|
|
b2d54cf40a | ||
|
|
1c1ddab28f | ||
|
|
fe0a3b6a3e | ||
|
|
f5b356d8b8 | ||
|
|
686ca41b4e | ||
|
|
8ff629a3e2 | ||
|
|
6c16e810e5 | ||
|
|
6d4ac49264 | ||
|
|
939bc37f86 | ||
|
|
14eb35f7de | ||
|
|
19fb70fb3f | ||
|
|
9f9f10d54e | ||
|
|
863a6cd2cd | ||
|
|
2ec95df3ec | ||
|
|
313cc108de | ||
|
|
33ccab0907 | ||
|
|
71125252ee | ||
|
|
2ec674fbf3 |
25
Makefile
Normal file
25
Makefile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
all: release
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo Cleaning...
|
||||||
|
-rm -r ./build
|
||||||
|
-rm -r ./dist
|
||||||
|
|
||||||
|
remove_symlinks:
|
||||||
|
@echo Removing symlinks for build...
|
||||||
|
-rm ./LXMF
|
||||||
|
-rm ./RNS
|
||||||
|
|
||||||
|
create_symlinks:
|
||||||
|
@echo Creating symlinks...
|
||||||
|
-ln -s ../Reticulum/RNS ./
|
||||||
|
-ln -s ../LXMF/LXMF ./
|
||||||
|
|
||||||
|
build_wheel:
|
||||||
|
python3 setup.py sdist bdist_wheel
|
||||||
|
|
||||||
|
release: remove_symlinks build_wheel create_symlinks
|
||||||
|
|
||||||
|
upload:
|
||||||
|
@echo Uploading to PyPi...
|
||||||
|
twine upload dist/*
|
||||||
13
README.md
13
README.md
@@ -27,7 +27,8 @@ The current version of the program should be considered a beta release. The prog
|
|||||||
### Feature roadmap
|
### Feature roadmap
|
||||||
- Access control and authentication for nodes, pages and files
|
- Access control and authentication for nodes, pages and files
|
||||||
- Network-wide propagated bulletins and discussion threads
|
- Network-wide propagated bulletins and discussion threads
|
||||||
- Geospatial information sharing and collaborative maps
|
- Collaborative maps
|
||||||
|
- Facilitation of trade and barter
|
||||||
|
|
||||||
## Dependencies:
|
## Dependencies:
|
||||||
- Python 3
|
- Python 3
|
||||||
@@ -49,6 +50,16 @@ The first time the program is running, you will be presented with the guide sect
|
|||||||
|
|
||||||
To use Nomad Network on packet radio or LoRa, you will need to configure your Reticulum installation to use any relevant packet radio TNCs or LoRa devices on your system. See the [Reticulum documentation](https://markqvist.github.io/Reticulum/manual/interfaces.html) for info.
|
To use Nomad Network on packet radio or LoRa, you will need to configure your Reticulum installation to use any relevant packet radio TNCs or LoRa devices on your system. See the [Reticulum documentation](https://markqvist.github.io/Reticulum/manual/interfaces.html) for info.
|
||||||
|
|
||||||
|
### Installing on Android
|
||||||
|
You can install Nomad Network on Android using Termux, but there's a few extra commands involved than the above one-liner. The process is documented in the [Android Installation](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html#reticulum-on-android) section of the Reticulum Manual. Once the Reticulum has been installed according to the linked documentation, Nomad Network can be installed as usual with pip.
|
||||||
|
|
||||||
|
## Support Nomad Network
|
||||||
|
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
|
||||||
|
|
||||||
|
- Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
|
||||||
|
- Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||||
|
- Ko-Fi: https://ko-fi.com/markqvist
|
||||||
|
|
||||||
## Caveat Emptor
|
## Caveat Emptor
|
||||||
Nomad Network is beta software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
Nomad Network is beta software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ class Directory:
|
|||||||
else:
|
else:
|
||||||
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
return "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||||
|
|
||||||
|
def alleged_display_str(self, source_hash):
|
||||||
|
if source_hash in self.directory_entries:
|
||||||
|
return self.directory_entries[source_hash].display_name
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def trust_level(self, source_hash, announced_display_name=None):
|
def trust_level(self, source_hash, announced_display_name=None):
|
||||||
if source_hash in self.directory_entries:
|
if source_hash in self.directory_entries:
|
||||||
if announced_display_name == None:
|
if announced_display_name == None:
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import RNS.vendor.umsgpack as msgpack
|
|||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
JOB_INTERVAL = 5
|
JOB_INTERVAL = 5
|
||||||
|
START_ANNOUNCE_DELAY = 6
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
RNS.log("Nomad Network Node starting...", RNS.LOG_VERBOSE)
|
RNS.log("Nomad Network Node starting...", RNS.LOG_VERBOSE)
|
||||||
self.app = app
|
self.app = app
|
||||||
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 = None
|
self.last_announce = time.time()
|
||||||
self.announce_interval = self.app.node_announce_interval
|
self.announce_interval = self.app.node_announce_interval
|
||||||
self.job_interval = Node.JOB_INTERVAL
|
self.job_interval = Node.JOB_INTERVAL
|
||||||
self.should_run_jobs = True
|
self.should_run_jobs = True
|
||||||
@@ -29,8 +30,18 @@ class Node:
|
|||||||
RNS.log("Node \""+self.name+"\" ready for incoming connections on "+RNS.prettyhexrep(self.destination.hash), RNS.LOG_VERBOSE)
|
RNS.log("Node \""+self.name+"\" ready for incoming connections on "+RNS.prettyhexrep(self.destination.hash), RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
if self.app.node_announce_at_start:
|
if self.app.node_announce_at_start:
|
||||||
|
def delayed_announce():
|
||||||
|
time.sleep(Node.START_ANNOUNCE_DELAY)
|
||||||
self.announce()
|
self.announce()
|
||||||
|
|
||||||
|
da_thread = threading.Thread(target=delayed_announce)
|
||||||
|
da_thread.setDaemon(True)
|
||||||
|
da_thread.start()
|
||||||
|
|
||||||
|
job_thread = threading.Thread(target=self.__jobs)
|
||||||
|
job_thread.setDaemon(True)
|
||||||
|
job_thread.start()
|
||||||
|
|
||||||
|
|
||||||
def register_pages(self):
|
def register_pages(self):
|
||||||
self.servedpages = []
|
self.servedpages = []
|
||||||
@@ -134,7 +145,7 @@ class Node:
|
|||||||
while self.should_run_jobs:
|
while self.should_run_jobs:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if now > self.last_announce + self.announce_interval:
|
if now > self.last_announce + self.announce_interval*60:
|
||||||
self.announce()
|
self.announce()
|
||||||
|
|
||||||
time.sleep(self.job_interval)
|
time.sleep(self.job_interval)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import atexit
|
import atexit
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
import contextlib
|
||||||
|
|
||||||
import RNS
|
import RNS
|
||||||
import LXMF
|
import LXMF
|
||||||
@@ -19,12 +24,26 @@ class NomadNetworkApp:
|
|||||||
|
|
||||||
configdir = os.path.expanduser("~")+"/.nomadnetwork"
|
configdir = os.path.expanduser("~")+"/.nomadnetwork"
|
||||||
|
|
||||||
|
START_ANNOUNCE_DELAY = 3
|
||||||
|
|
||||||
def exit_handler(self):
|
def exit_handler(self):
|
||||||
RNS.log("Nomad Network Client exit handler executing...", RNS.LOG_VERBOSE)
|
RNS.log("Nomad Network Client exit handler executing...", RNS.LOG_VERBOSE)
|
||||||
RNS.log("Saving directory...", RNS.LOG_VERBOSE)
|
RNS.log("Saving directory...", RNS.LOG_VERBOSE)
|
||||||
self.directory.save_to_disk()
|
self.directory.save_to_disk()
|
||||||
RNS.log("Nomad Network Client exiting now", RNS.LOG_VERBOSE)
|
RNS.log("Nomad Network Client exiting now", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
|
def exception_handler(self, e_type, e_value, e_traceback):
|
||||||
|
RNS.log("An unhandled exception occurred, the details of which will be dumped below", RNS.LOG_ERROR)
|
||||||
|
RNS.log("Type : "+str(e_type), RNS.LOG_ERROR)
|
||||||
|
RNS.log("Value : "+str(e_value), RNS.LOG_ERROR)
|
||||||
|
t_string = ""
|
||||||
|
for line in traceback.format_tb(e_traceback):
|
||||||
|
t_string += line
|
||||||
|
RNS.log("Trace : \n"+t_string, RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
if issubclass(e_type, KeyboardInterrupt):
|
||||||
|
sys.__excepthook__(e_type, e_value, e_traceback)
|
||||||
|
|
||||||
def __init__(self, configdir = None, rnsconfigdir = None):
|
def __init__(self, configdir = None, rnsconfigdir = None):
|
||||||
self.version = __version__
|
self.version = __version__
|
||||||
self.enable_client = False
|
self.enable_client = False
|
||||||
@@ -45,6 +64,7 @@ class NomadNetworkApp:
|
|||||||
|
|
||||||
self.configpath = self.configdir+"/config"
|
self.configpath = self.configdir+"/config"
|
||||||
self.logfilepath = self.configdir+"/logfile"
|
self.logfilepath = self.configdir+"/logfile"
|
||||||
|
self.errorfilepath = self.configdir+"/errors"
|
||||||
self.storagepath = self.configdir+"/storage"
|
self.storagepath = self.configdir+"/storage"
|
||||||
self.identitypath = self.configdir+"/storage/identity"
|
self.identitypath = self.configdir+"/storage/identity"
|
||||||
self.cachepath = self.configdir+"/storage/cache"
|
self.cachepath = self.configdir+"/storage/cache"
|
||||||
@@ -191,12 +211,38 @@ class NomadNetworkApp:
|
|||||||
self.autoselect_propagation_node()
|
self.autoselect_propagation_node()
|
||||||
|
|
||||||
if self.peer_announce_at_start:
|
if self.peer_announce_at_start:
|
||||||
|
def delayed_announce():
|
||||||
|
time.sleep(NomadNetworkApp.START_ANNOUNCE_DELAY)
|
||||||
self.announce_now()
|
self.announce_now()
|
||||||
|
|
||||||
atexit.register(self.exit_handler)
|
da_thread = threading.Thread(target=delayed_announce)
|
||||||
|
da_thread.setDaemon(True)
|
||||||
|
da_thread.start()
|
||||||
|
|
||||||
|
atexit.register(self.exit_handler)
|
||||||
|
sys.excepthook = self.exception_handler
|
||||||
|
|
||||||
|
# This stderr redirect is needed to stop urwid
|
||||||
|
# from spewing KeyErrors to the console and thus,
|
||||||
|
# messing up the UI. A pull request to fix the
|
||||||
|
# bug in urwid was submitted, but until it is
|
||||||
|
# merged, this hack will mitigate it.
|
||||||
|
strio = io.StringIO()
|
||||||
|
with contextlib.redirect_stderr(strio):
|
||||||
nomadnet.ui.spawn(self.uimode)
|
nomadnet.ui.spawn(self.uimode)
|
||||||
|
|
||||||
|
if strio.tell() > 0:
|
||||||
|
try:
|
||||||
|
strio.seek(0)
|
||||||
|
err_file = open(self.errorfilepath, "w")
|
||||||
|
err_file.write(strio.read())
|
||||||
|
err_file.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Could not write stderr output to error log file at "+str(self.errorfilepath)+".", RNS.LOG_ERROR)
|
||||||
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def set_display_name(self, display_name):
|
def set_display_name(self, display_name):
|
||||||
self.peer_settings["display_name"] = display_name
|
self.peer_settings["display_name"] = display_name
|
||||||
self.lxmf_destination.display_name = display_name
|
self.lxmf_destination.display_name = display_name
|
||||||
@@ -218,6 +264,8 @@ class NomadNetworkApp:
|
|||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_LINK_ESTABLISHED:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_LINK_ESTABLISHED:
|
||||||
return "Link established"
|
return "Link established"
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_REQUEST_SENT:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_REQUEST_SENT:
|
||||||
|
return "Sync request sent"
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_RECEIVING:
|
||||||
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"
|
||||||
@@ -241,6 +289,8 @@ class NomadNetworkApp:
|
|||||||
return True
|
return True
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_REQUEST_SENT:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_REQUEST_SENT:
|
||||||
return True
|
return True
|
||||||
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_RECEIVING:
|
||||||
|
return True
|
||||||
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 True
|
return True
|
||||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.2"
|
__version__ = "0.1.5"
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
|
||||||
|
import io
|
||||||
import argparse
|
import argparse
|
||||||
import nomadnet
|
import nomadnet
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class ConversationDisplayShortcuts():
|
|||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-k] Clear Editor [C-w] Close [C-t] Editor Type [C-p] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-k] Clear [C-w] Close [C-t] Title [C-p] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar")
|
||||||
|
|
||||||
class ConversationsArea(urwid.LineBox):
|
class ConversationsArea(urwid.LineBox):
|
||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
@@ -98,7 +98,7 @@ class ConversationsDisplay():
|
|||||||
highlight_offFocus="list_off_focus"
|
highlight_offFocus="list_off_focus"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.listbox = ConversationsArea(urwid.Filler(self.ilb, height=("relative", 100)))
|
self.listbox = ConversationsArea(urwid.Filler(self.ilb, height=("relative", 100)), title="Conversations")
|
||||||
self.listbox.delegate = self
|
self.listbox.delegate = self
|
||||||
|
|
||||||
def delete_selected_conversation(self):
|
def delete_selected_conversation(self):
|
||||||
@@ -352,6 +352,7 @@ class ConversationsDisplay():
|
|||||||
|
|
||||||
def cancel_sync(sender):
|
def cancel_sync(sender):
|
||||||
self.app.cancel_lxmf_sync()
|
self.app.cancel_lxmf_sync()
|
||||||
|
self.update_sync_dialog()
|
||||||
|
|
||||||
cancel_button = urwid.Button("Close", on_press=dismiss_dialog)
|
cancel_button = urwid.Button("Close", on_press=dismiss_dialog)
|
||||||
sync_progress = SyncProgressBar("progress_empty" , "progress_full", current=self.app.get_sync_progress(), done=1.0, satt=None)
|
sync_progress = SyncProgressBar("progress_empty" , "progress_full", current=self.app.get_sync_progress(), done=1.0, satt=None)
|
||||||
|
|||||||
@@ -248,10 +248,20 @@ To learn how to host your own node, read the `*Hosting a Node`* section of this
|
|||||||
|
|
||||||
TOPIC_HOSTING = '''>Hosting a Node
|
TOPIC_HOSTING = '''>Hosting a Node
|
||||||
|
|
||||||
To host a node on the network, you must enable it in the configuration file, by setting `*enable_node`* directive to `*yes`*. You should also configure the other node-related parameters such as the node name and announce interval settings. Once node hosting has been enabled in the configuration, Nomad Network will start hosting your node as soon as the program is lauched, and other peers on the network will be able to connect and interact with content on your node.
|
To host a node on the network, you must enable it in the configuration file, by setting `*enable_node`* directive to `*yes`*. You should also configure the other node-related parameters such as the node name and announce interval settings. Once node hosting has been enabled in the configuration, Nomad Network will start hosting your node as soon as the program is launched, and other peers on the network will be able to connect and interact with content on your node.
|
||||||
|
|
||||||
By default, no content is defined, apart from a short placeholder home page. To learn how to add your own content, read on.
|
By default, no content is defined, apart from a short placeholder home page. To learn how to add your own content, read on.
|
||||||
|
|
||||||
|
>>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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
To view LXMF Propagation nodes that are currently peered with your node, go to the `![ Network ]`! part of the program and press `!Ctrl-P`!.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
>>Pages
|
>>Pages
|
||||||
|
|
||||||
Nomad Network nodes can host pages similar to web pages, that other peers can read and interact with. Pages are written in a compact markup language called `*micron`*. To learn how to write formatted pages with micron, see the `*Markup`* section of this guide (which is, itself, written in micron). Pages can be linked arbitrarily with hyperlinks, that can also link to pages (or other resources) on other nodes.
|
Nomad Network nodes can host pages similar to web pages, that other peers can read and interact with. Pages are written in a compact markup language called `*micron`*. To learn how to write formatted pages with micron, see the `*Markup`* section of this guide (which is, itself, written in micron). Pages can be linked arbitrarily with hyperlinks, that can also link to pages (or other resources) on other nodes.
|
||||||
@@ -302,6 +312,8 @@ You're currently located in the guide section of the program. I'm sorry I had to
|
|||||||
|
|
||||||
To get the most out of Nomad Network, you will need a terminal that supports UTF-8 and at least 256 colors, ideally true-color. If your terminal supports true-color, you can go to the `![ Config ]`! menu item, launch the editor and change the configuration.
|
To get the most out of Nomad Network, you will need a terminal that supports UTF-8 and at least 256 colors, ideally true-color. If your terminal supports true-color, you can go to the `![ Config ]`! menu item, launch the editor and change the configuration.
|
||||||
|
|
||||||
|
It is recommended to use a terminal size of at least 122x32. Nomad Network will work with smaller terminal sizes, but the interface might feel a bit cramped.
|
||||||
|
|
||||||
If you don't already have a Nerd Font installed (see https://www.nerdfonts.com/), I also highly recommend to do so, since it will greatly expand the amount of glyphs, icons and graphics that Nomad Network can use. Once you have your terminal set up with a Nerd Font, go to the `![ Config ]`! menu item and enable Nerd Fonts in the configuration instead of normal unicode glyphs.
|
If you don't already have a Nerd Font installed (see https://www.nerdfonts.com/), I also highly recommend to do so, since it will greatly expand the amount of glyphs, icons and graphics that Nomad Network can use. Once you have your terminal set up with a Nerd Font, go to the `![ Config ]`! menu item and enable Nerd Fonts in the configuration instead of normal unicode glyphs.
|
||||||
|
|
||||||
Nomad Network expects that you are already connected to some form of Reticulum network. That could be as simple as the default UDP-based demo interface on your local ethernet network. This short guide won't go into any details on building networks, but you will find other entries in the guide that deal with network setup and configuration.
|
Nomad Network expects that you are already connected to some form of Reticulum network. That could be as simple as the default UDP-based demo interface on your local ethernet network. This short guide won't go into any details on building networks, but you will find other entries in the guide that deal with network setup and configuration.
|
||||||
|
|||||||
@@ -125,15 +125,18 @@ class MainDisplay():
|
|||||||
def update_active_sub_display(self):
|
def update_active_sub_display(self):
|
||||||
self.frame.contents["body"] = (self.sub_displays.active().widget, None)
|
self.frame.contents["body"] = (self.sub_displays.active().widget, None)
|
||||||
self.update_active_shortcuts()
|
self.update_active_shortcuts()
|
||||||
|
# TODO: Remove when new mitigation has been tested
|
||||||
|
# self.app.ui.main_display.request_redraw(extra_delay=0.0)
|
||||||
|
|
||||||
def update_active_shortcuts(self):
|
def update_active_shortcuts(self):
|
||||||
self.frame.contents["footer"] = (self.sub_displays.active().shortcuts().widget, None)
|
self.frame.contents["footer"] = (self.sub_displays.active().shortcuts().widget, None)
|
||||||
|
|
||||||
def request_redraw(self):
|
def request_redraw(self, extra_delay=0.0):
|
||||||
self.app.ui.loop.set_alarm_in(0.25, self.redraw_now)
|
self.app.ui.loop.set_alarm_in(0.25+extra_delay, self.redraw_now)
|
||||||
|
|
||||||
def redraw_now(self, sender=None, data=None):
|
def redraw_now(self, sender=None, data=None):
|
||||||
self.app.ui.loop.draw_screen()
|
self.app.ui.loop.screen.clear()
|
||||||
|
#self.app.ui.loop.draw_screen()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.menu_display.start()
|
self.menu_display.start()
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ class AnnounceInfo(urwid.WidgetWrap):
|
|||||||
self.parent.left_pile.contents[0] = (self.parent.announce_stream_display, options)
|
self.parent.left_pile.contents[0] = (self.parent.announce_stream_display, options)
|
||||||
|
|
||||||
def connect(sender):
|
def connect(sender):
|
||||||
|
# TODO: Remove when new mitigation has been tested
|
||||||
|
# self.app.ui.main_display.request_redraw(extra_delay=0.75)
|
||||||
self.parent.browser.retrieve_url(RNS.hexrep(source_hash, delimit=False))
|
self.parent.browser.retrieve_url(RNS.hexrep(source_hash, delimit=False))
|
||||||
show_announce_stream(None)
|
show_announce_stream(None)
|
||||||
|
|
||||||
@@ -254,7 +256,7 @@ class AnnounceStream(urwid.WidgetWrap):
|
|||||||
self.widget_list = []
|
self.widget_list = []
|
||||||
self.update_widget_list()
|
self.update_widget_list()
|
||||||
|
|
||||||
self.ilb = IndicativeListBox(
|
self.ilb = ExceptionHandlingListBox(
|
||||||
self.widget_list,
|
self.widget_list,
|
||||||
on_selection_change=self.list_selection,
|
on_selection_change=self.list_selection,
|
||||||
initialization_is_selection_change=False,
|
initialization_is_selection_change=False,
|
||||||
@@ -433,6 +435,8 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
self.parent.left_pile.contents[0] = (self.parent.known_nodes_display, options)
|
self.parent.left_pile.contents[0] = (self.parent.known_nodes_display, options)
|
||||||
|
|
||||||
def connect(sender):
|
def connect(sender):
|
||||||
|
# TODO: Remove when new mitigation has been tested
|
||||||
|
# self.app.ui.main_display.request_redraw(extra_delay=0.75)
|
||||||
self.parent.browser.retrieve_url(RNS.hexrep(source_hash, delimit=False))
|
self.parent.browser.retrieve_url(RNS.hexrep(source_hash, delimit=False))
|
||||||
show_known_nodes(None)
|
show_known_nodes(None)
|
||||||
|
|
||||||
@@ -496,6 +500,21 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
|||||||
urwid.WidgetWrap.__init__(self, urwid.LineBox(self.display_widget, title="Node Info"))
|
urwid.WidgetWrap.__init__(self, urwid.LineBox(self.display_widget, title="Node Info"))
|
||||||
|
|
||||||
|
|
||||||
|
# Yes, this is weird. There is a bug in Urwid/ILB that causes
|
||||||
|
# an indexing exception when the list is very small vertically.
|
||||||
|
# This mitigates it.
|
||||||
|
class ExceptionHandlingListBox(IndicativeListBox):
|
||||||
|
def keypress(self, size, key):
|
||||||
|
try:
|
||||||
|
return super(ExceptionHandlingListBox, self).keypress(size, key)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if key == "up":
|
||||||
|
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
||||||
|
elif key == "down":
|
||||||
|
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.sub_displays.network_display.left_pile.set_focus(1)
|
||||||
|
|
||||||
|
|
||||||
class KnownNodes(urwid.WidgetWrap):
|
class KnownNodes(urwid.WidgetWrap):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
@@ -504,7 +523,7 @@ class KnownNodes(urwid.WidgetWrap):
|
|||||||
|
|
||||||
self.widget_list = self.make_node_widgets()
|
self.widget_list = self.make_node_widgets()
|
||||||
|
|
||||||
self.ilb = IndicativeListBox(
|
self.ilb = ExceptionHandlingListBox(
|
||||||
self.widget_list,
|
self.widget_list,
|
||||||
on_selection_change=self.node_list_selection,
|
on_selection_change=self.node_list_selection,
|
||||||
initialization_is_selection_change=False,
|
initialization_is_selection_change=False,
|
||||||
@@ -576,7 +595,9 @@ class KnownNodes(urwid.WidgetWrap):
|
|||||||
self.delegate.left_pile.contents[0] = (overlay, options)
|
self.delegate.left_pile.contents[0] = (overlay, options)
|
||||||
|
|
||||||
def delete_selected_entry(self):
|
def delete_selected_entry(self):
|
||||||
source_hash = self.ilb.get_selected_item().original_widget.source_hash
|
si = self.ilb.get_selected_item()
|
||||||
|
if si != None:
|
||||||
|
source_hash = si.original_widget.source_hash
|
||||||
|
|
||||||
def dismiss_dialog(sender):
|
def dismiss_dialog(sender):
|
||||||
self.delegate.close_list_dialogs()
|
self.delegate.close_list_dialogs()
|
||||||
@@ -680,7 +701,7 @@ class AnnounceTime(urwid.WidgetWrap):
|
|||||||
if self.app.peer_settings["last_announce"] != None:
|
if self.app.peer_settings["last_announce"] != None:
|
||||||
self.last_announce_string = pretty_date(int(self.app.peer_settings["last_announce"]))
|
self.last_announce_string = pretty_date(int(self.app.peer_settings["last_announce"]))
|
||||||
|
|
||||||
self.display_widget.set_text("Last Announce : "+self.last_announce_string)
|
self.display_widget.set_text("Announced : "+self.last_announce_string)
|
||||||
|
|
||||||
def update_time_callback(self, loop=None, user_data=None):
|
def update_time_callback(self, loop=None, user_data=None):
|
||||||
self.update_time()
|
self.update_time()
|
||||||
@@ -922,7 +943,7 @@ class NodeInfo(urwid.WidgetWrap):
|
|||||||
self.t_active_links = NodeInfo.links_timer
|
self.t_active_links = NodeInfo.links_timer
|
||||||
self.t_active_links.update_stat()
|
self.t_active_links.update_stat()
|
||||||
|
|
||||||
announce_button = urwid.Button("Announce Now", on_press=announce_query)
|
announce_button = urwid.Button("Announce", on_press=announce_query)
|
||||||
connect_button = urwid.Button("Browse", on_press=connect_query)
|
connect_button = urwid.Button("Browse", on_press=connect_query)
|
||||||
|
|
||||||
pile = urwid.Pile([
|
pile = urwid.Pile([
|
||||||
@@ -935,9 +956,9 @@ class NodeInfo(urwid.WidgetWrap):
|
|||||||
urwid.Columns([
|
urwid.Columns([
|
||||||
("weight", 0.3, urwid.Button("Back", on_press=show_peer_info)),
|
("weight", 0.3, urwid.Button("Back", on_press=show_peer_info)),
|
||||||
("weight", 0.1, urwid.Text("")),
|
("weight", 0.1, urwid.Text("")),
|
||||||
("weight", 0.3, connect_button),
|
("weight", 0.4, connect_button),
|
||||||
("weight", 0.1, urwid.Text("")),
|
("weight", 0.1, urwid.Text("")),
|
||||||
("weight", 0.3, announce_button)
|
("weight", 0.5, announce_button)
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
@@ -1002,7 +1023,7 @@ class NetworkStats(urwid.WidgetWrap):
|
|||||||
def get_num_nodes():
|
def get_num_nodes():
|
||||||
return self.app.directory.number_of_known_nodes()
|
return self.app.directory.number_of_known_nodes()
|
||||||
|
|
||||||
self.w_heard_peers = UpdatingText(self.app, "Heard Peers: ", get_num_peers, append_text=" (last 30m)")
|
self.w_heard_peers = UpdatingText(self.app, "Heard Peers: ", get_num_peers, append_text=" (30m)")
|
||||||
self.w_known_nodes = UpdatingText(self.app, "Known Nodes: ", get_num_nodes)
|
self.w_known_nodes = UpdatingText(self.app, "Known Nodes: ", get_num_nodes)
|
||||||
|
|
||||||
pile = urwid.Pile([
|
pile = urwid.Pile([
|
||||||
@@ -1022,6 +1043,9 @@ class NetworkLeftPile(urwid.Pile):
|
|||||||
def keypress(self, size, key):
|
def keypress(self, size, key):
|
||||||
if key == "ctrl l":
|
if key == "ctrl l":
|
||||||
self.parent.toggle_list()
|
self.parent.toggle_list()
|
||||||
|
elif key == "ctrl p":
|
||||||
|
self.parent.reinit_lxmf_peers()
|
||||||
|
self.parent.show_peers()
|
||||||
elif key == "ctrl w":
|
elif key == "ctrl w":
|
||||||
self.parent.browser.disconnect()
|
self.parent.browser.disconnect()
|
||||||
elif key == "ctrl u":
|
elif key == "ctrl u":
|
||||||
@@ -1043,6 +1067,7 @@ class NetworkDisplay():
|
|||||||
self.browser.loopback = self.app.node.destination.hash
|
self.browser.loopback = self.app.node.destination.hash
|
||||||
|
|
||||||
self.known_nodes_display = KnownNodes(self.app)
|
self.known_nodes_display = KnownNodes(self.app)
|
||||||
|
self.lxmf_peers_display = LXMFPeers(self.app)
|
||||||
self.network_stats_display = NetworkStats(self.app, self)
|
self.network_stats_display = NetworkStats(self.app, self)
|
||||||
self.announce_stream_display = AnnounceStream(self.app, self)
|
self.announce_stream_display = AnnounceStream(self.app, self)
|
||||||
self.local_peer_display = LocalPeer(self.app, self)
|
self.local_peer_display = LocalPeer(self.app, self)
|
||||||
@@ -1084,6 +1109,15 @@ class NetworkDisplay():
|
|||||||
self.left_pile.contents[0] = (self.known_nodes_display, options)
|
self.left_pile.contents[0] = (self.known_nodes_display, options)
|
||||||
self.list_display = 1
|
self.list_display = 1
|
||||||
|
|
||||||
|
def show_peers(self):
|
||||||
|
options = self.left_pile.options(height_type="weight", height_amount=1)
|
||||||
|
self.left_pile.contents[0] = (self.lxmf_peers_display, options)
|
||||||
|
|
||||||
|
if self.list_display != 0:
|
||||||
|
self.list_display = 0
|
||||||
|
else:
|
||||||
|
self.list_display = 1
|
||||||
|
|
||||||
def focus_lists(self):
|
def focus_lists(self):
|
||||||
self.columns.focus_position = 0
|
self.columns.focus_position = 0
|
||||||
|
|
||||||
@@ -1093,6 +1127,11 @@ class NetworkDisplay():
|
|||||||
self.close_list_dialogs()
|
self.close_list_dialogs()
|
||||||
self.announce_stream_display.rebuild_widget_list()
|
self.announce_stream_display.rebuild_widget_list()
|
||||||
|
|
||||||
|
def reinit_lxmf_peers(self):
|
||||||
|
self.lxmf_peers_display = LXMFPeers(self.app)
|
||||||
|
self.lxmf_peers_display.delegate = self
|
||||||
|
self.close_list_dialogs()
|
||||||
|
|
||||||
def close_list_dialogs(self):
|
def close_list_dialogs(self):
|
||||||
if self.list_display == 0:
|
if self.list_display == 0:
|
||||||
options = self.left_pile.options(height_type="weight", height_amount=1)
|
options = self.left_pile.options(height_type="weight", height_amount=1)
|
||||||
@@ -1121,6 +1160,103 @@ class NetworkDisplay():
|
|||||||
self.known_nodes_display.rebuild_widget_list()
|
self.known_nodes_display.rebuild_widget_list()
|
||||||
|
|
||||||
|
|
||||||
|
class LXMFPeers(urwid.WidgetWrap):
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.peer_list = app.message_router.peers
|
||||||
|
# self.peer_list = {}
|
||||||
|
|
||||||
|
g = self.app.ui.glyphs
|
||||||
|
|
||||||
|
self.widget_list = self.make_peer_widgets()
|
||||||
|
|
||||||
|
self.ilb = IndicativeListBox(
|
||||||
|
self.widget_list,
|
||||||
|
on_selection_change=self.node_list_selection,
|
||||||
|
initialization_is_selection_change=False,
|
||||||
|
highlight_offFocus="list_off_focus"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(self.peer_list) > 0:
|
||||||
|
self.display_widget = self.ilb
|
||||||
|
widget_style = None
|
||||||
|
self.no_content = False
|
||||||
|
else:
|
||||||
|
self.no_content = True
|
||||||
|
widget_style = "inactive_text"
|
||||||
|
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 Peers"), widget_style))
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
|
||||||
|
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
||||||
|
elif key == "ctrl x":
|
||||||
|
self.delete_selected_entry()
|
||||||
|
|
||||||
|
return super(LXMFPeers, self).keypress(size, key)
|
||||||
|
|
||||||
|
|
||||||
|
def node_list_selection(self, arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_selected_entry(self):
|
||||||
|
si = self.ilb.get_selected_item()
|
||||||
|
if si != None:
|
||||||
|
destination_hash = si.original_widget.destination_hash
|
||||||
|
self.app.message_router.unpeer(destination_hash)
|
||||||
|
self.delegate.reinit_lxmf_peers()
|
||||||
|
self.delegate.show_peers()
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild_widget_list(self):
|
||||||
|
self.peer_list = self.app.message_router.peers
|
||||||
|
self.widget_list = self.make_peer_widgets()
|
||||||
|
self.ilb.set_body(self.widget_list)
|
||||||
|
if len(self.widget_list) > 0:
|
||||||
|
self.no_content = False
|
||||||
|
else:
|
||||||
|
self.no_content = True
|
||||||
|
self.delegate.reinit_lxmf_peers()
|
||||||
|
|
||||||
|
def make_peer_widgets(self):
|
||||||
|
widget_list = []
|
||||||
|
for peer_id in self.peer_list:
|
||||||
|
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):
|
||||||
|
def __init__(self, app, peer, delegate):
|
||||||
|
destination_hash = peer.destination_hash
|
||||||
|
|
||||||
|
self.app = app
|
||||||
|
g = self.app.ui.glyphs
|
||||||
|
|
||||||
|
node_identity = RNS.Identity.recall(destination_hash)
|
||||||
|
display_str = RNS.prettyhexrep(destination_hash)
|
||||||
|
if node_identity != None:
|
||||||
|
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 += "\n "+str(display_name)
|
||||||
|
|
||||||
|
sym = g["sent"]
|
||||||
|
style = "list_unknown"
|
||||||
|
focus_style = "list_focus"
|
||||||
|
|
||||||
|
widget = ListEntry(sym+" "+display_str+"\n Last heard "+pretty_date(int(peer.last_heard))+"\n "+str(len(peer.unhandled_messages))+" unhandled messages")
|
||||||
|
# urwid.connect_signal(widget, "click", delegate.connect_node, node)
|
||||||
|
|
||||||
|
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||||
|
self.display_widget.destination_hash = destination_hash
|
||||||
|
urwid.WidgetWrap.__init__(self, self.display_widget)
|
||||||
|
|
||||||
|
|
||||||
def pretty_date(time=False):
|
def pretty_date(time=False):
|
||||||
"""
|
"""
|
||||||
|
|||||||
2
nomadnet/vendor/configobj.py
vendored
2
nomadnet/vendor/configobj.py
vendored
@@ -19,7 +19,7 @@ import sys
|
|||||||
|
|
||||||
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
|
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
|
||||||
|
|
||||||
import six
|
import RNS.vendor.six as six
|
||||||
__version__ = '5.0.6'
|
__version__ = '5.0.6'
|
||||||
|
|
||||||
# imported lazily to avoid startup performance hit if it isn't used
|
# imported lazily to avoid startup performance hit if it isn't used
|
||||||
|
|||||||
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.7', 'lxmf>=0.1.0', 'urwid>=2.1.2'],
|
install_requires=['rns>=0.3.0', 'lxmf>=0.1.3', 'urwid>=2.1.2'],
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user