Compare commits

...

21 Commits
0.3.1 ... 0.3.3

Author SHA1 Message Date
Mark Qvist
f4e3f1cb46 Excluded pycache from example inclusions 2023-02-04 19:06:16 +01:00
Mark Qvist
6aca6d671a Add node examples on first run 2023-02-04 17:04:16 +01:00
Mark Qvist
681295a818 Create examples directory on init 2023-02-04 16:43:50 +01:00
Mark Qvist
a4f5b28707 Renamed directory 2023-02-04 16:42:23 +01:00
Mark Qvist
e5ec9af77b Merge branch 'master' of github.com:markqvist/NomadNet 2023-02-04 16:06:30 +01:00
markqvist
d19fc081f1 Merge pull request #21 from chengtripp/chengtripp-messageboard-1
Add Simple Message Board Example
2023-02-04 16:07:06 +01:00
chengtripp
02a15f1503 Create README.md 2023-02-04 14:20:42 +00:00
chengtripp
570b99d814 Create messageboard.mu 2023-02-04 14:17:38 +00:00
chengtripp
f37ce14822 Create messageboard.py 2023-02-04 14:16:06 +00:00
Mark Qvist
8a1cb25403 Ensure PN autoselection on node save if trusted 2023-02-04 13:16:27 +01:00
Mark Qvist
16ca15c82c Updated guide 2023-02-04 13:16:00 +01:00
Mark Qvist
365a027f9b Updated version 2023-02-04 12:05:06 +01:00
Mark Qvist
e3074830d6 Updated dependencies 2023-02-04 12:04:34 +01:00
Mark Qvist
71ff0f9bcd Added feedback on opening link 2023-02-04 12:03:46 +01:00
Mark Qvist
78106b3f8c Fixed invalid indent 2023-02-04 11:50:13 +01:00
Mark Qvist
dc64ab26d4 Fixed browser cursor position on scrolling 2023-02-04 11:49:36 +01:00
Mark Qvist
a8d337a94e Fixed propagation node autoselection not working when None value was set in settings dict 2023-02-01 15:39:45 +01:00
Mark Qvist
c1cbc56459 Kill log viewer process before exiting urwid main loop 2023-02-01 14:23:04 +01:00
Mark Qvist
7cb53fe170 Request keys option for unknown announce edge case 2023-02-01 12:23:45 +01:00
Mark Qvist
fea602bbcb Dialog on missing announce key 2023-02-01 11:52:56 +01:00
Mark Qvist
9e9dd2a481 Updated dependencies and version 2023-01-14 21:15:21 +01:00
12 changed files with 413 additions and 27 deletions

View File

