Compare commits

...

18 Commits
0.1.7 ... 0.1.8

Author SHA1 Message Date
Mark Qvist
b312bd97e0 Run nomadnet as daemon in docker 2022-05-17 22:44:38 +02:00
Mark Qvist
d8e84409c7 Fixed dockerfile 2022-05-17 22:43:08 +02:00
Mark Qvist
a09d79f27c Updated readme 2022-05-17 22:34:35 +02:00
Mark Qvist
3ec82b2775 Updated guide 2022-05-17 19:42:01 +02:00
Mark Qvist
f92858866a Added destination ignore functionality 2022-05-17 19:21:14 +02:00
Mark Qvist
13484a665a Improved link rendering and target handling in text blocks and with alignment. Fixes #4. 2022-05-17 18:37:29 +02:00
Mark Qvist
3cd336ab90 Improved local node and peer info 2022-05-17 16:12:00 +02:00
Mark Qvist
2e085d2d8d Improved announce info display 2022-05-17 14:42:46 +02:00
Mark Qvist
1cdea6c1ab Improved node info display 2022-05-17 14:33:37 +02:00
Mark Qvist
2b58a7840a Improved LXMF peer display 2022-05-17 14:15:29 +02:00
Mark Qvist
bc7231def0 Updated readme and dockerfile 2022-05-17 13:19:35 +02:00
Mark Qvist
73fb44dcc3 Updated readme and dockerfile 2022-05-17 13:18:55 +02:00
Mark Qvist
467f02b379 Merge branch 'master' of github.com:markqvist/NomadNet 2022-05-17 13:11:15 +02:00
Mark Qvist
c6e26e7f7f Added daemon mode 2022-05-17 13:11:04 +02:00
markqvist
28eb7a9731 Merge pull request #5 from jphastings/dockerfile
Automated docker build for nomadnet daemon
2022-05-03 21:15:47 +02:00
JP Hastings-Spital
a917cbf010 Automated docker build for nomadnet daemon
Uses a Github Action (`.github/workflows/publish-container.yml`) to create a
docker container image (from `Dockerfile`) that represents an extremely minimal
python installation with a virtualenv holding all requirements necessary to
execute `nomadnet`.

New docker images are created on pushes to `master` or pushes to tags
matching `*.*.*` (ie. version tags) and are retrievable with those tags.

Examples:

```sh
$ docker pull ghcr.io/markqvist/nomadnet:master

# Print docker labels, to demonstrate the image has been retrieved
$ docker inspect -f '{{json .Config.Labels}}' ghcr.io/markqvist/nomadnet:master | jq
{
  "org.opencontainers.image.created": "2022-04-27T06:01:55.894Z",
  "org.opencontainers.image.description": "Communicate Freely",
  "org.opencontainers.image.licenses": "GPL-3.0",
  "org.opencontainers.image.revision": "59cffc4a9de0f276d2cc87537ff1316aed5f16dd",
  "org.opencontainers.image.source": "https://github.com/markqvist/NomadNet",
  "org.opencontainers.image.title": "NomadNet",
  "org.opencontainers.image.url": "https://github.com/markqvist/NomadNet",
  "org.opencontainers.image.version": "master"
}

# Run nomadnet interactively without installing it (with default config)
$ docker run -it ghcr.io/markqvist/nomadnet:master

# Run nomadnet as a daemon, using config stored on the host machine in specific directories
$ docker run -d -v /local/path/nomadnetconfig/:/root/.nomadnetwork/ -v /local/path/reticulumconfig/:/root/.reticulum/:rw ghcr.io/markqvist/nomadnet:master
```

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# Date:      Tue Apr 26 23:50:22 2022 +0100
#
# On branch dockerfile
# Changes to be committed:
#	new file:   .dockerignore
#	new file:   .github/workflows/publish-container.yml
#	new file:   Dockerfile
#	modified:   README.md
#

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# Date:      Tue Apr 26 23:50:22 2022 +0100
#
# On branch dockerfile
# Changes to be committed:
#	new file:   .dockerignore
#	new file:   .github/workflows/publish-container.yml
#	new file:   Dockerfile
#	modified:   README.md
#

# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# Date:      Tue Apr 26 23:50:22 2022 +0100
#
# On branch dockerfile
# Changes to be committed:
#	new file:   .dockerignore
#	new file:   .github/workflows/publish-container.yml
#	new file:   Dockerfile
#	modified:   README.md
#
2022-04-27 21:42:59 +01:00
Mark Qvist
34b377bafe Fixed typo 2022-04-20 21:17:52 +02:00
markqvist
6d7e8c413e Update README.md 2022-04-08 11:20:29 +02:00
17 changed files with 499 additions and 83 deletions

1
.dockerignore Symbolic link
View File

@@ -0,0 +1 @@
.gitignore

