mirror of
https://github.com/markqvist/NomadNet.git
synced 2024-12-28 08:39:30 -05:00
Implemented basic node hosting and page serving.
This commit is contained in:
parent
55e8479979
commit
862f4835c7
@ -26,7 +26,7 @@ class Conversation:
|
||||
|
||||
# Add the announce to the directory announce
|
||||
# stream logger
|
||||
app.directory.announce_received(destination_hash, app_data)
|
||||
app.directory.lxmf_announce_received(destination_hash, app_data)
|
||||
|
||||
@staticmethod
|
||||
def query_for_peer(source_hash):
|
||||
|
@ -5,7 +5,7 @@ import time
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
|
||||
class Directory:
|
||||
ANNOUNCE_STREAM_MAXLENGTH = 256
|
||||
ANNOUNCE_STREAM_MAXLENGTH = 64
|
||||
|
||||
def __init__(self, app):
|
||||
self.directory_entries = {}
|
||||
@ -54,7 +54,13 @@ class Directory:
|
||||
except Exception as e:
|
||||
RNS.log("Could not load directory from disk. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def announce_received(self, source_hash, app_data):
|
||||
def lxmf_announce_received(self, source_hash, app_data):
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
|
||||
def node_announce_received(self, source_hash, app_data):
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
|
124
nomadnet/Node.py
Normal file
124
nomadnet/Node.py
Normal file
@ -0,0 +1,124 @@
|
||||
import os
|
||||
import RNS
|
||||
import time
|
||||
import threading
|
||||
import RNS.vendor.umsgpack as msgpack
|
||||
|
||||
class Node:
|
||||
JOB_INTERVAL = 5
|
||||
|
||||
def __init__(self, app):
|
||||
RNS.log("Nomad Network Node starting...", RNS.LOG_VERBOSE)
|
||||
self.app = app
|
||||
self.identity = self.app.identity
|
||||
self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, "nomadnetwork", "node")
|
||||
self.last_announce = None
|
||||
self.announce_interval = self.app.node_announce_interval
|
||||
self.job_interval = Node.JOB_INTERVAL
|
||||
self.should_run_jobs = True
|
||||
self.app_data = None
|
||||
self.name = self.app.node_name
|
||||
|
||||
self.register_pages()
|
||||
self.register_files()
|
||||
|
||||
if self.app.node_announce_at_start:
|
||||
self.announce()
|
||||
|
||||
if self.name == None:
|
||||
name_string = self.app.peer_settings["display_name"]+"'s Node"
|
||||
else:
|
||||
name_string = self.name
|
||||
|
||||
RNS.log("Node \""+name_string+"\" ready for incoming connections on "+RNS.prettyhexrep(self.destination.hash), RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def register_pages(self):
|
||||
self.servedpages = []
|
||||
self.scan_pages(self.app.pagespath)
|
||||
|
||||
if not self.app.pagespath+"index.mu" in self.servedpages:
|
||||
self.destination.register_request_handler(
|
||||
"/page/index.mu",
|
||||
response_generator = self.serve_default_index,
|
||||
allow = RNS.Destination.ALLOW_ALL
|
||||
)
|
||||
|
||||
for page in self.servedpages:
|
||||
request_path = "/page"+page.replace(self.app.pagespath, "")
|
||||
|
||||
self.destination.register_request_handler(
|
||||
request_path,
|
||||
response_generator = self.serve_page,
|
||||
allow = RNS.Destination.ALLOW_ALL
|
||||
)
|
||||
|
||||
def register_files(self):
|
||||
self.servedfiles = []
|
||||
self.scan_files(self.app.filespath)
|
||||
|
||||
def scan_pages(self, base_path):
|
||||
files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
|
||||
for file in files:
|
||||
self.servedpages.append(base_path+"/"+file)
|
||||
|
||||
for directory in directories:
|
||||
self.scan_pages(base_path+"/"+directory)
|
||||
|
||||
def scan_files(self, base_path):
|
||||
files = [file for file in os.listdir(base_path) if os.path.isfile(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."]
|
||||
|
||||
for file in files:
|
||||
self.servedfiles.append(base_path+"/"+file)
|
||||
|
||||
for directory in directories:
|
||||
self.scan_files(base_path+"/"+directory)
|
||||
|
||||
def serve_page(self, path, data, request_id, remote_identity, requested_at):
|
||||
RNS.log("Request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
|
||||
file_path = path.replace("/page", self.app.pagespath, 1)
|
||||
try:
|
||||
RNS.log("Serving file: "+file_path, RNS.LOG_VERBOSE)
|
||||
fh = open(file_path, "rb")
|
||||
response_data = fh.read()
|
||||
fh.close()
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
|
||||
def serve_default_index(self, path, data, request_id, remote_identity, requested_at):
|
||||
RNS.log("Serving default index for request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
|
||||
return DEFAULT_INDEX.encode("utf-8")
|
||||
|
||||
def announce(self):
|
||||
self.app_data = self.name.encode("utf-8")
|
||||
self.last_announce = time.time()
|
||||
self.destination.announce(app_data=self.app_data)
|
||||
|
||||
def __jobs(self):
|
||||
while self.should_run_jobs:
|
||||
now = time.time()
|
||||
|
||||
if now > self.last_announce + self.announce_interval:
|
||||
self.announce()
|
||||
|
||||
time.sleep(self.job_interval)
|
||||
|
||||
def peer_connected(link):
|
||||
RNS.log("Peer connected to "+str(self.destination), RNS.LOG_INFO)
|
||||
link.set_link_closed_callback(self.peer_disconnected)
|
||||
|
||||
|
||||
DEFAULT_INDEX = '''>Default Home Page
|
||||
|
||||
This node is serving pages, but no home page file (index.mu) was found in the page storage directory. This is an auto-generated placeholder.
|
||||
|
||||
If you are the node operator, you can define your own home page by creating a file named `*index.mu`* in the page storage directory.
|
||||
'''
|
@ -51,6 +51,9 @@ class NomadNetworkApp:
|
||||
self.directorypath = self.configdir+"/storage/directory"
|
||||
self.peersettingspath = self.configdir+"/storage/peersettings"
|
||||
|
||||
self.pagespath = self.configdir+"/storage/pages"
|
||||
self.filespath = self.configdir+"/storage/files"
|
||||
|
||||
if not os.path.isdir(self.storagepath):
|
||||
os.makedirs(self.storagepath)
|
||||
|
||||
@ -63,6 +66,12 @@ class NomadNetworkApp:
|
||||
if not os.path.isdir(self.conversationpath):
|
||||
os.makedirs(self.conversationpath)
|
||||
|
||||
if not os.path.isdir(self.pagespath):
|
||||
os.makedirs(self.pagespath)
|
||||
|
||||
if not os.path.isdir(self.filespath):
|
||||
os.makedirs(self.filespath)
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try:
|
||||
self.config = ConfigObj(self.configpath)
|
||||
@ -151,6 +160,11 @@ class NomadNetworkApp:
|
||||
|
||||
self.directory = nomadnet.Directory.Directory(self)
|
||||
|
||||
if self.enable_node:
|
||||
self.node = nomadnet.Node(self)
|
||||
else:
|
||||
self.node = None
|
||||
|
||||
nomadnet.ui.spawn(self.uimode)
|
||||
|
||||
def set_display_name(self, display_name):
|
||||
@ -295,13 +309,40 @@ class NomadNetworkApp:
|
||||
self.uimode = nomadnet.ui.UI_WEB
|
||||
|
||||
if "node" in self.config:
|
||||
for option in self.config["node"]:
|
||||
value = self.config["node"][option]
|
||||
if not "enable_node" in self.config["node"]:
|
||||
self.enable_node = False
|
||||
else:
|
||||
self.enable_node = self.config["node"].as_bool("enable_node")
|
||||
|
||||
if option == "enable_node":
|
||||
value = self.config["node"].as_bool(option)
|
||||
self.enable_node = value
|
||||
if not "node_name" in self.config["node"]:
|
||||
self.node_name = None
|
||||
else:
|
||||
value = self.config["node"]["node_name"]
|
||||
if value.lower() == "none":
|
||||
self.node_name = None
|
||||
else:
|
||||
self.node_name = self.config["node"]["node_name"]
|
||||
|
||||
if not "announce_at_start" in self.config["node"]:
|
||||
self.node_announce_at_start = False
|
||||
else:
|
||||
self.node_announce_at_start = self.config["announce_at_start"].as_bool("announce_at_start")
|
||||
|
||||
if not "announce_interval" in self.config["node"]:
|
||||
self.node_announce_interval = 720
|
||||
else:
|
||||
value = self.config["announce_interval"].as_int("announce_interval")
|
||||
if value < 1:
|
||||
value = 1
|
||||
self.node_announce_interval = value
|
||||
|
||||
if "pages_path" in self.config["node"]:
|
||||
self.pagespath = self.config["node"]["pages_path"]
|
||||
|
||||
if "files_path" in self.config["node"]:
|
||||
self.filespath = self.config["node"]["files_path"]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_shared_instance():
|
||||
if NomadNetworkApp._shared_instance != None:
|
||||
@ -385,6 +426,23 @@ 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.
|
||||
# 12 hours by default.
|
||||
|
||||
announce_interval = 720
|
||||
|
||||
# Whether to announce when the node starts
|
||||
|
||||
announce_at_start = No
|
||||
|
||||
'''.splitlines()
|
@ -3,6 +3,7 @@ import glob
|
||||
|
||||
from .NomadNetworkApp import NomadNetworkApp
|
||||
from .Conversation import Conversation
|
||||
from .Node import Node
|
||||
from .ui import *
|
||||
|
||||
|
||||
|
@ -168,7 +168,7 @@ A `*peer`* refers to another Nomad Network client, which will generally be opera
|
||||
|
||||
An `*announce`* can be sent by any peer on the network, which will notify other peers of its existence, and contains the cryptographic keys that allows other peers to communicate with it.
|
||||
|
||||
In the `![ Network ]`! section of the program, you can monitor announces on the network, initiate conversations with announced peers, and announce your own peer on the network.
|
||||
In the `![ Network ]`! section of the program, you can monitor announces on the network, initiate conversations with announced peers, and announce your own peer on the network. You can also connect to nodes on the network and browse information shared by them.
|
||||
|
||||
>>Conversations
|
||||
|
||||
@ -188,8 +188,7 @@ If no nodes exist on a network, all peers will still be able to communicate dire
|
||||
|
||||
'''
|
||||
|
||||
TOPIC_CONVERSATIONS = '''Conversations
|
||||
=============
|
||||
TOPIC_CONVERSATIONS = '''>Conversations
|
||||
|
||||
Conversations in Nomad Network
|
||||
'''
|
||||
|
@ -10,7 +10,7 @@ class NetworkDisplayShortcuts():
|
||||
self.app = app
|
||||
g = app.ui.glyphs
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-"+g["arrow_u"]+g["arrow_d"]+"] Navigate announces"), "shortcutbar")
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-l] View Nodes/Announces [C-"+g["arrow_u"]+g["arrow_d"]+"] Navigate Lists"), "shortcutbar")
|
||||
|
||||
|
||||
class DialogLineBox(urwid.LineBox):
|
||||
@ -93,7 +93,7 @@ class AnnounceInfo(urwid.WidgetWrap):
|
||||
|
||||
def show_announce_stream(sender):
|
||||
options = self.parent.left_pile.options(height_type="weight", height_amount=1)
|
||||
self.parent.left_pile.contents[1] = (AnnounceStream(self.app, self.parent), options)
|
||||
self.parent.left_pile.contents[0] = (AnnounceStream(self.app, self.parent), options)
|
||||
|
||||
def converse(sender):
|
||||
show_announce_stream(None)
|
||||
@ -176,7 +176,7 @@ class AnnounceStreamEntry(urwid.WidgetWrap):
|
||||
parent = self.app.ui.main_display.sub_displays.network_display
|
||||
info_widget = AnnounceInfo(announce, parent, self.app)
|
||||
options = parent.left_pile.options(height_type="weight", height_amount=1)
|
||||
parent.left_pile.contents[1] = (info_widget, options)
|
||||
parent.left_pile.contents[0] = (info_widget, options)
|
||||
|
||||
class AnnounceStream(urwid.WidgetWrap):
|
||||
def __init__(self, app, parent):
|
||||
@ -203,6 +203,12 @@ class AnnounceStream(urwid.WidgetWrap):
|
||||
self.display_widget = self.ilb
|
||||
urwid.WidgetWrap.__init__(self, urwid.LineBox(self.display_widget, title="Announce Stream"))
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key == "up":
|
||||
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
||||
|
||||
return super(AnnounceStream, self).keypress(size, key)
|
||||
|
||||
def rebuild_widget_list(self):
|
||||
self.added_entries = []
|
||||
self.widget_list = []
|
||||
@ -287,7 +293,8 @@ class KnownNodes(urwid.WidgetWrap):
|
||||
else:
|
||||
self.no_content = True
|
||||
widget_style = "inactive_text"
|
||||
self.display_widget = 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")
|
||||
|
||||
urwid.WidgetWrap.__init__(self, urwid.AttrMap(urwid.LineBox(self.display_widget, title="Known Nodes"), widget_style))
|
||||
|
||||
@ -361,7 +368,7 @@ class LocalPeer(urwid.WidgetWrap):
|
||||
def save_query(sender):
|
||||
def dismiss_dialog(sender):
|
||||
self.dialog_open = False
|
||||
self.parent.left_pile.contents[3] = (LocalPeer(self.app, self.parent), options)
|
||||
self.parent.left_pile.contents[2] = (LocalPeer(self.app, self.parent), options)
|
||||
|
||||
self.app.set_display_name(e_name.get_edit_text())
|
||||
|
||||
@ -378,13 +385,13 @@ 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[3] = (overlay, options)
|
||||
self.parent.left_pile.contents[2] = (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[3] = (LocalPeer(self.app, self.parent), options)
|
||||
self.parent.left_pile.contents[2] = (LocalPeer(self.app, self.parent), options)
|
||||
|
||||
self.app.announce_now()
|
||||
|
||||
@ -402,11 +409,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[3] = (overlay, options)
|
||||
self.parent.left_pile.contents[2] = (overlay, options)
|
||||
|
||||
def node_settings_query(sender):
|
||||
options = self.parent.left_pile.options(height_type="pack", height_amount=None)
|
||||
self.parent.left_pile.contents[3] = (self.parent.node_settings_display, options)
|
||||
self.parent.left_pile.contents[2] = (self.parent.node_settings_display, options)
|
||||
|
||||
if LocalPeer.announce_timer == None:
|
||||
self.t_last_announce = AnnounceTime(self.app)
|
||||
@ -443,7 +450,7 @@ class NodeSettings(urwid.WidgetWrap):
|
||||
|
||||
def show_peer_info(sender):
|
||||
options = self.parent.left_pile.options(height_type="pack", height_amount=None)
|
||||
self.parent.left_pile.contents[3] = (LocalPeer(self.app, self.parent), options)
|
||||
self.parent.left_pile.contents[2] = (LocalPeer(self.app, self.parent), options)
|
||||
|
||||
widget_style = "inactive_text"
|
||||
pile = urwid.Pile([
|
||||
@ -518,6 +525,16 @@ class NetworkStats(urwid.WidgetWrap):
|
||||
self.w_heard_peers.start()
|
||||
self.w_known_nodes.start()
|
||||
|
||||
|
||||
|
||||
class NetworkLeftPile(urwid.Pile):
|
||||
def keypress(self, size, key):
|
||||
if key == "ctrl l":
|
||||
self.parent.toggle_list()
|
||||
else:
|
||||
return super(NetworkLeftPile, self).keypress(size, key)
|
||||
|
||||
|
||||
class NetworkDisplay():
|
||||
list_width = 0.33
|
||||
|
||||
@ -525,19 +542,21 @@ class NetworkDisplay():
|
||||
self.app = app
|
||||
g = self.app.ui.glyphs
|
||||
|
||||
self.known_nodes_display = KnownNodes(self.app)
|
||||
self.known_nodes_display = None
|
||||
self.network_stats_display = NetworkStats(self.app, self)
|
||||
self.announce_stream_display = AnnounceStream(self.app, self)
|
||||
self.local_peer_display = LocalPeer(self.app, self)
|
||||
self.node_settings_display = NodeSettings(self.app, self)
|
||||
|
||||
self.left_pile = urwid.Pile([
|
||||
("pack", self.known_nodes_display),
|
||||
self.list_display = 0
|
||||
self.left_pile = NetworkLeftPile([
|
||||
("weight", 1, self.announce_stream_display),
|
||||
("pack", self.network_stats_display),
|
||||
("pack", self.local_peer_display),
|
||||
])
|
||||
|
||||
self.left_pile.parent = self
|
||||
|
||||
self.left_area = self.left_pile
|
||||
self.right_area = urwid.AttrMap(urwid.LineBox(urwid.Filler(urwid.Text("Disconnected\n"+g["arrow_l"]+" "+g["arrow_r"], align="center"), "middle"), title="Remote Node"), "inactive_text")
|
||||
|
||||
@ -552,6 +571,18 @@ class NetworkDisplay():
|
||||
self.shortcuts_display = NetworkDisplayShortcuts(self.app)
|
||||
self.widget = self.columns
|
||||
|
||||
def toggle_list(self):
|
||||
if self.list_display != 0:
|
||||
self.announce_stream_display = AnnounceStream(self.app, self)
|
||||
options = self.left_pile.options(height_type="weight", height_amount=1)
|
||||
self.left_pile.contents[0] = (self.announce_stream_display, options)
|
||||
self.list_display = 0
|
||||
else:
|
||||
self.known_nodes_display = KnownNodes(self.app)
|
||||
options = self.left_pile.options(height_type="weight", height_amount=1)
|
||||
self.left_pile.contents[0] = (self.known_nodes_display, options)
|
||||
self.list_display = 1
|
||||
|
||||
def start(self):
|
||||
self.local_peer_display.start()
|
||||
self.network_stats_display.start()
|
||||
|
Loading…
Reference in New Issue
Block a user