@@ -111,6 +111,7 @@ class NomadNetworkApp:
self.pagespath = self.configdir+"/storage/pages" self.pagespath = self.configdir+"/storage/pages"
self.filespath = self.configdir+"/storage/files" self.filespath = self.configdir+"/storage/files"
self.cachepath = self.configdir+"/storage/cache" self.cachepath = self.configdir+"/storage/cache"
self.examplespath = self.configdir+"/examples"
self.downloads_path = os.path.expanduser("~/Downloads") self.downloads_path = os.path.expanduser("~/Downloads")
@@ -167,6 +168,16 @@ class NomadNetworkApp:
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR) RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
nomadnet.panic() nomadnet.panic()
else: else:
if not os.path.isdir(self.examplespath):
try:
import shutil
examplespath = os.path.join(os.path.dirname(__file__), "examples")
shutil.copytree(examplespath, self.examplespath, ignore=shutil.ignore_patterns("__pycache__"))
except Exception as e:
RNS.log("Could not copy examples into the "+self.examplespath+" directory.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Could not load config file, creating default configuration file...") RNS.log("Could not load config file, creating default configuration file...")
self.createDefaultConfig() self.createDefaultConfig()
self.firstrun = True self.firstrun = True
@@ -456,9 +467,8 @@ class NomadNetworkApp:
def autoselect_propagation_node(self): def autoselect_propagation_node(self):
selected_node = None selected_node = None
if "propagation_node" in self.peer_settings: if "propagation_node" in self.peer_settings and self.peer_settings["propagation_node"] != None:
selected_node = self.peer_settings["propagation_node"] selected_node = self.peer_settings["propagation_node"]
else: else:
nodes = self.directory.known_nodes() nodes = self.directory.known_nodes()
trusted_nodes = [] trusted_nodes = []
@@ -706,7 +716,7 @@ class NomadNetworkApp:
self.config["textui"]["intro_text"] = "Nomad Network" self.config["textui"]["intro_text"] = "Nomad Network"
if not "editor" in self.config["textui"]: if not "editor" in self.config["textui"]:
self.config["textui"]["editor"] = "editor" self.config["textui"]["editor"] = "nano"
if not "glyphs" in self.config["textui"]: if not "glyphs" in self.config["textui"]:
self.config["textui"]["glyphs"] = "unicode" self.config["textui"]["glyphs"] = "unicode"
@@ -958,10 +968,8 @@ glyphs = unicode
# application. On by default. # application. On by default.
mouse_enabled = True mouse_enabled = True
# What editor to use for editing text. By # What editor to use for editing text.
# default the operating systems "editor" editor = nano
# alias will be used.
editor = editor
# If you don't want the Guide section to # If you don't want the Guide section to
# show up in the menu, you can disable it. # show up in the menu, you can disable it.

View File

@@ -1 +1 @@
__version__ = "0.3.1" __version__ = "0.3.3"

View File

@@ -0,0 +1,18 @@
# lxmf_messageboard
Simple message board that can be hosted on a NomadNet node, messages can be posted by 'conversing' with a unique peer, all messages are then forwarded to the message board.
## How Do I Use It?
A user can submit messages to the message board by initiating a chat with the message board peer, they are assigned a username (based on the first 5 characters of their address) and their messages are added directly to the message board. The message board can be viewed on a page hosted by a NomadNet node.
An example message board can be found on the reticulum testnet hosted on the SolarExpress Node `<d16df67bff870a8eaa2af6957c5a2d7d>` and the message board peer `<ad713cd3fedf36cc190f0cb89c4be1ff>`
## How Does It Work?
The message board page itself is hosted on a NomadNet node, you can place the message_board.mu into the pages directory. You can then run the message_board.py script which provides the peer that the users can send messages to. The two parts are joined together using umsgpack and a flat file system similar to NomadNet and Reticulum and runs in the background.
## How Do I Set It Up?
* Turn on node hosting in NomadNet
* Put the `message_board.mu` file into `pages` directory in the config file for `NomadNet`. Edit the file to customise from the default page.
* Run the `message_board.py` script (`python3 message_board.py` either in a `screen` or as a system service), this script uses `NomadNet` and `RNS` libraries and has no additional libraries that need to be installed. Take a note of the message boards address, it is printed on starting the board, you can then place this address in `message_board.mu` file to make it easier for users to interact the board.
## Credits
* This example application was written and contributed by @chengtripp

View File

@@ -0,0 +1,41 @@
#!/bin/python3
import time
import os
import RNS.vendor.umsgpack as msgpack
message_board_peer = 'please_replace'
userdir = os.path.expanduser("~")
if os.path.isdir("/etc/nomadmb") and os.path.isfile("/etc/nomadmb/config"):
configdir = "/etc/nomadmb"
elif os.path.isdir(userdir+"/.config/nomadmb") and os.path.isfile(userdir+"/.config/nomadmb/config"):
configdir = userdir+"/.config/nomadmb"
else:
configdir = userdir+"/.nomadmb"
storagepath = configdir+"/storage"
if not os.path.isdir(storagepath):
os.makedirs(storagepath)
boardpath = configdir+"/storage/board"
print('`!`F222`Bddd`cNomadNet Message Board')
print('-')
print('`a`b`f')
print("")
print("To add a message to the board just converse with the NomadNet Message Board at `[lxmf@{}]".format(message_board_peer))
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
print("Last Updated: {}".format(time_string))
print("")
print('>Messages')
print(" Date Time Username Message")
f = open(boardpath, "rb")
board_contents = msgpack.unpack(f)
board_contents.reverse()
for content in board_contents:
print("`a{}".format(content.rstrip()))
print("")
f.close()