42
.github/workflows/publish-container.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Create and publish a Docker image
on:
push:
branches: ['master']
tags: ['*.*.*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM python:3.11-rc-alpine3.14 as build
RUN apk add --no-cache build-base linux-headers libffi-dev cargo
# Create a virtualenv that we'll copy to the published image
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip3 install setuptools-rust pyopenssl cryptography
COPY . /app/
RUN cd /app/ && python3 setup.py install
# Use multi-stage build, as we don't need rust compilation on the final image
FROM python:3.11-rc-alpine3.14
LABEL org.opencontainers.image.documentation="https://github.com/markqvist/NomadNet#nomad-network-daemon-with-docker"
ENV PATH="/opt/venv/bin:$PATH"
COPY --from=build /opt/venv /opt/venv
VOLUME /root/.reticulum
VOLUME /root/.nomadnetwork
ENTRYPOINT ["nomadnet", "--daemon"]

View File

@@ -25,9 +25,8 @@ Nomad Network does not need any connections to the public internet to work. In f
The current version of the program should be considered a beta release. The program works well, but there will most probably be bugs and possibly sub-optimal performance in some scenarios. On the other hand, this is the ideal time to have an influence on the direction of the development of Nomad Network. To do so, join the discussion, report bugs and request features here on the GitHub project. The current version of the program should be considered a beta release. The program works well, but there will most probably be bugs and possibly sub-optimal performance in some scenarios. On the other hand, this is the ideal time to have an influence on the direction of the development of Nomad Network. To do so, join the discussion, report bugs and request features here on the GitHub project.
### Feature roadmap ### Feature roadmap
- Access control and authentication for nodes, pages and files
- Network-wide propagated bulletins and discussion threads - Network-wide propagated bulletins and discussion threads
- Collaborative maps - Collaborative maps and geospatial information sharing
- Facilitation of trade and barter - Facilitation of trade and barter
## How do I get started? ## How do I get started?
@@ -39,6 +38,12 @@ pip3 install nomadnet
# Run the client # Run the client
nomadnet nomadnet
# Or alternatively run as a daemon, with no user interface
nomadnet --daemon
# List options
nomadnet --help
``` ```
The first time the program is running, you will be presented with the guide section, which contains all the information you need to start using Nomad Network. The first time the program is running, you will be presented with the guide section, which contains all the information you need to start using Nomad Network.
@@ -49,11 +54,33 @@ If you want to try Nomad Network without building your own physical network, you
**Please Note**: If this is the very first time you use pip to install a program on your system, you might need to reboot your system for the program to become available. If you get a "command not found" error or similar when running the program, reboot your system and try again. **Please Note**: If this is the very first time you use pip to install a program on your system, you might need to reboot your system for the program to become available. If you get a "command not found" error or similar when running the program, reboot your system and try again.
### Installing on Android ### Install on Android
You can install Nomad Network on Android using Termux, but there's a few more 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. You can install Nomad Network on Android using Termux, but there's a few more 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.
For a native Android application with a graphical user interface, have a look at [Sideband](https://unsigned.io/sideband). For a native Android application with a graphical user interface, have a look at [Sideband](https://unsigned.io/sideband).
### Nomad Network Daemon with Docker
Nomad Network is automatically published as a docker image on Github Packages. Image tags are one of either `master` (for the latest release) or the version number (eg `0.1.7`) for the specified version number (as tagged in this git repo).
```sh
$ docker pull ghcr.io/markqvist/nomadnet:master
# Run nomadnet interactively without installing it (with default config)
$ docker run -it ghcr.io/markqvist/nomadnet:master
# Run nomadnet as a daemon, using config stored on the host machine in specific
# directories, and allowing access to specific numbered ports openned by nomadnet
# on the host machine on the same port numbers.
$ docker run -d \
-v /local/path/nomadnetconfig/:/root/.nomadnetwork/ \
-v /local/path/reticulumconfig/:/root/.reticulum/ \
-p 37428:37428 \
-p 37429:37429 \
ghcr.io/markqvist/nomadnet:master
```
## Help & Discussion ## Help & Discussion
For help requests, discussion, sharing ideas or anything else related to Nomad Network, please have a look at the [Nomad Network discussions pages](https://github.com/markqvist/Reticulum/discussions/categories/nomad-network). For help requests, discussion, sharing ideas or anything else related to Nomad Network, please have a look at the [Nomad Network discussions pages](https://github.com/markqvist/Reticulum/discussions/categories/nomad-network).

View File

@@ -14,20 +14,25 @@ class Conversation:
@staticmethod @staticmethod
def received_announce(destination_hash, announced_identity, app_data): def received_announce(destination_hash, announced_identity, app_data):
app = nomadnet.NomadNetworkApp.get_shared_instance() app = nomadnet.NomadNetworkApp.get_shared_instance()
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
# Check if the announced destination is in
# our list of conversations
if destination_hash_text in [e[0] for e in Conversation.conversation_list(app)]:
if app.directory.find(destination_hash):
if Conversation.created_callback != None:
Conversation.created_callback()
else:
if Conversation.created_callback != None:
Conversation.created_callback()
# Add the announce to the directory announce if not destination_hash in app.ignored_list:
# stream logger destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
app.directory.lxmf_announce_received(destination_hash, app_data) # Check if the announced destination is in
# our list of conversations
if destination_hash_text in [e[0] for e in Conversation.conversation_list(app)]:
if app.directory.find(destination_hash):
if Conversation.created_callback != None:
Conversation.created_callback()
else:
if Conversation.created_callback != None:
Conversation.created_callback()
# Add the announce to the directory announce
# stream logger
app.directory.lxmf_announce_received(destination_hash, app_data)
else:
RNS.log("Ignored announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
@staticmethod @staticmethod
def query_for_peer(source_hash): def query_for_peer(source_hash):
@@ -66,7 +71,8 @@ class Conversation:
except Exception as e: except Exception as e:
pass pass
Conversation.created_callback() if Conversation.created_callback != None:
Conversation.created_callback()
return ingested_path return ingested_path

View File

@@ -12,12 +12,17 @@ class Directory:
@staticmethod @staticmethod
def received_announce(destination_hash, announced_identity, app_data): def received_announce(destination_hash, announced_identity, app_data):
app = nomadnet.NomadNetworkApp.get_shared_instance() app = nomadnet.NomadNetworkApp.get_shared_instance()
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity) if not destination_hash in app.ignored_list:
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
app.directory.node_announce_received(destination_hash, app_data, associated_peer) associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
app.autoselect_propagation_node()
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)
def __init__(self, app): def __init__(self, app):
@@ -86,7 +91,9 @@ class Directory:
self.announce_stream.insert(0, (timestamp, source_hash, app_data, False)) self.announce_stream.insert(0, (timestamp, source_hash, app_data, False))
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()
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: if app_data != None:
@@ -101,7 +108,8 @@ class Directory:
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True) node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
self.remember(node_entry) self.remember(node_entry)
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 remove_announce_with_timestamp(self, timestamp): def remove_announce_with_timestamp(self, timestamp):
selected_announce = None selected_announce = None

View File

@@ -24,6 +24,8 @@ class Node:
self.register_pages() self.register_pages()
self.register_files() self.register_files()
self.destination.set_link_established_callback(self.peer_connected)
if self.name == None: if self.name == None:
self.name = self.app.peer_settings["display_name"]+"'s Node" self.name = self.app.peer_settings["display_name"]+"'s Node"
@@ -97,6 +99,12 @@ class Node:
def serve_page(self, path, data, request_id, remote_identity, requested_at): def serve_page(self, path, data, request_id, remote_identity, requested_at):
RNS.log("Page request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE) RNS.log("Page request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["served_page_requests"] += 1
self.app.save_peer_settings()
except Exception as e:
RNS.log("Could not increase served page request count", RNS.LOG_ERROR)
file_path = path.replace("/page", self.app.pagespath, 1) file_path = path.replace("/page", self.app.pagespath, 1)
@@ -163,6 +171,13 @@ class Node:
# TODO: Improve file handling, this will be slow for large files # TODO: Improve file handling, this will be slow for large files
def serve_file(self, path, data, request_id, remote_identity, requested_at): def serve_file(self, path, data, request_id, remote_identity, requested_at):
RNS.log("File request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE) RNS.log("File request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["served_file_requests"] += 1
self.app.save_peer_settings()
except Exception as e:
RNS.log("Could not increase served file request count", RNS.LOG_ERROR)
file_path = path.replace("/file", self.app.filespath, 1) file_path = path.replace("/file", self.app.filespath, 1)
file_name = path.replace("/file/", "", 1) file_name = path.replace("/file/", "", 1)
try: try:
@@ -197,10 +212,20 @@ class Node:
time.sleep(self.job_interval) time.sleep(self.job_interval)
def peer_connected(link): def peer_connected(self, link):
RNS.log("Peer connected to "+str(self.destination), RNS.LOG_INFO) RNS.log("Peer connected to "+str(self.destination), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["node_connects"] += 1
self.app.save_peer_settings()
except Exception as e:
RNS.log("Could not increase node connection count", RNS.LOG_ERROR)
link.set_link_closed_callback(self.peer_disconnected) link.set_link_closed_callback(self.peer_disconnected)
def peer_disconnected(self, link):
RNS.log("Peer disconnected from "+str(self.destination), RNS.LOG_VERBOSE)
pass
DEFAULT_INDEX = '''>Default Home Page DEFAULT_INDEX = '''>Default Home Page

View File

@@ -46,7 +46,7 @@ class NomadNetworkApp:
if issubclass(e_type, KeyboardInterrupt): if issubclass(e_type, KeyboardInterrupt):
sys.__excepthook__(e_type, e_value, e_traceback) sys.__excepthook__(e_type, e_value, e_traceback)
def __init__(self, configdir = None, rnsconfigdir = None): def __init__(self, configdir = None, rnsconfigdir = None, daemon = False):
self.version = __version__ self.version = __version__
self.enable_client = False self.enable_client = False
self.enable_node = False self.enable_node = False
@@ -65,6 +65,7 @@ class NomadNetworkApp:
self.rns = RNS.Reticulum(configdir = rnsconfigdir) self.rns = RNS.Reticulum(configdir = rnsconfigdir)
self.configpath = self.configdir+"/config" self.configpath = self.configdir+"/config"
self.ignoredpath = self.configdir+"/ignored"
self.logfilepath = self.configdir+"/logfile" self.logfilepath = self.configdir+"/logfile"
self.errorfilepath = self.configdir+"/errors" self.errorfilepath = self.configdir+"/errors"
self.storagepath = self.configdir+"/storage" self.storagepath = self.configdir+"/storage"
@@ -171,6 +172,15 @@ class NomadNetworkApp:
if not "last_lxmf_sync" in self.peer_settings: if not "last_lxmf_sync" in self.peer_settings:
self.peer_settings["last_lxmf_sync"] = 0 self.peer_settings["last_lxmf_sync"] = 0
if not "node_connects" in self.peer_settings:
self.peer_settings["node_connects"] = 0
if not "served_page_requests" in self.peer_settings:
self.peer_settings["served_page_requests"] = 0
if not "served_file_requests" in self.peer_settings:
self.peer_settings["served_file_requests"] = 0
except Exception as e: except Exception as e:
RNS.log("Could not load local peer settings from "+self.peersettingspath, RNS.LOG_ERROR) RNS.log("Could not load local peer settings from "+self.peersettingspath, RNS.LOG_ERROR)
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR) RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
@@ -193,12 +203,36 @@ class NomadNetworkApp:
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR) RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
nomadnet.panic() nomadnet.panic()
self.ignored_list = []
if os.path.isfile(self.ignoredpath):
try:
fh = open(self.ignoredpath, "rb")
ignored_input = fh.read()
fh.close()
ignored_hash_strs = ignored_input.splitlines()
for hash_str in ignored_hash_strs:
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
try:
ignored_hash = bytes.fromhex(hash_str.decode("utf-8"))
self.ignored_list.append(ignored_hash)
except Exception as e:
RNS.log("Could not decode RNS Identity hash from: "+str(hash_str), RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while fetching loading list of ignored destinations: "+str(e), RNS.LOG_ERROR)
self.directory = nomadnet.Directory(self) self.directory = nomadnet.Directory(self)
self.message_router = LXMF.LXMRouter(identity = self.identity, storagepath = self.storagepath, autopeer = True) self.message_router = LXMF.LXMRouter(identity = self.identity, storagepath = self.storagepath, autopeer = True)
self.message_router.register_delivery_callback(self.lxmf_delivery) self.message_router.register_delivery_callback(self.lxmf_delivery)
for destination_hash in self.ignored_list:
self.message_router.ignore_destination(destination_hash)
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"])
self.lxmf_destination.set_default_app_data(self.get_display_name_bytes) self.lxmf_destination.set_default_app_data(self.get_display_name_bytes)
@@ -239,6 +273,10 @@ class NomadNetworkApp:
job_thread.setDaemon(True) job_thread.setDaemon(True)
job_thread.start() job_thread.start()
# Override UI choice from config on --daemon switch
if daemon:
self.uimode = nomadnet.ui.UI_NONE
# This stderr redirect is needed to stop urwid # This stderr redirect is needed to stop urwid
# from spewing KeyErrors to the console and thus, # from spewing KeyErrors to the console and thus,
# messing up the UI. A pull request to fix the # messing up the UI. A pull request to fix the
@@ -367,7 +405,7 @@ class NomadNetworkApp:
selected_node = node selected_node = node
if selected_node == None: 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) RNS.log("Could not autoselect a propagation node! LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING)
else: else:
node_identity = RNS.Identity.recall(selected_node.source_hash) node_identity = RNS.Identity.recall(selected_node.source_hash)
if node_identity != None: if node_identity != None:

View File

@@ -1 +1 @@
__version__ = "0.1.7" __version__ = "0.1.8"

View File

@@ -7,14 +7,15 @@ import argparse
import nomadnet import nomadnet
def program_setup(configdir, rnsconfigdir): def program_setup(configdir, rnsconfigdir, daemon):
app = nomadnet.NomadNetworkApp(configdir = configdir, rnsconfigdir = rnsconfigdir) app = nomadnet.NomadNetworkApp(configdir = configdir, rnsconfigdir = rnsconfigdir, daemon = daemon)
def main(): def main():
try: try:
parser = argparse.ArgumentParser(description="Nomad Network Client") parser = argparse.ArgumentParser(description="Nomad Network Client")
parser.add_argument("--config", action="store", default=None, help="path to alternative Nomad Network config directory", type=str) parser.add_argument("--config", action="store", default=None, help="path to alternative Nomad Network config directory", type=str)
parser.add_argument("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str) parser.add_argument("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument("-d", "--daemon", action="store_true", default=False, help="run Nomad Network in daemon mode")
parser.add_argument("--version", action="version", version="Nomad Network Client {version}".format(version=__version__)) parser.add_argument("--version", action="version", version="Nomad Network Client {version}".format(version=__version__))
args = parser.parse_args() args = parser.parse_args()
@@ -29,7 +30,7 @@ def main():
else: else:
rnsconfigarg = None rnsconfigarg = None
program_setup(configarg, rnsconfigarg) program_setup(configarg, rnsconfigarg, args.daemon)
except KeyboardInterrupt: except KeyboardInterrupt:
print("") print("")

View File

@@ -4,5 +4,5 @@ import nomadnet
class MenuUI: class MenuUI:
def __init__(self): def __init__(self):
RNS.log("Menu UI not implemented", RNS.LOG_ERROR) RNS.log("Menu UI not implemented", RNS.LOG_ERROR, _override_destination=True)
nomadnet.panic() nomadnet.panic()

16
nomadnet/ui/NoneUI.py Normal file
View File

@@ -0,0 +1,16 @@
import RNS
import nomadnet
import time
from nomadnet import NomadNetworkApp
class NoneUI:
def __init__(self):
self.app = NomadNetworkApp.get_shared_instance()
self.app.ui = self
RNS.log("Nomad Network started in daemon mode, all further messages are logged to "+str(self.app.logfilepath), RNS.LOG_INFO, _override_destination=True)
while True:
time.sleep(1)

View File

@@ -12,11 +12,15 @@ UI_MENU = 0x01
UI_TEXT = 0x02 UI_TEXT = 0x02
UI_GRAPHICAL = 0x03 UI_GRAPHICAL = 0x03
UI_WEB = 0x04 UI_WEB = 0x04
UI_MODES = [UI_MENU, UI_TEXT, UI_GRAPHICAL, UI_WEB] UI_MODES = [UI_NONE, UI_MENU, UI_TEXT, UI_GRAPHICAL, UI_WEB]
def spawn(uimode): def spawn(uimode):
if uimode in UI_MODES: if uimode in UI_MODES:
RNS.log("Starting user interface...", RNS.LOG_INFO) if uimode == UI_NONE:
RNS.log("Starting Nomad Network daemon...", RNS.LOG_INFO)
else:
RNS.log("Starting user interface...", RNS.LOG_INFO)
if uimode == UI_MENU: if uimode == UI_MENU:
from .MenuUI import MenuUI from .MenuUI import MenuUI
return MenuUI() return MenuUI()
@@ -29,8 +33,11 @@ def spawn(uimode):
elif uimode == UI_WEB: elif uimode == UI_WEB:
from .WebUI import WebUI from .WebUI import WebUI
return WebUI() return WebUI()
elif uimode == UI_NONE:
from .NoneUI import NoneUI
return NoneUI()
else: else:
return None return None
else: else:
RNS.log("Invalid UI mode", RNS.LOG_ERROR) RNS.log("Invalid UI mode", RNS.LOG_ERROR, _override_destination=True)
nomadnet.panic() nomadnet.panic()

View File

@@ -551,6 +551,10 @@ Determines where the node server will look for hosted pages. Must be a readable
>>>> >>>>
Determines where the node server will look for downloadable files. Must be a readable filesystem path. Determines where the node server will look for downloadable files. Must be a readable filesystem path.
< <
>Ignoring Destinations
If you encounter peers or nodes on the network, that you would rather not see in your client, you can add them to the `!~/.nomadnetwork/ignored`! file. To ignore nodes or peers, add one 20-character hexadecimal destination hash per line to the file. To unignore one again, simply remove the corresponding entry from the file and restart Nomad Network.
''' '''
TOPIC_NETWORKS = '''>Network Configuration TOPIC_NETWORKS = '''>Network Configuration

View File

@@ -528,7 +528,6 @@ class LinkableText(urwid.Text):
if self.delegate != None: if self.delegate != None:
self.delegate.handle_link(link_target) self.delegate.handle_link(link_target)
def find_next_part_pos(self, pos, part_positions): def find_next_part_pos(self, pos, part_positions):
for position in part_positions: for position in part_positions:
if position > pos: if position > pos:
@@ -596,7 +595,12 @@ class LinkableText(urwid.Text):
return key return key
elif key == "right": elif key == "right":
old = self._cursor_position
self._cursor_position = self.find_next_part_pos(self._cursor_position, part_positions) self._cursor_position = self.find_next_part_pos(self._cursor_position, part_positions)
if self._cursor_position == old:
self._cursor_position = 0
return "down"
self._invalidate() self._invalidate()
elif key == "left": elif key == "left":
@@ -641,9 +645,33 @@ class LinkableText(urwid.Text):
if button != 1 or not is_mouse_press(event): if button != 1 or not is_mouse_press(event):
return False return False
else: else:
pos = (y * size[0]) + x (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]
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 self._cursor_position = pos
item = self.find_item_at_pos(self._cursor_position) item = self.find_item_at_pos(self._cursor_position)
if item != None: if item != None:
if isinstance(item, LinkSpec): if isinstance(item, LinkSpec):
self.handle_link(item.link_target) self.handle_link(item.link_target)

View File

@@ -118,6 +118,33 @@ class AnnounceInfo(urwid.WidgetWrap):
self.app.ui.main_display.sub_displays.network_display.directory_change_callback() self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
show_announce_stream(None) show_announce_stream(None)
if is_node:
node_ident = RNS.Identity.recall(source_hash)
op_hash = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", node_ident)
op_str = self.app.directory.simplest_display_str(op_hash)
def msg_op(sender):
show_announce_stream(None)
if is_node:
try:
existing_conversations = nomadnet.Conversation.conversation_list(self.app)
source_hash_text = RNS.hexrep(op_hash, delimit=False)
display_name = op_str
if not source_hash_text in [c[0] for c in existing_conversations]:
entry = DirectoryEntry(source_hash, display_name, trust_level)
self.app.directory.remember(entry)
new_conversation = nomadnet.Conversation(source_hash_text, nomadnet.NomadNetworkApp.get_shared_instance(), initiator=True)
self.app.ui.main_display.sub_displays.conversations_display.update_conversation_list()
self.app.ui.main_display.sub_displays.conversations_display.display_conversation(None, source_hash_text)
self.app.ui.main_display.show_conversations(None)
except Exception as e:
RNS.log("Error while starting conversation from announce. The contained exception was: "+str(e), RNS.LOG_ERROR)
def converse(sender): def converse(sender):
show_announce_stream(None) show_announce_stream(None)
try: try:
@@ -141,13 +168,14 @@ class AnnounceInfo(urwid.WidgetWrap):
if is_node: if is_node:
type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect)) type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect))
msg_button = ("weight", 0.45, urwid.Button("Msg Op", on_press=msg_op))
save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node)) save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node))
else: else:
type_button = ("weight", 0.45, urwid.Button("Converse", on_press=converse)) type_button = ("weight", 0.45, urwid.Button("Converse", on_press=converse))
save_button = None save_button = None
if is_node: if is_node:
button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), save_button, ("weight", 0.1, urwid.Text("")), type_button]) button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), type_button, ("weight", 0.1, urwid.Text("")), msg_button, ("weight", 0.1, urwid.Text("")), save_button])
else: else:
button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), type_button]) button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_announce_stream)), ("weight", 0.1, urwid.Text("")), type_button])
@@ -164,9 +192,6 @@ class AnnounceInfo(urwid.WidgetWrap):
] ]
if is_node: if is_node:
node_ident = RNS.Identity.recall(source_hash)
op_hash = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", node_ident)
op_str = self.app.directory.simplest_display_str(op_hash)
operator_entry = urwid.Text("Oprtr : "+op_str, align="left") operator_entry = urwid.Text("Oprtr : "+op_str, align="left")
pile_widgets.insert(4, operator_entry) pile_widgets.insert(4, operator_entry)
@@ -382,9 +407,9 @@ class KnownNodeInfo(urwid.WidgetWrap):
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">" addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
if node_ident != None: if node_ident != None:
lxmf_addr_str = RNS.prettyhexrep(RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_ident)) lxmf_addr_str = g["sent"]+" LXMF Propagation Node Address is "+RNS.prettyhexrep(RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_ident))
else: else:
lxmf_addr_str = "Unknown" lxmf_addr_str = "No associated Propagation Node known"
type_string = "Node " + g["node"] type_string = "Node " + g["node"]
@@ -430,16 +455,45 @@ class KnownNodeInfo(urwid.WidgetWrap):
e_name = urwid.Edit(caption="Name : ",edit_text=display_str) e_name = urwid.Edit(caption="Name : ",edit_text=display_str)
node_ident = RNS.Identity.recall(source_hash)
op_hash = None
op_str = None
if node_ident != None:
op_hash = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", node_ident)
op_str = self.app.directory.simplest_display_str(op_hash)
else:
op_str = "Unknown"
def show_known_nodes(sender): def show_known_nodes(sender):
options = self.parent.left_pile.options(height_type="weight", height_amount=1) options = self.parent.left_pile.options(height_type="weight", height_amount=1)
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)
def msg_op(sender):
show_known_nodes(None)
if node_ident != None:
try:
existing_conversations = nomadnet.Conversation.conversation_list(self.app)
source_hash_text = RNS.hexrep(op_hash, delimit=False)
display_name = op_str
if not source_hash_text in [c[0] for c in existing_conversations]:
entry = DirectoryEntry(source_hash, display_name, trust_level)
self.app.directory.remember(entry)
new_conversation = nomadnet.Conversation(source_hash_text, nomadnet.NomadNetworkApp.get_shared_instance(), initiator=True)
self.app.ui.main_display.sub_displays.conversations_display.update_conversation_list()
self.app.ui.main_display.sub_displays.conversations_display.display_conversation(None, source_hash_text)
self.app.ui.main_display.show_conversations(None)
except Exception as e:
RNS.log("Error while starting conversation from node info. The contained exception was: "+str(e), RNS.LOG_ERROR)
def pn_change(sender, userdata): def pn_change(sender, userdata):
self.pn_changed = True self.pn_changed = True
@@ -470,17 +524,20 @@ class KnownNodeInfo(urwid.WidgetWrap):
self.app.ui.main_display.sub_displays.network_display.directory_change_callback() self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
show_known_nodes(None) show_known_nodes(None)
type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect)) back_button = ("weight", 0.2, urwid.Button("Back", on_press=show_known_nodes))
save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node)) connect_button = ("weight", 0.2, urwid.Button("Connect", on_press=connect))
button_columns = urwid.Columns([("weight", 0.45, urwid.Button("Back", on_press=show_known_nodes)), ("weight", 0.1, urwid.Text("")), save_button, ("weight", 0.1, urwid.Text("")), type_button]) save_button = ("weight", 0.2, urwid.Button("Save", on_press=save_node))
msg_button = ("weight", 0.2, urwid.Button("Msg Op", on_press=msg_op))
bdiv = ("weight", 0.02, urwid.Text(""))
button_columns = urwid.Columns([back_button, bdiv, connect_button, bdiv, msg_button, bdiv, save_button])
pile_widgets = [ pile_widgets = [
urwid.Text("Type : "+type_string, align="left"), urwid.Text("Type : "+type_string, align="left"),
# urwid.Text("Name : "+display_str, align="left"),
e_name, e_name,
urwid.Text("Node Addr : "+addr_str, align="left"), urwid.Text("Node Addr : "+addr_str, align="left"),
urwid.Text("LXMF Addr : "+lxmf_addr_str, align="left"), urwid.Divider(g["divider1"]),
# urwid.Text(["Trust : ", (style, trust_str)], align="left"), urwid.Text(lxmf_addr_str, align="center"),
urwid.Divider(g["divider1"]), urwid.Divider(g["divider1"]),
propagation_node_checkbox, propagation_node_checkbox,
connect_identify_checkbox, connect_identify_checkbox,
@@ -492,15 +549,8 @@ class KnownNodeInfo(urwid.WidgetWrap):
button_columns button_columns
] ]
node_ident = RNS.Identity.recall(source_hash)
if node_ident != None:
op_hash = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", node_ident)
op_str = self.app.directory.simplest_display_str(op_hash)
else:
op_str = "Unknown"
operator_entry = urwid.Text("Operator : "+op_str, align="left") operator_entry = urwid.Text("Operator : "+op_str, align="left")
pile_widgets.insert(4, operator_entry) pile_widgets.insert(3, operator_entry)
hops = RNS.Transport.hops_to(source_hash) hops = RNS.Transport.hops_to(source_hash)
if hops == 1: if hops == 1:
@@ -514,7 +564,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
hops_str = "Unknown" hops_str = "Unknown"
operator_entry = urwid.Text("Distance : "+hops_str, align="left") operator_entry = urwid.Text("Distance : "+hops_str, align="left")
pile_widgets.insert(5, operator_entry) pile_widgets.insert(4, operator_entry)
pile = urwid.Pile(pile_widgets) pile = urwid.Pile(pile_widgets)
@@ -563,7 +613,7 @@ class KnownNodes(urwid.WidgetWrap):
self.pile = urwid.Pile([urwid.Text(("warning_text", g["info"]+"\n"), align="center"), SelectText(("warning_text", "Currently, no nodes are known\n\n"), align="center")]) self.pile = urwid.Pile([urwid.Text(("warning_text", g["info"]+"\n"), align="center"), SelectText(("warning_text", "Currently, no nodes are known\n\n"), align="center")])
self.display_widget = urwid.Filler(self.pile, valign="top", height="pack") self.display_widget = urwid.Filler(self.pile, valign="top", height="pack")
urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="Known Nodes"), widget_style)) urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="Saved Nodes"), widget_style))
def keypress(self, size, key): def keypress(self, size, key):
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()): if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
@@ -756,7 +806,7 @@ class NodeAnnounceTime(urwid.WidgetWrap):
if self.app.peer_settings["node_last_announce"] != None: if self.app.peer_settings["node_last_announce"] != None:
self.last_announce_string = pretty_date(int(self.app.peer_settings["node_last_announce"])) self.last_announce_string = pretty_date(int(self.app.peer_settings["node_last_announce"]))
self.display_widget.set_text("Last Announce : "+self.last_announce_string) self.display_widget.set_text("Last Announce : "+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()
@@ -787,7 +837,103 @@ class NodeActiveConnections(urwid.WidgetWrap):
if self.app.node != None: if self.app.node != None:
self.stat_string = str(len(self.app.node.destination.links)) self.stat_string = str(len(self.app.node.destination.links))
self.display_widget.set_text("Connected Peers : "+self.stat_string) self.display_widget.set_text("Connections Now : "+self.stat_string)
def update_stat_callback(self, loop=None, user_data=None):
self.update_stat()
if self.started:
self.app.ui.loop.set_alarm_in(self.timeout, self.update_stat_callback)
def start(self):
was_started = self.started
self.started = True
if not was_started:
self.update_stat_callback()
def stop(self):
self.started = False
class NodeTotalConnections(urwid.WidgetWrap):
def __init__(self, app):
self.started = False
self.app = app
self.timeout = self.app.config["textui"]["animation_interval"]
self.display_widget = urwid.Text("")
self.update_stat()
urwid.WidgetWrap.__init__(self, self.display_widget)
def update_stat(self):
self.stat_string = "None"
if self.app.node != None:
self.stat_string = str(self.app.peer_settings["node_connects"])
self.display_widget.set_text("Total Connections : "+self.stat_string)
def update_stat_callback(self, loop=None, user_data=None):
self.update_stat()
if self.started:
self.app.ui.loop.set_alarm_in(self.timeout, self.update_stat_callback)
def start(self):
was_started = self.started
self.started = True
if not was_started:
self.update_stat_callback()
def stop(self):
self.started = False
class NodeTotalPages(urwid.WidgetWrap):
def __init__(self, app):
self.started = False
self.app = app
self.timeout = self.app.config["textui"]["animation_interval"]
self.display_widget = urwid.Text("")
self.update_stat()
urwid.WidgetWrap.__init__(self, self.display_widget)
def update_stat(self):
self.stat_string = "None"
if self.app.node != None:
self.stat_string = str(self.app.peer_settings["served_page_requests"])
self.display_widget.set_text("Served Pages : "+self.stat_string)
def update_stat_callback(self, loop=None, user_data=None):
self.update_stat()
if self.started:
self.app.ui.loop.set_alarm_in(self.timeout, self.update_stat_callback)
def start(self):
was_started = self.started
self.started = True
if not was_started:
self.update_stat_callback()
def stop(self):
self.started = False
class NodeTotalFiles(urwid.WidgetWrap):
def __init__(self, app):
self.started = False
self.app = app
self.timeout = self.app.config["textui"]["animation_interval"]
self.display_widget = urwid.Text("")
self.update_stat()
urwid.WidgetWrap.__init__(self, self.display_widget)
def update_stat(self):
self.stat_string = "None"
if self.app.node != None:
self.stat_string = str(self.app.peer_settings["served_file_requests"])
self.display_widget.set_text("Served Files : "+self.stat_string)
def update_stat_callback(self, loop=None, user_data=None): def update_stat_callback(self, loop=None, user_data=None):
self.update_stat() self.update_stat()
@@ -823,7 +969,7 @@ class LocalPeer(urwid.WidgetWrap):
def save_query(sender): def save_query(sender):
def dismiss_dialog(sender): def dismiss_dialog(sender):
self.dialog_open = False self.dialog_open = False
self.parent.left_pile.contents[2] = (LocalPeer(self.app, self.parent), options) self.parent.left_pile.contents[1] = (LocalPeer(self.app, self.parent), options)
self.app.set_display_name(e_name.get_edit_text()) self.app.set_display_name(e_name.get_edit_text())
@@ -840,19 +986,19 @@ class LocalPeer(urwid.WidgetWrap):
overlay = dialog overlay = dialog
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.dialog_open = True self.dialog_open = True
self.parent.left_pile.contents[2] = (overlay, options) self.parent.left_pile.contents[1] = (overlay, options)
def announce_query(sender): def announce_query(sender):
def dismiss_dialog(sender): def dismiss_dialog(sender):
self.dialog_open = False self.dialog_open = False
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (LocalPeer(self.app, self.parent), options) self.parent.left_pile.contents[1] = (LocalPeer(self.app, self.parent), options)
self.app.announce_now() self.app.announce_now()
dialog = DialogLineBox( dialog = DialogLineBox(
urwid.Pile([ urwid.Pile([
urwid.Text("\n\n\nAnnounce Sent\n\n", align="center"), urwid.Text("\n\n\nAnnounce Sent\n\n\n", align="center"),
urwid.Button("OK", on_press=dismiss_dialog) urwid.Button("OK", on_press=dismiss_dialog)
]), title=g["info"] ]), title=g["info"]
) )
@@ -864,11 +1010,11 @@ class LocalPeer(urwid.WidgetWrap):
self.dialog_open = True self.dialog_open = True
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (overlay, options) self.parent.left_pile.contents[1] = (overlay, options)
def node_info_query(sender): def node_info_query(sender):
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (self.parent.node_info_display, options) self.parent.left_pile.contents[1] = (self.parent.node_info_display, options)
if LocalPeer.announce_timer == None: if LocalPeer.announce_timer == None:
self.t_last_announce = AnnounceTime(self.app) self.t_last_announce = AnnounceTime(self.app)
@@ -901,6 +1047,9 @@ class LocalPeer(urwid.WidgetWrap):
class NodeInfo(urwid.WidgetWrap): class NodeInfo(urwid.WidgetWrap):
announce_timer = None announce_timer = None
links_timer = None links_timer = None
conns_timer = None
pages_timer = None
files_timer = None
def __init__(self, app, parent): def __init__(self, app, parent):
self.app = app self.app = app
@@ -913,7 +1062,7 @@ class NodeInfo(urwid.WidgetWrap):
def show_peer_info(sender): def show_peer_info(sender):
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (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:
@@ -927,11 +1076,17 @@ class NodeInfo(urwid.WidgetWrap):
t_id = urwid.Text("Addr : "+RNS.hexrep(self.app.node.destination.hash, delimit=False)) t_id = urwid.Text("Addr : "+RNS.hexrep(self.app.node.destination.hash, delimit=False))
e_name = urwid.Text("Name : "+display_name) e_name = urwid.Text("Name : "+display_name)
def stats_query(sender):
self.app.peer_settings["node_connects"] = 0
self.app.peer_settings["served_page_requests"] = 0
self.app.peer_settings["served_file_requests"] = 0
self.app.save_peer_settings()
def announce_query(sender): def announce_query(sender):
def dismiss_dialog(sender): def dismiss_dialog(sender):
self.dialog_open = False self.dialog_open = False
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (NodeInfo(self.app, self.parent), options) self.parent.left_pile.contents[1] = (NodeInfo(self.app, self.parent), options)
self.app.node.announce() self.app.node.announce()
@@ -949,7 +1104,7 @@ class NodeInfo(urwid.WidgetWrap):
self.dialog_open = True self.dialog_open = True
options = self.parent.left_pile.options(height_type="pack", height_amount=None) options = self.parent.left_pile.options(height_type="pack", height_amount=None)
self.parent.left_pile.contents[2] = (overlay, options) self.parent.left_pile.contents[1] = (overlay, options)
def connect_query(sender): def connect_query(sender):
self.parent.browser.retrieve_url(RNS.hexrep(self.app.node.destination.hash, delimit=False)) self.parent.browser.retrieve_url(RNS.hexrep(self.app.node.destination.hash, delimit=False))
@@ -968,22 +1123,54 @@ 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()
if NodeInfo.conns_timer == None:
self.t_total_connections = NodeTotalConnections(self.app)
NodeInfo.conns_timer = self.t_total_connections
else:
self.t_total_connections = NodeInfo.conns_timer
self.t_total_connections.update_stat()
if NodeInfo.pages_timer == None:
self.t_total_pages = NodeTotalPages(self.app)
NodeInfo.pages_timer = self.t_total_pages
else:
self.t_total_pages = NodeInfo.pages_timer
self.t_total_pages.update_stat()
if NodeInfo.files_timer == None:
self.t_total_files = NodeTotalFiles(self.app)
NodeInfo.files_timer = self.t_total_files
else:
self.t_total_files = NodeInfo.files_timer
self.t_total_files.update_stat()
lxmf_addr_str = g["sent"]+" LXMF Propagation Node Address is "+RNS.prettyhexrep(RNS.Destination.hash_from_name_and_identity("lxmf.propagation", self.app.node.destination.identity))
e_lxmf = urwid.Text(lxmf_addr_str, align="center")
announce_button = urwid.Button("Announce", 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)
reset_button = urwid.Button("Rst Stats", on_press=stats_query)
pile = urwid.Pile([ pile = urwid.Pile([
t_id, t_id,
e_name, e_name,
urwid.Divider(g["divider1"]), urwid.Divider(g["divider1"]),
e_lxmf,
urwid.Divider(g["divider1"]),
self.t_last_announce, self.t_last_announce,
self.t_active_links, self.t_active_links,
self.t_total_connections,
self.t_total_pages,
self.t_total_files,
urwid.Divider(g["divider1"]), urwid.Divider(g["divider1"]),
urwid.Columns([ urwid.Columns([
("weight", 0.3, urwid.Button("Back", on_press=show_peer_info)), ("weight", 5, urwid.Button("Back", on_press=show_peer_info)),
("weight", 0.1, urwid.Text("")), ("weight", 0.5, urwid.Text("")),
("weight", 0.4, connect_button), ("weight", 6, connect_button),
("weight", 0.1, urwid.Text("")), ("weight", 0.5, urwid.Text("")),
("weight", 0.5, announce_button) ("weight", 8, reset_button),
("weight", 0.5, urwid.Text("")),
("weight", 7, announce_button),
]) ])
]) ])
else: else:
@@ -1001,7 +1188,9 @@ class NodeInfo(urwid.WidgetWrap):
if self.app.node != None: if self.app.node != None:
self.t_last_announce.start() self.t_last_announce.start()
self.t_active_links.start() self.t_active_links.start()
self.t_total_connections.start()
self.t_total_pages.start()
self.t_total_files.start()
class UpdatingText(urwid.WidgetWrap): class UpdatingText(urwid.WidgetWrap):
@@ -1103,7 +1292,7 @@ class NetworkDisplay():
self.list_display = 1 self.list_display = 1
self.left_pile = NetworkLeftPile([ self.left_pile = NetworkLeftPile([
("weight", 1, self.known_nodes_display), ("weight", 1, self.known_nodes_display),
("pack", self.network_stats_display), # ("pack", self.network_stats_display),
("pack", self.local_peer_display), ("pack", self.local_peer_display),
]) ])
@@ -1212,7 +1401,7 @@ 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.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") 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)) urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="LXMF Propagation Peers"), widget_style))
def keypress(self, size, key): def keypress(self, size, key):
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()): if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
@@ -1269,13 +1458,13 @@ class LXMFPeerEntry(urwid.WidgetWrap):
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:
display_str += "\n "+str(display_name) display_str += " "+str(display_name)
sym = g["sent"] sym = g["sent"]
style = "list_unknown" style = "list_unknown"
focus_style = "list_focus" 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") widget = ListEntry(sym+" "+display_str+"\n "+str(len(peer.unhandled_messages))+" unhandled messages - "+"Last heard "+pretty_date(int(peer.last_heard)))
# urwid.connect_signal(widget, "click", delegate.connect_node, node) # urwid.connect_signal(widget, "click", delegate.connect_node, node)
self.display_widget = urwid.AttrMap(widget, style, focus_style) self.display_widget = urwid.AttrMap(widget, style, focus_style)

View File

@@ -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.3.4', 'lxmf>=0.1.4', 'urwid>=2.1.2'], install_requires=['rns>=0.3.6', 'lxmf>=0.1.6', 'urwid>=2.1.2'],
python_requires='>=3.6', python_requires='>=3.6',
) )