mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 14:54:26 +01:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ef9e55a22 | ||
|
|
0a0de599dd | ||
|
|
2767e35a9f | ||
|
|
2b3943eeba | ||
|
|
7a625d677d | ||
|
|
2354ba3412 | ||
|
|
fa2382e34a | ||
|
|
7814712118 | ||
|
|
a6eac0d7ce | ||
|
|
f9259b8173 | ||
|
|
d65d03fe89 | ||
|
|
acb5da89fb | ||
|
|
3fc321e804 | ||
|
|
b312bd97e0 | ||
|
|
d8e84409c7 | ||
|
|
a09d79f27c | ||
|
|
3ec82b2775 | ||
|
|
f92858866a | ||
|
|
13484a665a | ||
|
|
3cd336ab90 | ||
|
|
2e085d2d8d | ||
|
|
1cdea6c1ab | ||
|
|
2b58a7840a | ||
|
|
bc7231def0 | ||
|
|
73fb44dcc3 | ||
|
|
467f02b379 | ||
|
|
c6e26e7f7f | ||
|
|
28eb7a9731 | ||
|
|
a917cbf010 | ||
|
|
34b377bafe | ||
|
|
6d7e8c413e |
1
.dockerignore
Symbolic link
1
.dockerignore
Symbolic link
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
42
.github/workflows/publish-container.yml
vendored
Normal file
42
.github/workflows/publish-container.yml
vendored
Normal 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 }}
|
||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
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"
|
||||
ENV PYTHONUNBUFFERED="yes"
|
||||
COPY --from=build /opt/venv /opt/venv
|
||||
|
||||
VOLUME /root/.reticulum
|
||||
VOLUME /root/.nomadnetwork
|
||||
|
||||
ENTRYPOINT ["nomadnet"]
|
||||
CMD ["--daemon"]
|
||||
59
README.md
59
README.md
@@ -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.
|
||||
|
||||
### Feature roadmap
|
||||
- Access control and authentication for nodes, pages and files
|
||||
- Network-wide propagated bulletins and discussion threads
|
||||
- Collaborative maps
|
||||
- Collaborative maps and geospatial information sharing
|
||||
- Facilitation of trade and barter
|
||||
|
||||
## How do I get started?
|
||||
@@ -39,6 +38,12 @@ pip3 install nomadnet
|
||||
|
||||
# Run the client
|
||||
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.
|
||||
@@ -49,11 +54,43 @@ 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.
|
||||
|
||||
### 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.
|
||||
|
||||
For a native Android application with a graphical user interface, have a look at [Sideband](https://unsigned.io/sideband).
|
||||
|
||||
### Docker Images
|
||||
|
||||
Nomad Network is automatically published as a docker image on Github Packages. Image tags are one of either `master` (for the very latest commit) or the version number (eg `0.1.8`) for a specific release.
|
||||
|
||||
```sh
|
||||
$ docker pull ghcr.io/markqvist/nomadnet:master
|
||||
|
||||
# Run nomadnet interactively in a container
|
||||
$ docker run -it ghcr.io/markqvist/nomadnet:master --textui
|
||||
|
||||
# Run nomadnet as a daemon, using config stored on the host machine in specified
|
||||
# directories, and connect the containers network to the host network (which will
|
||||
# allow the default AutoInterface to automatically peer with other discovered
|
||||
# Reticulum instances).
|
||||
$ docker run -d \
|
||||
-v /local/path/nomadnetconfigdir/:/root/.nomadnetwork/ \
|
||||
-v /local/path/reticulumconfigdir/:/root/.reticulum/ \
|
||||
--network host
|
||||
ghcr.io/markqvist/nomadnet:master
|
||||
|
||||
# You can also keep the network of the container isolated from the host, but you
|
||||
# will need to manually configure one or more Reticulum interfaces to reach other
|
||||
# nodes in a network, by editing the Reticulum configuration file.
|
||||
$ docker run -d \
|
||||
-v /local/path/nomadnetconfigdir/:/root/.nomadnetwork/ \
|
||||
-v /local/path/reticulumconfigdir/:/root/.reticulum/ \
|
||||
ghcr.io/markqvist/nomadnet:master
|
||||
|
||||
# Send daemon log output to console instead of file
|
||||
$ docker run -i ghcr.io/markqvist/nomadnet:master --daemon --console
|
||||
```
|
||||
|
||||
## 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).
|
||||
@@ -61,8 +98,20 @@ For help requests, discussion, sharing ideas or anything else related to Nomad N
|
||||
## 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
|
||||
- Monero:
|
||||
```
|
||||
84FpY1QbxHcgdseePYNmhTHcrgMX4nFf
|
||||
BYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD1
|
||||
9b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
|
||||
```
|
||||
- Ethereum
|
||||
```
|
||||
0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
|
||||
```
|
||||
- Bitcoin
|
||||
```
|
||||
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||
```
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
## Caveat Emptor
|
||||
|
||||
@@ -14,6 +14,8 @@ class Conversation:
|
||||
@staticmethod
|
||||
def received_announce(destination_hash, announced_identity, app_data):
|
||||
app = nomadnet.NomadNetworkApp.get_shared_instance()
|
||||
|
||||
if not destination_hash in app.ignored_list:
|
||||
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
|
||||
# Check if the announced destination is in
|
||||
# our list of conversations
|
||||
@@ -29,6 +31,9 @@ class Conversation:
|
||||
# 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
|
||||
def query_for_peer(source_hash):
|
||||
try:
|
||||
@@ -66,6 +71,7 @@ class Conversation:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if Conversation.created_callback != None:
|
||||
Conversation.created_callback()
|
||||
|
||||
return ingested_path
|
||||
|
||||
@@ -12,6 +12,8 @@ class Directory:
|
||||
@staticmethod
|
||||
def received_announce(destination_hash, announced_identity, app_data):
|
||||
app = nomadnet.NomadNetworkApp.get_shared_instance()
|
||||
|
||||
if not destination_hash in app.ignored_list:
|
||||
destination_hash_text = RNS.hexrep(destination_hash, delimit=False)
|
||||
|
||||
associated_peer = RNS.Destination.hash_from_name_and_identity("lxmf.delivery", announced_identity)
|
||||
@@ -19,6 +21,9 @@ class Directory:
|
||||
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):
|
||||
self.directory_entries = {}
|
||||
@@ -86,6 +91,8 @@ class Directory:
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, False))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
|
||||
if hasattr(self.app.ui, "main_display"):
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
|
||||
def node_announce_received(self, source_hash, app_data, associated_peer):
|
||||
@@ -101,6 +108,7 @@ class Directory:
|
||||
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
|
||||
self.remember(node_entry)
|
||||
|
||||
if hasattr(self.app.ui, "main_display"):
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
|
||||
def remove_announce_with_timestamp(self, timestamp):
|
||||
|
||||
@@ -24,6 +24,8 @@ class Node:
|
||||
self.register_pages()
|
||||
self.register_files()
|
||||
|
||||
self.destination.set_link_established_callback(self.peer_connected)
|
||||
|
||||
if self.name == None:
|
||||
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):
|
||||
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)
|
||||
|
||||
@@ -163,6 +171,13 @@ class Node:
|
||||
# TODO: Improve file handling, this will be slow for large files
|
||||
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)
|
||||
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_name = path.replace("/file/", "", 1)
|
||||
try:
|
||||
@@ -197,10 +212,20 @@ class Node:
|
||||
|
||||
time.sleep(self.job_interval)
|
||||
|
||||
def peer_connected(link):
|
||||
RNS.log("Peer connected to "+str(self.destination), RNS.LOG_INFO)
|
||||
def peer_connected(self, link):
|
||||
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)
|
||||
|
||||
def peer_disconnected(self, link):
|
||||
RNS.log("Peer disconnected from "+str(self.destination), RNS.LOG_VERBOSE)
|
||||
pass
|
||||
|
||||
DEFAULT_INDEX = '''>Default Home Page
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class NomadNetworkApp:
|
||||
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, daemon = False, force_console = False):
|
||||
self.version = __version__
|
||||
self.enable_client = False
|
||||
self.enable_node = False
|
||||
@@ -59,12 +59,18 @@ class NomadNetworkApp:
|
||||
else:
|
||||
self.configdir = configdir
|
||||
|
||||
if force_console:
|
||||
self.force_console_log = True
|
||||
else:
|
||||
self.force_console_log = False
|
||||
|
||||
if NomadNetworkApp._shared_instance == None:
|
||||
NomadNetworkApp._shared_instance = self
|
||||
|
||||
self.rns = RNS.Reticulum(configdir = rnsconfigdir)
|
||||
|
||||
self.configpath = self.configdir+"/config"
|
||||
self.ignoredpath = self.configdir+"/ignored"
|
||||
self.logfilepath = self.configdir+"/logfile"
|
||||
self.errorfilepath = self.configdir+"/errors"
|
||||
self.storagepath = self.configdir+"/storage"
|
||||
@@ -171,6 +177,15 @@ class NomadNetworkApp:
|
||||
if not "last_lxmf_sync" in self.peer_settings:
|
||||
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:
|
||||
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)
|
||||
@@ -185,6 +200,9 @@ class NomadNetworkApp:
|
||||
"node_last_announce": None,
|
||||
"propagation_node": None,
|
||||
"last_lxmf_sync": 0,
|
||||
"node_connects": 0,
|
||||
"served_page_requests": 0,
|
||||
"served_file_requests": 0
|
||||
}
|
||||
self.save_peer_settings()
|
||||
RNS.log("Created new peer settings file")
|
||||
@@ -193,12 +211,36 @@ class NomadNetworkApp:
|
||||
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
|
||||
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.message_router = LXMF.LXMRouter(identity = self.identity, storagepath = self.storagepath, autopeer = True)
|
||||
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.set_default_app_data(self.get_display_name_bytes)
|
||||
|
||||
@@ -212,7 +254,18 @@ class NomadNetworkApp:
|
||||
RNS.log("LXMF Router ready to receive on: "+RNS.prettyhexrep(self.lxmf_destination.hash))
|
||||
|
||||
if self.enable_node:
|
||||
self.message_router.set_message_storage_limit(gigabytes=self.message_storage_limit)
|
||||
for dest_str in self.prioritised_lxmf_destinations:
|
||||
try:
|
||||
dest_hash = bytes.fromhex(dest_str)
|
||||
if len(dest_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
|
||||
self.message_router.prioritise(dest_hash)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Cannot prioritise "+str(dest_str)+", it is not a valid destination hash", RNS.LOG_ERROR)
|
||||
|
||||
self.message_router.enable_propagation()
|
||||
|
||||
RNS.log("LXMF Propagation Node started on: "+RNS.prettyhexrep(self.message_router.propagation_destination.hash))
|
||||
self.node = nomadnet.Node(self)
|
||||
else:
|
||||
@@ -239,6 +292,10 @@ class NomadNetworkApp:
|
||||
job_thread.setDaemon(True)
|
||||
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
|
||||
# from spewing KeyErrors to the console and thus,
|
||||
# messing up the UI. A pull request to fix the
|
||||
@@ -306,6 +363,10 @@ class NomadNetworkApp:
|
||||
return "Done, no new messages"
|
||||
else:
|
||||
return "Downloaded "+str(new_msgs)+" new messages"
|
||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_IDENTITY_RCVD:
|
||||
return "Node did not receive identification"
|
||||
elif self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_NO_ACCESS:
|
||||
return "Node did not allow request"
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
@@ -333,7 +394,7 @@ class NomadNetworkApp:
|
||||
return self.message_router.propagation_transfer_progress
|
||||
|
||||
def request_lxmf_sync(self, limit = None):
|
||||
if self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_IDLE or self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||
if self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_IDLE or self.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
|
||||
self.peer_settings["last_lxmf_sync"] = time.time()
|
||||
self.save_peer_settings()
|
||||
self.message_router.request_messages_from_propagation_node(self.identity, max_messages = limit)
|
||||
@@ -367,7 +428,7 @@ class NomadNetworkApp:
|
||||
selected_node = node
|
||||
|
||||
if selected_node == None:
|
||||
RNS.log("Could not autoselect a prepagation node! LXMF propagation will not be available until a trusted node announces on the network.", RNS.LOG_WARNING)
|
||||
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:
|
||||
node_identity = RNS.Identity.recall(selected_node.source_hash)
|
||||
if node_identity != None:
|
||||
@@ -452,7 +513,7 @@ class NomadNetworkApp:
|
||||
if RNS.loglevel > 7:
|
||||
RNS.loglevel = 7
|
||||
if option == "destination":
|
||||
if value.lower() == "file":
|
||||
if value.lower() == "file" and not self.force_console_log:
|
||||
RNS.logdest = RNS.LOG_FILE
|
||||
if "logfile" in self.config["logging"]:
|
||||
self.logfilepath = self.config["logging"]["logfile"]
|
||||
@@ -603,6 +664,19 @@ class NomadNetworkApp:
|
||||
if "files_path" in self.config["node"]:
|
||||
self.filespath = self.config["node"]["files_path"]
|
||||
|
||||
if "prioritise_destinations" in self.config["node"]:
|
||||
self.prioritised_lxmf_destinations = self.config["node"].as_list("prioritise_destinations")
|
||||
else:
|
||||
self.prioritised_lxmf_destinations = []
|
||||
|
||||
if not "message_storage_limit" in self.config["node"]:
|
||||
self.message_storage_limit = 2
|
||||
else:
|
||||
value = self.config["node"].as_float("message_storage_limit")
|
||||
if value < 0.064:
|
||||
value = 0.064
|
||||
self.message_storage_limit = value
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_shared_instance():
|
||||
@@ -713,28 +787,43 @@ editor = editor
|
||||
|
||||
# If you don't want the Guide section to
|
||||
# show up in the menu, you can disable it.
|
||||
|
||||
hide_guide = no
|
||||
|
||||
[node]
|
||||
|
||||
# Whether to enable node hosting
|
||||
|
||||
enable_node = no
|
||||
|
||||
# The node name will be visible to other
|
||||
# peers on the network, and included in
|
||||
# announces.
|
||||
|
||||
node_name = None
|
||||
|
||||
# Automatic announce interval in minutes.
|
||||
# 6 hours by default.
|
||||
|
||||
announce_interval = 360
|
||||
|
||||
# Whether to announce when the node starts
|
||||
|
||||
# Whether to announce when the node starts.
|
||||
announce_at_start = Yes
|
||||
|
||||
# The maximum amount of storage to use for
|
||||
# the LXMF Propagation Node message store,
|
||||
# specified in gigabytes. When this limit
|
||||
# is reached, LXMF will periodically remove
|
||||
# messages in its message store. By default,
|
||||
# LXMF prioritises keeping messages that are
|
||||
# new and small. Large and old messages will
|
||||
# be removed first. This setting is optional
|
||||
# and defaults to 2 gigabytes.
|
||||
# message_storage_limit = 2
|
||||
|
||||
# You can tell the LXMF message router to
|
||||
# prioritise storage for one or more
|
||||
# destinations. If the message store reaches
|
||||
# the specified limit, LXMF will prioritise
|
||||
# keeping messages for destinations specified
|
||||
# with this option. This setting is optional,
|
||||
# and generally you do not need to use it.
|
||||
# prioritise_destinations = 10bc7624c27032a18639, ba780a6dff4cc1391db8
|
||||
|
||||
'''.splitlines()
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.7"
|
||||
__version__ = "0.1.9"
|
||||
|
||||
@@ -7,14 +7,22 @@ import argparse
|
||||
import nomadnet
|
||||
|
||||
|
||||
def program_setup(configdir, rnsconfigdir):
|
||||
app = nomadnet.NomadNetworkApp(configdir = configdir, rnsconfigdir = rnsconfigdir)
|
||||
def program_setup(configdir, rnsconfigdir, daemon, console):
|
||||
app = nomadnet.NomadNetworkApp(
|
||||
configdir = configdir,
|
||||
rnsconfigdir = rnsconfigdir,
|
||||
daemon = daemon,
|
||||
force_console = console,
|
||||
)
|
||||
|
||||
def main():
|
||||
try:
|
||||
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("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("-t", "--textui", action="store_true", default=False, help="run Nomad Network in text-UI mode")
|
||||
parser.add_argument("-d", "--daemon", action="store_true", default=False, help="run Nomad Network in daemon mode")
|
||||
parser.add_argument("-c", "--console", action="store_true", default=False, help="in daemon mode, log to console instead of file")
|
||||
parser.add_argument("--version", action="version", version="Nomad Network Client {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -29,7 +37,18 @@ def main():
|
||||
else:
|
||||
rnsconfigarg = None
|
||||
|
||||
program_setup(configarg, rnsconfigarg)
|
||||
console = False
|
||||
if args.daemon:
|
||||
daemon = True
|
||||
if args.console:
|
||||
console = True
|
||||
else:
|
||||
daemon = False
|
||||
|
||||
if args.textui:
|
||||
daemon = False
|
||||
|
||||
program_setup(configarg, rnsconfigarg, daemon, console)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
||||
@@ -4,5 +4,5 @@ import nomadnet
|
||||
class MenuUI:
|
||||
|
||||
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()
|
||||
19
nomadnet/ui/NoneUI.py
Normal file
19
nomadnet/ui/NoneUI.py
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
|
||||
if not self.app.force_console_log:
|
||||
RNS.log("Nomad Network started in daemon mode, all further messages are logged to "+str(self.app.logfilepath), RNS.LOG_INFO, _override_destination=True)
|
||||
else:
|
||||
RNS.log("Nomad Network daemon started", RNS.LOG_INFO)
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
@@ -12,11 +12,15 @@ UI_MENU = 0x01
|
||||
UI_TEXT = 0x02
|
||||
UI_GRAPHICAL = 0x03
|
||||
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):
|
||||
if uimode in UI_MODES:
|
||||
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:
|
||||
from .MenuUI import MenuUI
|
||||
return MenuUI()
|
||||
@@ -29,8 +33,11 @@ def spawn(uimode):
|
||||
elif uimode == UI_WEB:
|
||||
from .WebUI import WebUI
|
||||
return WebUI()
|
||||
elif uimode == UI_NONE:
|
||||
from .NoneUI import NoneUI
|
||||
return NoneUI()
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
RNS.log("Invalid UI mode", RNS.LOG_ERROR)
|
||||
RNS.log("Invalid UI mode", RNS.LOG_ERROR, _override_destination=True)
|
||||
nomadnet.panic()
|
||||
@@ -334,7 +334,7 @@ class ConversationsDisplay():
|
||||
self.dialog_open = False
|
||||
self.sync_dialog = None
|
||||
self.update_conversation_list()
|
||||
if self.app.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||
if self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
|
||||
self.app.cancel_lxmf_sync()
|
||||
|
||||
max_messages_group = []
|
||||
@@ -360,7 +360,7 @@ class ConversationsDisplay():
|
||||
real_sync_button = urwid.Button("Sync Now", on_press=sync_now)
|
||||
hidden_sync_button = urwid.Button("Cancel Sync", on_press=cancel_sync)
|
||||
|
||||
if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||
if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
|
||||
sync_button = real_sync_button
|
||||
else:
|
||||
sync_button = hidden_sync_button
|
||||
@@ -423,7 +423,7 @@ class ConversationsDisplay():
|
||||
if self.dialog_open and self.sync_dialog != None:
|
||||
self.sync_dialog.sync_progress.set_completion(self.app.get_sync_progress())
|
||||
|
||||
if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE:
|
||||
if self.app.get_sync_status() == "Idle" or self.app.message_router.propagation_transfer_state >= LXMF.LXMRouter.PR_COMPLETE:
|
||||
self.sync_dialog.bc.contents[0] = (self.sync_dialog.real_sync_button, self.sync_dialog.bc.options("weight", 0.45))
|
||||
else:
|
||||
self.sync_dialog.bc.contents[0] = (self.sync_dialog.hidden_sync_button, self.sync_dialog.bc.options("weight", 0.45))
|
||||
|
||||
@@ -514,12 +514,12 @@ What color theme to use. Set it to match your terminal theme. Can be either `!da
|
||||
|
||||
>> Node Section
|
||||
|
||||
This section holds configuration directives related to the node hosting. It is delimited by the `![node]`! header in the configuration file. Available directives, along with their default values, are as follows:
|
||||
This section holds configuration directives related to the node hosting. It is delimited by the `![node]`! header in the configuration file. Available directives, along with example values, are as follows:
|
||||
|
||||
>>>
|
||||
`!enable_node = yes`!
|
||||
`!enable_node = no`!
|
||||
>>>>
|
||||
Determines whether the node server should be started on launch. Must be a boolean value.
|
||||
Determines whether the node server should be started on launch. Must be a boolean value, and is turned off by default.
|
||||
<
|
||||
|
||||
>>>
|
||||
@@ -551,6 +551,22 @@ 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.
|
||||
<
|
||||
|
||||
>>>
|
||||
`!message_storage_limit = 2`!
|
||||
>>>>
|
||||
Configures the maximum amount of storage, in gigabytes, the LXMF Propagation Node will use to store messages.
|
||||
<
|
||||
|
||||
>>>
|
||||
`!prioritise_destinations = 10bc7624c27032a18639, ba780a6dff4cc1391db8`!
|
||||
>>>>
|
||||
Configures the LXMF Propagation Node to prioritise storing messages for certain destinations. If the message store reaches the specified limit, LXMF will prioritise keeping messages for destinations specified with this option. This setting is optional, and generally you do not need to use it.
|
||||
<
|
||||
|
||||
>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
|
||||
|
||||
@@ -528,7 +528,6 @@ class LinkableText(urwid.Text):
|
||||
if self.delegate != None:
|
||||
self.delegate.handle_link(link_target)
|
||||
|
||||
|
||||
def find_next_part_pos(self, pos, part_positions):
|
||||
for position in part_positions:
|
||||
if position > pos:
|
||||
@@ -596,7 +595,12 @@ class LinkableText(urwid.Text):
|
||||
return key
|
||||
|
||||
elif key == "right":
|
||||
old = self._cursor_position
|
||||
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()
|
||||
|
||||
elif key == "left":
|
||||
@@ -641,9 +645,33 @@ class LinkableText(urwid.Text):
|
||||
if button != 1 or not is_mouse_press(event):
|
||||
return False
|
||||
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
|
||||
item = self.find_item_at_pos(self._cursor_position)
|
||||
|
||||
if item != None:
|
||||
if isinstance(item, LinkSpec):
|
||||
self.handle_link(item.link_target)
|
||||
|
||||
@@ -118,6 +118,33 @@ class AnnounceInfo(urwid.WidgetWrap):
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
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):
|
||||
show_announce_stream(None)
|
||||
try:
|
||||
@@ -141,13 +168,14 @@ class AnnounceInfo(urwid.WidgetWrap):
|
||||
|
||||
if is_node:
|
||||
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))
|
||||
else:
|
||||
type_button = ("weight", 0.45, urwid.Button("Converse", on_press=converse))
|
||||
save_button = None
|
||||
|
||||
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:
|
||||
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:
|
||||
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")
|
||||
pile_widgets.insert(4, operator_entry)
|
||||
|
||||
@@ -382,9 +407,9 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
addr_str = "<"+RNS.hexrep(source_hash, delimit=False)+">"
|
||||
|
||||
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:
|
||||
lxmf_addr_str = "Unknown"
|
||||
lxmf_addr_str = "No associated Propagation Node known"
|
||||
|
||||
|
||||
type_string = "Node " + g["node"]
|
||||
@@ -430,16 +455,45 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
|
||||
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):
|
||||
options = self.parent.left_pile.options(height_type="weight", height_amount=1)
|
||||
self.parent.left_pile.contents[0] = (self.parent.known_nodes_display, options)
|
||||
|
||||
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))
|
||||
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):
|
||||
self.pn_changed = True
|
||||
|
||||
@@ -470,17 +524,20 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
show_known_nodes(None)
|
||||
|
||||
type_button = ("weight", 0.45, urwid.Button("Connect", on_press=connect))
|
||||
save_button = ("weight", 0.45, urwid.Button("Save", on_press=save_node))
|
||||
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])
|
||||
back_button = ("weight", 0.2, urwid.Button("Back", on_press=show_known_nodes))
|
||||
connect_button = ("weight", 0.2, urwid.Button("Connect", on_press=connect))
|
||||
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 = [
|
||||
urwid.Text("Type : "+type_string, align="left"),
|
||||
# urwid.Text("Name : "+display_str, align="left"),
|
||||
e_name,
|
||||
urwid.Text("Node Addr : "+addr_str, align="left"),
|
||||
urwid.Text("LXMF Addr : "+lxmf_addr_str, align="left"),
|
||||
# urwid.Text(["Trust : ", (style, trust_str)], align="left"),
|
||||
urwid.Divider(g["divider1"]),
|
||||
urwid.Text(lxmf_addr_str, align="center"),
|
||||
urwid.Divider(g["divider1"]),
|
||||
propagation_node_checkbox,
|
||||
connect_identify_checkbox,
|
||||
@@ -492,15 +549,8 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
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")
|
||||
pile_widgets.insert(4, operator_entry)
|
||||
pile_widgets.insert(3, operator_entry)
|
||||
|
||||
hops = RNS.Transport.hops_to(source_hash)
|
||||
if hops == 1:
|
||||
@@ -514,7 +564,7 @@ class KnownNodeInfo(urwid.WidgetWrap):
|
||||
hops_str = "Unknown"
|
||||
|
||||
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)
|
||||
|
||||
@@ -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.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):
|
||||
if key == "up" and (self.no_content or self.ilb.first_item_is_selected()):
|
||||
@@ -787,7 +837,103 @@ class NodeActiveConnections(urwid.WidgetWrap):
|
||||
if self.app.node != None:
|
||||
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):
|
||||
self.update_stat()
|
||||
@@ -823,7 +969,7 @@ class LocalPeer(urwid.WidgetWrap):
|
||||
def save_query(sender):
|
||||
def dismiss_dialog(sender):
|
||||
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())
|
||||
|
||||
@@ -840,19 +986,19 @@ class LocalPeer(urwid.WidgetWrap):
|
||||
overlay = dialog
|
||||
options = self.parent.left_pile.options(height_type="pack", height_amount=None)
|
||||
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 dismiss_dialog(sender):
|
||||
self.dialog_open = False
|
||||
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()
|
||||
|
||||
dialog = DialogLineBox(
|
||||
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)
|
||||
]), title=g["info"]
|
||||
)
|
||||
@@ -864,11 +1010,11 @@ class LocalPeer(urwid.WidgetWrap):
|
||||
|
||||
self.dialog_open = True
|
||||
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):
|
||||
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:
|
||||
self.t_last_announce = AnnounceTime(self.app)
|
||||
@@ -901,6 +1047,9 @@ class LocalPeer(urwid.WidgetWrap):
|
||||
class NodeInfo(urwid.WidgetWrap):
|
||||
announce_timer = None
|
||||
links_timer = None
|
||||
conns_timer = None
|
||||
pages_timer = None
|
||||
files_timer = None
|
||||
|
||||
def __init__(self, app, parent):
|
||||
self.app = app
|
||||
@@ -913,7 +1062,7 @@ class NodeInfo(urwid.WidgetWrap):
|
||||
|
||||
def show_peer_info(sender):
|
||||
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.node != None:
|
||||
@@ -927,11 +1076,17 @@ class NodeInfo(urwid.WidgetWrap):
|
||||
t_id = urwid.Text("Addr : "+RNS.hexrep(self.app.node.destination.hash, delimit=False))
|
||||
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 dismiss_dialog(sender):
|
||||
self.dialog_open = False
|
||||
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()
|
||||
|
||||
@@ -949,7 +1104,7 @@ class NodeInfo(urwid.WidgetWrap):
|
||||
|
||||
self.dialog_open = True
|
||||
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):
|
||||
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.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)
|
||||
connect_button = urwid.Button("Browse", on_press=connect_query)
|
||||
reset_button = urwid.Button("Rst Stats", on_press=stats_query)
|
||||
|
||||
pile = urwid.Pile([
|
||||
t_id,
|
||||
e_name,
|
||||
urwid.Divider(g["divider1"]),
|
||||
e_lxmf,
|
||||
urwid.Divider(g["divider1"]),
|
||||
self.t_last_announce,
|
||||
self.t_active_links,
|
||||
self.t_total_connections,
|
||||
self.t_total_pages,
|
||||
self.t_total_files,
|
||||
urwid.Divider(g["divider1"]),
|
||||
urwid.Columns([
|
||||
("weight", 0.3, urwid.Button("Back", on_press=show_peer_info)),
|
||||
("weight", 0.1, urwid.Text("")),
|
||||
("weight", 0.4, connect_button),
|
||||
("weight", 0.1, urwid.Text("")),
|
||||
("weight", 0.5, announce_button)
|
||||
("weight", 5, urwid.Button("Back", on_press=show_peer_info)),
|
||||
("weight", 0.5, urwid.Text("")),
|
||||
("weight", 6, connect_button),
|
||||
("weight", 0.5, urwid.Text("")),
|
||||
("weight", 8, reset_button),
|
||||
("weight", 0.5, urwid.Text("")),
|
||||
("weight", 7, announce_button),
|
||||
])
|
||||
])
|
||||
else:
|
||||
@@ -1001,7 +1188,9 @@ class NodeInfo(urwid.WidgetWrap):
|
||||
if self.app.node != None:
|
||||
self.t_last_announce.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):
|
||||
@@ -1103,7 +1292,7 @@ class NetworkDisplay():
|
||||
self.list_display = 1
|
||||
self.left_pile = NetworkLeftPile([
|
||||
("weight", 1, self.known_nodes_display),
|
||||
("pack", self.network_stats_display),
|
||||
# ("pack", self.network_stats_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.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):
|
||||
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)
|
||||
display_name = self.app.directory.alleged_display_str(node_hash)
|
||||
if display_name != None:
|
||||
display_str += "\n "+str(display_name)
|
||||
display_str += " "+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")
|
||||
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)
|
||||
|
||||
self.display_widget = urwid.AttrMap(widget, style, focus_style)
|
||||
|
||||
Reference in New Issue
Block a user