View File

@@ -0,0 +1,187 @@
# Simple message board that can be hosted on a NomadNet node, messages can be posted by 'conversing' with a unique peer, all messages are then forwarded to the message board.
# https://github.com/chengtripp/lxmf_messageboard
import RNS
import LXMF
import os, time
from queue import Queue
import RNS.vendor.umsgpack as msgpack
display_name = "NomadNet Message Board"
max_messages = 20
def setup_lxmf():
if os.path.isfile(identitypath):
identity = RNS.Identity.from_file(identitypath)
RNS.log('Loaded identity from file', RNS.LOG_INFO)
else:
RNS.log('No Primary Identity file found, creating new...', RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identitypath)
return identity
def lxmf_delivery(message):
# Do something here with a received message
RNS.log("A message was received: "+str(message.content.decode('utf-8')))
message_content = message.content.decode('utf-8')
source_hash_text = RNS.hexrep(message.source_hash, delimit=False)
#Create username (just first 5 char of your addr)
username = source_hash_text[0:5]
RNS.log('Username: {}'.format(username), RNS.LOG_INFO)
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp))
new_message = '{} {}: {}\n'.format(time_string, username, message_content)
# Push message to board
# First read message board (if it exists
if os.path.isfile(boardpath):
f = open(boardpath, "rb")
message_board = msgpack.unpack(f)
f.close()
else:
message_board = []
#Check we aren't doubling up (this can sometimes happen if there is an error initially and it then gets fixed)
if new_message not in message_board:
# Append our new message to the list
message_board.append(new_message)
# Prune the message board if needed
while len(message_board) > max_messages:
RNS.log('Pruning Message Board')
message_board.pop(0)
# Now open the board and write the updated list
f = open(boardpath, "wb")
msgpack.pack(message_board, f)
f.close()
# Send reply
message_reply = '{}_{}_Your message has been added to the messageboard'.format(source_hash_text, time.time())
q.put(message_reply)
def announce_now(lxmf_destination):
lxmf_destination.announce()
def send_message(destination_hash, message_content):
try:
# Make a binary destination hash from a hexadecimal string
destination_hash = bytes.fromhex(destination_hash)
except Exception as e:
RNS.log("Invalid destination hash", RNS.LOG_ERROR)
return
# Check that size is correct
if not len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
RNS.log("Invalid destination hash length", RNS.LOG_ERROR)
else:
# Length of address was correct, let's try to recall the
# corresponding Identity
destination_identity = RNS.Identity.recall(destination_hash)
if destination_identity == None:
# No path/identity known, we'll have to abort or request one
RNS.log("Could not recall an Identity for the requested address. You have probably never received an announce from it. Try requesting a path from the network first. In fact, let's do this now :)", RNS.LOG_ERROR)
RNS.Transport.request_path(destination_hash)
RNS.log("OK, a path was requested. If the network knows a path, you will receive an announce with the Identity data shortly.", RNS.LOG_INFO)
else:
# We know the identity for the destination hash, let's
# reconstruct a destination object.
lxmf_destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery")
# Create a new message object
lxm = LXMF.LXMessage(lxmf_destination, local_lxmf_destination, message_content, title="Reply", desired_method=LXMF.LXMessage.DIRECT)
# You can optionally tell LXMF to try to send the message
# as a propagated message if a direct link fails
lxm.try_propagation_on_fail = True
# Send it
message_router.handle_outbound(lxm)
def announce_check():
if os.path.isfile(announcepath):
f = open(announcepath, "r")
announce = int(f.readline())
f.close()
else:
RNS.log('failed to open announcepath', RNS.LOG_DEBUG)
announce = 1
if announce > int(time.time()):
RNS.log('Recent announcement', RNS.LOG_DEBUG)
else:
f = open(announcepath, "w")
next_announce = int(time.time()) + 1800
f.write(str(next_announce))
f.close()
announce_now(local_lxmf_destination)
RNS.log('Announcement sent, expr set 1800 seconds', RNS.LOG_INFO)
#Setup Paths and Config Files
userdir = os.path.expanduser("~")
if os.path.isdir("/etc/nomadmb") and os.path.isfile("/etc/nomadmb/config"):
configdir = "/etc/nomadmb"
elif os.path.isdir(userdir+"/.config/nomadmb") and os.path.isfile(userdir+"/.config/nomadmb/config"):
configdir = userdir+"/.config/nomadmb"
else:
configdir = userdir+"/.nomadmb"
storagepath = configdir+"/storage"
if not os.path.isdir(storagepath):
os.makedirs(storagepath)
identitypath = configdir+"/storage/identity"
announcepath = configdir+"/storage/announce"
boardpath = configdir+"/storage/board"
# Message Queue
q = Queue(maxsize = 5)
# Start Reticulum and print out all the debug messages
reticulum = RNS.Reticulum(loglevel=RNS.LOG_VERBOSE)
# Create a Identity.
current_identity = setup_lxmf()
# Init the LXMF router
message_router = LXMF.LXMRouter(identity = current_identity, storagepath = configdir)
# Register a delivery destination (for yourself)
# In this example we use the same Identity as we used
# to instantiate the LXMF router. It could be a different one,
# but it can also just be the same, depending on what you want.
local_lxmf_destination = message_router.register_delivery_identity(current_identity, display_name=display_name)
# Set a callback for when a message is received
message_router.register_delivery_callback(lxmf_delivery)
# Announce node properties
RNS.log('LXMF Router ready to receive on: {}'.format(RNS.prettyhexrep(local_lxmf_destination.hash)), RNS.LOG_INFO)
announce_check()
while True:
# Work through internal message queue
for i in list(q.queue):
message_id = q.get()
split_message = message_id.split('_')
destination_hash = split_message[0]
message = split_message[2]
RNS.log('{} {}'.format(destination_hash, message), RNS.LOG_INFO)
send_message(destination_hash, message)
# Check whether we need to make another announcement
announce_check()
#Sleep
time.sleep(10)

View File

@@ -26,13 +26,32 @@ class BrowserFrame(urwid.Frame):
elif key == "ctrl g": elif key == "ctrl g":
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.sub_displays.network_display.toggle_fullscreen() nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.sub_displays.network_display.toggle_fullscreen()
elif self.get_focus() == "body": elif self.get_focus() == "body":
if key == "down" or key == "up":
try:
if hasattr(self.delegate, "page_pile") and self.delegate.page_pile:
def df(loop, user_data):
st = None
nf = self.delegate.page_pile.get_focus()
if hasattr(nf, "key_timeout"):
st = nf
elif hasattr(nf, "original_widget"):
no = nf.original_widget
if hasattr(no, "original_widget"):
st = no.original_widget
else:
if hasattr(no, "key_timeout"):
st = no
if st and hasattr(st, "key_timeout") and hasattr(st, "keypress") and callable(st.keypress):
st.keypress(None, None)
nomadnet.NomadNetworkApp.get_shared_instance().ui.loop.set_alarm_in(0.25, df)
except Exception as e:
RNS.log("Error while setting up cursor timeout. The contained exception was: "+str(e), RNS.LOG_ERROR)
return super(BrowserFrame, self).keypress(size, key) return super(BrowserFrame, self).keypress(size, key)
# if key == "up" and self.delegate.messagelist.top_is_visible:
# nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
# elif key == "down" and self.delegate.messagelist.bottom_is_visible:
# self.set_focus("footer")
# else:
# return super(ConversationFrame, self).keypress(size, key)
else: else:
return super(BrowserFrame, self).keypress(size, key) return super(BrowserFrame, self).keypress(size, key)
@@ -80,6 +99,7 @@ class Browser:
self.link_target = None self.link_target = None
self.frame = None self.frame = None
self.attr_maps = [] self.attr_maps = []
self.page_pile = None
self.build_display() self.build_display()
self.history = [] self.history = []
@@ -155,6 +175,7 @@ class Browser:
if destination_type == "nomadnetwork.node": if destination_type == "nomadnetwork.node":
if self.status >= Browser.DISCONECTED: if self.status >= Browser.DISCONECTED:
RNS.log("Browser handling link to: "+str(link_target), RNS.LOG_DEBUG) RNS.log("Browser handling link to: "+str(link_target), RNS.LOG_DEBUG)
self.browser_footer = urwid.Text("Opening link to: "+str(link_target))
try: try:
self.retrieve_url(link_target) self.retrieve_url(link_target)
except Exception as e: except Exception as e:
@@ -219,6 +240,7 @@ class Browser:
self.browser_header = urwid.Text("") self.browser_header = urwid.Text("")
self.browser_footer = urwid.Text("") self.browser_footer = urwid.Text("")
self.page_pile = None
self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle") self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle")
self.frame = BrowserFrame(self.browser_body, header=self.browser_header, footer=self.browser_footer) self.frame = BrowserFrame(self.browser_body, header=self.browser_header, footer=self.browser_footer)
@@ -266,6 +288,7 @@ class Browser:
def update_display(self): def update_display(self):
if self.status == Browser.DISCONECTED: if self.status == Browser.DISCONECTED:
self.display_widget.set_attr_map({None: "inactive_text"}) self.display_widget.set_attr_map({None: "inactive_text"})
self.page_pile = None
self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle") self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle")
self.browser_footer = urwid.Text("") self.browser_footer = urwid.Text("")
self.browser_header = urwid.Text("") self.browser_header = urwid.Text("")
@@ -314,6 +337,8 @@ class Browser:
def update_page_display(self): def update_page_display(self):
pile = urwid.Pile(self.attr_maps) pile = urwid.Pile(self.attr_maps)
pile.automove_cursor_on_scroll = True
self.page_pile = pile
self.browser_body = urwid.AttrMap(ScrollBar(Scrollable(pile, force_forward_keypress=True), thumb_char="\u2503", trough_char=" "), "scrollbar") self.browser_body = urwid.AttrMap(ScrollBar(Scrollable(pile, force_forward_keypress=True), thumb_char="\u2503", trough_char=" "), "scrollbar")
def identify(self): def identify(self):

View File

@@ -312,14 +312,22 @@ The distributed message store is resilient to intermittency, and will remain fun
>>Pages >>Pages
Nomad Network nodes can host pages similar to web pages, that other peers can read and interact with. Pages are written in a compact markup language called `*micron`*. To learn how to write formatted pages with micron, see the `*Markup`* section of this guide (which is, itself, written in micron). Pages can be linked arbitrarily with hyperlinks, that can also link to pages (or other resources) on other nodes. Nomad Network nodes can host pages similar to web pages, that other peers can read and interact with. Pages are written in a compact markup language called `*micron`*. To learn how to write formatted pages with micron, see the `*Markup`* section of this guide (which is, itself, written in micron). Pages can be linked together with hyperlinks, that can also link to pages (or other resources) on other nodes.
To add pages to your node, place micron files in the `*pages`* directory of your Nomad Network programs `*storage`* directory. By default, the path to this will be `!~/.nomadnetwork/storage/pages`!. You should probably create the file `!index.mu`! first, as this is the page that will get served by default to a connecting peer. To add pages to your node, place micron files in the `*pages`* directory of your Nomad Network programs `*storage`* directory. By default, the path to this will be `!~/.nomadnetwork/storage/pages`!. You should probably create the file `!index.mu`! first, as this is the page that will get served by default to a connecting peer.
You can control how long a peer will cache your pages by including the cache header in a page. To do so, the first line of your page must start with `!#!c=X`!, where `!X`! is the cache time in seconds. To tell the peer to always load the page from your node, and never cache it, set the cache time to zero. You should only do this if there is a real need, for example if your page displays dynamic content that `*must`* be updated at every page view. The default caching time is 12 hours. In most cases, you should not need to include the cache control header in your pages. You can control how long a peer will cache your pages by including the cache header in a page. To do so, the first line of your page must start with `!#!c=X`!, where `!X`! is the cache time in seconds. To tell the peer to always load the page from your node, and never cache it, set the cache time to zero. You should only do this if there is a real need, for example if your page displays dynamic content that `*must`* be updated at every page view. The default caching time is 12 hours. In most cases, you should not need to include the cache control header in your pages.
>> Dynamic Pages
You can use a preprocessor such as PHP, bash, Python (or whatever you prefer) to generate dynamic pages. To do so, just set executable permissions on the relevant page file, and be sure to include the interpreter at the beginning of the file, for example `!#!/usr/bin/python3`!. You can use a preprocessor such as PHP, bash, Python (or whatever you prefer) to generate dynamic pages. To do so, just set executable permissions on the relevant page file, and be sure to include the interpreter at the beginning of the file, for example `!#!/usr/bin/python3`!.
In the `!examples`! directory, you can find various small examples for the use of this feature. The currently included examples are:
- A messageboard that receives messages over LXMF, contributed by trippcheng
By default, you can find the examples in `!~/.nomadnetwork/examples`!. If you build something neat, that you feel would fit here, you are more than welcome to contribute it.
>>Authenticating Users >>Authenticating Users
Sometimes, you don't want everyone to be able to view certain pages or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests. Sometimes, you don't want everyone to be able to view certain pages or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests.
@@ -330,9 +338,9 @@ For each user allowed to access the page, add a line to this file, containing th
`Faaa `Faaa
`= `=
24c2dc2248953e0a3c21 d454bcdac0e64fb68ba8e267543ae110
564a476410f4fca2ae93 2b9ff3fb5902c9ca5ff97bdfb239ef50
8bb864743f12f53395a1 7106d5abbc7208bfb171f2dd84b36490
`= `=
`` ``
@@ -348,7 +356,7 @@ Like pages, you can place files you want to make available in the `!~/.nomadnetw
Links to pages and resources in Nomad Network use a simple URL format. Here is an example: Links to pages and resources in Nomad Network use a simple URL format. Here is an example:
`!1385edace36466a6b3dd:/page/index.mu`! `!18176ffddcc8cce1ddf8e3f72068f4a6:/page/index.mu`!
The first part is the 10 byte destination address of the node (represented as readable hexadecimal), followed by the `!:`! character. Everything after the `!:`! represents the request path. The first part is the 10 byte destination address of the node (represented as readable hexadecimal), followed by the `!:`! character. Everything after the `!:`! represents the request path.

View File

@@ -140,6 +140,15 @@ class MainDisplay():
self.menu_display.start() self.menu_display.start()
def quit(self, sender=None): def quit(self, sender=None):
logterm_pid = None
if True or RNS.vendor.platformutils.is_android():
if self.sub_displays.log_display != None and self.sub_displays.log_display.log_term != None:
if self.sub_displays.log_display.log_term.log_term != None:
logterm_pid = self.sub_displays.log_display.log_term.log_term.pid
if logterm_pid != None:
import os, signal
os.kill(logterm_pid, signal.SIGKILL)
raise urwid.ExitMainLoop raise urwid.ExitMainLoop

View File

@@ -239,7 +239,7 @@ class AnnounceInfo(urwid.WidgetWrap):
class AnnounceStreamEntry(urwid.WidgetWrap): class AnnounceStreamEntry(urwid.WidgetWrap):
def __init__(self, app, announce): def __init__(self, app, announce, delegate):
full_time_format = "%Y-%m-%d %H:%M:%S" full_time_format = "%Y-%m-%d %H:%M:%S"
date_time_format = "%Y-%m-%d" date_time_format = "%Y-%m-%d"
time_time_format = "%H:%M:%S" time_time_format = "%H:%M:%S"
@@ -250,6 +250,7 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
source_hash = announce[1] source_hash = announce[1]
announce_type = announce[3] announce_type = announce[3]
self.app = app self.app = app
self.delegate = delegate
self.timestamp = timestamp self.timestamp = timestamp
time_format = app.time_format time_format = app.time_format
dt = datetime.fromtimestamp(self.timestamp) dt = datetime.fromtimestamp(self.timestamp)
@@ -299,10 +300,46 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
urwid.WidgetWrap.__init__(self, self.display_widget) urwid.WidgetWrap.__init__(self, self.display_widget)
def display_announce(self, event, announce): def display_announce(self, event, announce):
try:
parent = self.app.ui.main_display.sub_displays.network_display parent = self.app.ui.main_display.sub_displays.network_display
info_widget = AnnounceInfo(announce, parent, self.app) info_widget = AnnounceInfo(announce, parent, self.app)
options = parent.left_pile.options(height_type="weight", height_amount=1) options = parent.left_pile.options(height_type="weight", height_amount=1)
parent.left_pile.contents[0] = (info_widget, options) parent.left_pile.contents[0] = (info_widget, options)
except KeyError as e:
def dismiss_dialog(sender):
self.delegate.parent.close_list_dialogs()
def confirmed(sender):
def close_req(sender):
self.delegate.parent.close_list_dialogs()
dialog_pile.contents[0] = (urwid.Text("\nKeys requested from network\n", align="center"), options)
RNS.Transport.request_path(announce[1])
confirmed_button = urwid.Button("Request keys", on_press=confirmed)
dialog_pile = urwid.Pile([
urwid.Text("The keys for the announced destination could not be recalled. You can wait for an announce to arrive, or request the keys from the network.\n", align="center"),
urwid.Columns([
("weight", 0.45, confirmed_button),
("weight", 0.1, urwid.Text("")),
("weight", 0.45, urwid.Button("Close", on_press=dismiss_dialog)),
])
])
dialog = ListDialogLineBox(
dialog_pile,
title="Keys Unknown"
)
confirmed_button.dialog_pile = dialog_pile
dialog.delegate = self.delegate.parent
bottom = self.delegate
overlay = urwid.Overlay(dialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2)
options = self.delegate.parent.left_pile.options("weight", 1)
self.delegate.parent.left_pile.contents[0] = (overlay, options)
def timestamp(self): def timestamp(self):
return self.timestamp return self.timestamp
@@ -358,7 +395,7 @@ class AnnounceStream(urwid.WidgetWrap):
new_entries.insert(0, e) new_entries.insert(0, e)
for e in new_entries: for e in new_entries:
nw = AnnounceStreamEntry(self.app, e) nw = AnnounceStreamEntry(self.app, e, self)
nw.timestamp = e[0] nw.timestamp = e[0]
self.widget_list.insert(0, nw) self.widget_list.insert(0, nw)
@@ -573,6 +610,10 @@ class KnownNodeInfo(urwid.WidgetWrap):
node_entry = DirectoryEntry(source_hash, display_name=display_str, trust_level=trust_level, hosts_node=True, identify_on_connect=connect_identify_checkbox.get_state()) node_entry = DirectoryEntry(source_hash, display_name=display_str, trust_level=trust_level, hosts_node=True, identify_on_connect=connect_identify_checkbox.get_state())
self.app.directory.remember(node_entry) self.app.directory.remember(node_entry)
self.app.ui.main_display.sub_displays.network_display.directory_change_callback() self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
if trust_level == DirectoryEntry.TRUSTED:
self.app.autoselect_propagation_node()
show_known_nodes(None) show_known_nodes(None)
back_button = ("weight", 0.2, urwid.Button("Back", on_press=show_known_nodes)) back_button = ("weight", 0.2, urwid.Button("Back", on_press=show_known_nodes))
@@ -667,7 +708,7 @@ class KnownNodes(urwid.WidgetWrap):
else: else:
self.no_content = True self.no_content = True
widget_style = "inactive_text" widget_style = "inactive_text"
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 saved\n\nCtrl+L to view the announce stream\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="Saved Nodes"), widget_style)) urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="Saved Nodes"), widget_style))
@@ -1498,10 +1539,6 @@ class NetworkDisplay():
self.local_peer_display.start() self.local_peer_display.start()
self.node_info_display.start() self.node_info_display.start()
self.network_stats_display.start() self.network_stats_display.start()
# There seems to be an intermittent memory leak somewhere
# in the periodic updating here. The periodic updater should
# not be needed anymore, so dis
#self.announce_stream_display.start()
def shortcuts(self): def shortcuts(self):
return self.shortcuts_display return self.shortcuts_display

View File

@@ -107,6 +107,51 @@ class Scrollable(urwid.WidgetDecoration):
if canv_full.cursor is not None: if canv_full.cursor is not None:
# Full canvas contains the cursor, but scrolled out of view # Full canvas contains the cursor, but scrolled out of view
self._forward_keypress = False self._forward_keypress = False
# Reset cursor position on page/up down scrolling
try:
if hasattr(ow, "automove_cursor_on_scroll") and ow.automove_cursor_on_scroll:
pwi = 0
ch = 0
last_hidden = False
first_visible = False
for w,o in ow.contents:
wcanv = w.render((maxcol,))
wh = wcanv.rows()
if wh:
ch += wh
if not last_hidden and ch >= self._trim_top:
last_hidden = True
elif last_hidden:
if not first_visible:
first_visible = True
if w.selectable():
ow.focus_item = pwi
st = None
nf = ow.get_focus()
if hasattr(nf, "key_timeout"):
st = nf
elif hasattr(nf, "original_widget"):
no = nf.original_widget
if hasattr(no, "original_widget"):
st = no.original_widget
else:
if hasattr(no, "key_timeout"):
st = no
if st and hasattr(st, "key_timeout") and hasattr(st, "keypress") and callable(st.keypress):
st.keypress(None, None)
break
pwi += 1
except Exception as e:
pass
else: else:
# Original widget does not have a cursor, but may be selectable # Original widget does not have a cursor, but may be selectable

View File

@@ -3,4 +3,5 @@ quotes = [
("That's enough entropy for you my friend", "Unknown"), ("That's enough entropy for you my friend", "Unknown"),
("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier"), ("Any time two people connect online, it's financed by a third person who believes they can manipulate the first two", "Jaron Lanier"),
("The landscape of the future is set, but how one will march across it is not determined", "Terence McKenna") ("The landscape of the future is set, but how one will march across it is not determined", "Terence McKenna")
("Freedom originates in the division of power, despotism in its concentration.", "John Acton")
] ]

View File

@@ -5,6 +5,12 @@ exec(open("nomadnet/_version.py", "r").read())
with open("README.md", "r") as fh: with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
package_data = {
"": [
"examples/messageboard/*",
]
}
setuptools.setup( setuptools.setup(
name="nomadnet", name="nomadnet",
version=__version__, version=__version__,
@@ -15,6 +21,7 @@ setuptools.setup(
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/markqvist/nomadnet", url="https://github.com/markqvist/nomadnet",
packages=setuptools.find_packages(), packages=setuptools.find_packages(),
package_data=package_data,
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
@@ -23,6 +30,6 @@ setuptools.setup(
entry_points= { entry_points= {
'console_scripts': ['nomadnet=nomadnet.nomadnet:main'] 'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
}, },
install_requires=["rns>=0.4.6", "lxmf>=0.2.8", "urwid>=2.1.2", "qrcode"], install_requires=["rns>=0.4.8", "lxmf>=0.3.0", "urwid>=2.1.2", "qrcode"],
python_requires=">=3.6", python_requires=">=3.6",
) )