Improved browser functionality.

This commit is contained in:
Mark Qvist 2021-08-28 20:12:03 +02:00
parent 6e4baf3731
commit 538bee4526
5 changed files with 104 additions and 22 deletions

View File

@ -20,7 +20,7 @@ THEMES = {
# Style name # 16-color style # Monochrome style # 88, 256 and true-color style # Style name # 16-color style # Monochrome style # 88, 256 and true-color style
('heading', 'light gray,underline', 'default', 'underline', 'g93,underline', 'default'), ('heading', 'light gray,underline', 'default', 'underline', 'g93,underline', 'default'),
('menubar', 'black', 'light gray', 'standout', '#111', '#bbb'), ('menubar', 'black', 'light gray', 'standout', '#111', '#bbb'),
('scrollbar', 'black', 'light gray', 'standout', '#444', 'default'), ('scrollbar', 'light gray', 'default', 'standout', '#444', 'default'),
('shortcutbar', 'black', 'light gray', 'standout', '#111', '#bbb'), ('shortcutbar', 'black', 'light gray', 'standout', '#111', '#bbb'),
('body_text', 'white', 'default', 'default', '#ddd', 'default'), ('body_text', 'white', 'default', 'default', '#ddd', 'default'),
('error_text', 'dark red', 'default', 'default', 'dark red', 'default'), ('error_text', 'dark red', 'default', 'default', 'dark red', 'default'),
@ -38,10 +38,13 @@ THEMES = {
("list_off_focus", "black", "dark gray", "standout", "#111", "dark gray"), ("list_off_focus", "black", "dark gray", "standout", "#111", "dark gray"),
("list_trusted", "light green", "default", "default", "#6b2", "default"), ("list_trusted", "light green", "default", "default", "#6b2", "default"),
("list_focus_trusted", "black", "light gray", "standout", "#180", "#bbb"), ("list_focus_trusted", "black", "light gray", "standout", "#180", "#bbb"),
("list_unknown", "dark gray", "default", "default", "light gray", "default"), ("list_unknown", "dark gray", "default", "default", "#bbb", "default"),
("list_normal", "dark gray", "default", "default", "light gray", "default"), ("list_normal", "dark gray", "default", "default", "#bbb", "default"),
("list_untrusted", "dark red", "default", "default", "dark red", "default"), ("list_untrusted", "dark red", "default", "default", "dark red", "default"),
("list_focus_untrusted", "black", "light gray", "standout", "#810", "#bbb"), ("list_focus_untrusted", "black", "light gray", "standout", "#810", "#bbb"),
("topic_list_normal", "white", "default", "default", "#ddd", "default"),
("progress_full", "black", "light gray", "standout", "#111", "#bbb"),
("progress_empty", "light gray", "default", "default", "#ddd", "default"),
], ],
} }
} }
@ -68,6 +71,8 @@ GLYPHS = {
("divider1", "-", "\u2504", "\u2504"), ("divider1", "-", "\u2504", "\u2504"),
("peer", "P", "\U0001F464", "\uf415"), ("peer", "P", "\U0001F464", "\uf415"),
("node", "N", "\U0001F5A5", "\uf502"), ("node", "N", "\U0001F5A5", "\uf502"),
("page", "", "\U0001F5B9", "\uf719 "),
("speed", "", "\U0001F5B9", "\uf9c4"),
("decoration_menu", "", "", " \uf93a"), ("decoration_menu", "", "", " \uf93a"),
} }

View File

@ -53,6 +53,9 @@ class Browser:
self.link = None self.link = None
self.status = Browser.DISCONECTED self.status = Browser.DISCONECTED
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.page_data = None self.page_data = None
self.displayed_page_data = None self.displayed_page_data = None
self.auth_identity = auth_identity self.auth_identity = auth_identity
@ -78,7 +81,7 @@ class Browser:
return RNS.hexrep(self.destination_hash, delimit=False)+":"+path return RNS.hexrep(self.destination_hash, delimit=False)+":"+path
def handle_link(self, link_target): def handle_link(self, link_target):
RNS.log("Browser handling link to: "+str(link_target)) RNS.log("Browser handling link to: "+str(link_target), RNS.LOG_DEBUG)
try: try:
self.retrieve_url(link_target) self.retrieve_url(link_target)
except Exception as e: except Exception as e:
@ -101,7 +104,13 @@ class Browser:
self.display_widget = urwid.AttrMap(urwid.LineBox(self.frame, title="Remote Node"), "inactive_text") self.display_widget = urwid.AttrMap(urwid.LineBox(self.frame, title="Remote Node"), "inactive_text")
def make_status_widget(self): def make_status_widget(self):
return urwid.Pile([urwid.Divider(self.g["divider1"]), urwid.Text(self.status_text())]) if self.response_progress > 0:
pb = ResponseProgressBar("progress_empty" , "progress_full", current=self.response_progress, done=1.0, satt=None)
widget = urwid.Pile([urwid.Divider(self.g["divider1"]), pb])
else:
widget = urwid.Pile([urwid.Divider(self.g["divider1"]), urwid.Text(self.status_text())])
return widget
def make_control_widget(self): def make_control_widget(self):
return urwid.Pile([urwid.Text(self.g["node"]+" "+self.current_url()), urwid.Divider(self.g["divider1"])]) return urwid.Pile([urwid.Text(self.g["node"]+" "+self.current_url()), urwid.Divider(self.g["divider1"])])
@ -119,9 +128,14 @@ class Browser:
("weight", 0.5, urwid.Text(" ")) ("weight", 0.5, urwid.Text(" "))
]) ])
pile = urwid.Pile([ if len(self.attr_maps) > 0:
urwid.Text("!\n\n"+self.status_text()+"\n", align="center"), pile = urwid.Pile([
columns urwid.Text("!\n\n"+self.status_text()+"\n", align="center"),
columns
])
else:
pile = urwid.Pile([
urwid.Text("!\n\n"+self.status_text(), align="center")
]) ])
return urwid.Filler(pile, "middle") return urwid.Filler(pile, "middle")
@ -139,16 +153,21 @@ class Browser:
if self.status == Browser.DONE: if self.status == Browser.DONE:
self.browser_footer = self.make_status_widget() self.browser_footer = self.make_status_widget()
self.update_page_display() self.update_page_display()
elif self.status <= Browser.REQUEST_SENT: elif self.status <= Browser.REQUEST_SENT:
if len(self.attr_maps) == 0: if len(self.attr_maps) == 0:
self.browser_body = urwid.Filler(urwid.Text("Retrieving\n["+self.current_url()+"]", align="center"), "middle") self.browser_body = urwid.Filler(urwid.Text("Retrieving\n["+self.current_url()+"]", align="center"), "middle")
self.browser_footer = self.make_status_widget() self.browser_footer = self.make_status_widget()
elif self.status == Browser.REQUEST_FAILED: elif self.status == Browser.REQUEST_FAILED:
self.browser_body = self.make_request_failed_widget() self.browser_body = self.make_request_failed_widget()
self.browser_footer = urwid.Text("") self.browser_footer = urwid.Text("")
elif self.status == Browser.REQUEST_TIMEOUT: elif self.status == Browser.REQUEST_TIMEOUT:
self.browser_body = self.make_request_failed_widget() self.browser_body = self.make_request_failed_widget()
self.browser_footer = urwid.Text("") self.browser_footer = urwid.Text("")
else: else:
pass pass
@ -172,6 +191,10 @@ class Browser:
self.attr_maps = [] self.attr_maps = []
self.status = Browser.DISCONECTED self.status = Browser.DISCONECTED
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.update_display() self.update_display()
@ -264,6 +287,7 @@ class Browser:
now = time.time() now = time.time()
if now > pr_time+self.timeout: if now > pr_time+self.timeout:
self.request_timeout() self.request_timeout()
return
time.sleep(0.25) time.sleep(0.25)
@ -286,6 +310,7 @@ class Browser:
now = time.time() now = time.time()
if now > l_time+self.timeout: if now > l_time+self.timeout:
self.request_timeout() self.request_timeout()
return
time.sleep(0.25) time.sleep(0.25)
@ -293,12 +318,17 @@ class Browser:
# Send the request # Send the request
self.status = Browser.REQUESTING self.status = Browser.REQUESTING
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.update_display() self.update_display()
receipt = self.link.request( receipt = self.link.request(
self.path, self.path,
data = None, data = None,
response_callback = self.response_received, response_callback = self.response_received,
failed_callback = self.request_failed, failed_callback = self.request_failed,
progress_callback = self.response_progressed,
timeout = self.timeout timeout = self.timeout
) )
@ -328,6 +358,8 @@ class Browser:
self.page_data = request_receipt.response self.page_data = request_receipt.response
self.markup = self.page_data.decode("utf-8") self.markup = self.page_data.decode("utf-8")
self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self) self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self)
self.response_progress = 0
self.update_display() self.update_display()
except Exception as e: except Exception as e:
RNS.log("An error occurred while handling response. The contained exception was: "+str(e)) RNS.log("An error occurred while handling response. The contained exception was: "+str(e))
@ -337,18 +369,46 @@ class Browser:
if request_receipt != None: if request_receipt != None:
if request_receipt.request_id == self.last_request_id: if request_receipt.request_id == self.last_request_id:
self.status = Browser.REQUEST_FAILED self.status = Browser.REQUEST_FAILED
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.update_display() self.update_display()
else: else:
self.status = Browser.REQUEST_FAILED self.status = Browser.REQUEST_FAILED
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.update_display() self.update_display()
def request_timeout(self, request_receipt=None): def request_timeout(self, request_receipt=None):
self.status = Browser.REQUEST_TIMEOUT self.status = Browser.REQUEST_TIMEOUT
self.response_progress = 0
self.response_size = None
self.response_transfer_size = None
self.update_display()
def response_progressed(self, request_receipt):
self.response_progress = request_receipt.progress
self.response_time = request_receipt.response_time()
self.response_size = request_receipt.response_size
self.response_transfer_size = request_receipt.response_transfer_size
self.update_display() self.update_display()
def status_text(self): def status_text(self):
if self.response_transfer_size != None:
response_time_str = "{:.2f}".format(self.response_time)
stats_string = " "+self.g["page"]+size_str(self.response_size)
stats_string += " "+self.g["arrow_d"]+size_str(self.response_transfer_size)+" in "+response_time_str
stats_string += "s "+self.g["speed"]+size_str(self.response_transfer_size/self.response_time, suffix="b")+"/s"
else:
stats_string = ""
if self.status == Browser.NO_PATH: if self.status == Browser.NO_PATH:
return "No path to destination known" return "No path to destination known"
elif self.status == Browser.PATH_REQUESTED: elif self.status == Browser.PATH_REQUESTED:
@ -368,10 +428,30 @@ class Browser:
elif self.status == Browser.RECEIVING_RESPONSE: elif self.status == Browser.RECEIVING_RESPONSE:
return "Receiving response..." return "Receiving response..."
elif self.status == Browser.DONE: elif self.status == Browser.DONE:
return "Done" return "Done"+stats_string
elif self.status == Browser.DISCONECTED: elif self.status == Browser.DISCONECTED:
return "Disconnected" return "Disconnected"
else: else:
return "Browser Status Unknown" return "Browser Status Unknown"
class ResponseProgressBar(urwid.ProgressBar):
def get_text(self):
return "Receiving response "+super().get_text()
# A convenience function for printing a human-
# readable file size
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1000.0:
return "%3.2f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.2f %s%s" % (num, last_unit, suffix)

View File

@ -70,7 +70,7 @@ class GuideEntry(urwid.WidgetWrap):
widget = ListEntry(topic_name) widget = ListEntry(topic_name)
urwid.connect_signal(widget, "click", self.display_topic, topic_name) urwid.connect_signal(widget, "click", self.display_topic, topic_name)
style = "list_normal" style = "topic_list_normal"
focus_style = "list_focus" focus_style = "list_focus"
self.display_widget = urwid.AttrMap(widget, style, focus_style) self.display_widget = urwid.AttrMap(widget, style, focus_style)
urwid.WidgetWrap.__init__(self, self.display_widget) urwid.WidgetWrap.__init__(self, self.display_widget)
@ -155,12 +155,6 @@ TOPIC_INTRODUCTION = '''>Nomad Network
`c`*Communicate Freely.`* `c`*Communicate Freely.`*
`a `a
TODO: REMOVE
This is a `F07flink `[With a label`344858860838a8d9f8ed:/page/test] to some resource`f.
This is a link `*`[With a label`:/page/test]`* to some resource.
This is a link `[With a label`:] to some resource.
This is a link `*`[With a label`344858860838a8d9f8ed] to some`* resource.
The intention with this program is to provide a tool to that allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them. The intention with this program is to provide a tool to that allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them.
Nomad Network is build on LXMF and Reticulum, which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to gigabit fiber. Nomad Network is build on LXMF and Reticulum, which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to gigabit fiber.
@ -417,9 +411,13 @@ TOPIC_MARKUP += "\n`=\n\n>Closing Remarks\n\nIf you made it all the way here, yo
TOPIC_FIRST_RUN = '''>First Time Information TOPIC_FIRST_RUN = '''>First Time Information
Hi there. This first run message will only appear once. It contains a few pointers on getting started with Nomad Network, and getting the most out of the program. You're currently located in the guide section of the program. I'm sorry I had to drag you here by force, but it will only happen this one time, I promise. If you ever get lost, return here and peruse the list of topics you see on the left. I will do my best to fill it with answers to mostly anything about Nomad Network. Hi there. This first run message will only appear once. It contains a few pointers on getting started with Nomad Network, and getting the most out of the program.
To get the most out of Nomad Network, you will need a terminal that supports UTF-8 and at least 256 colors, ideally true-color. By default, Nomad Network starts in low-color mode. It does this for the sake of compatibility, but it does look rather ugly. If your terminal supports true-color or just 256 colors, you should go to the `![ Config ]`! menu item, launch the editor and change the configuration to use a high-color mode. You're currently located in the guide section of the program. I'm sorry I had to drag you here by force, but it will only happen this one time, I promise. If you ever get lost, return here and peruse the list of topics you see on the left. I will do my best to fill it with answers to mostly anything about Nomad Network.
To get the most out of Nomad Network, you will need a terminal that supports UTF-8 and at least 256 colors, ideally true-color.
By default, Nomad Network starts in low-color mode. It does this for the sake of compatibility, but it does look rather ugly. If your terminal supports true-color or just 256 colors, you should go to the `![ Config ]`! menu item, launch the editor and change the configuration to use a high-color mode.
If you don't already have a Nerd Font installed (see https://www.nerdfonts.com/), I also highly recommend to do so, since it will greatly expand the amount of glyphs, icons and graphics that Nomad Network can use. If you don't already have a Nerd Font installed (see https://www.nerdfonts.com/), I also highly recommend to do so, since it will greatly expand the amount of glyphs, icons and graphics that Nomad Network can use.

View File

@ -291,7 +291,6 @@ def make_output(state, line, url_delegate):
specname = make_style(state) specname = make_style(state)
speclist = SYNTH_SPECS[specname] speclist = SYNTH_SPECS[specname]
orig_spec = urwid.AttrSpec('underline', 'default', cm)
if cm == 1: if cm == 1:
orig_spec = speclist[0] orig_spec = speclist[0]
elif cm == 16: elif cm == 16:

View File

@ -23,6 +23,6 @@ setuptools.setup(
entry_points= { entry_points= {
'console_scripts': ['nomadnet=nomadnet.nomadnet:main'] 'console_scripts': ['nomadnet=nomadnet.nomadnet:main']
}, },
install_requires=['rns>=0.2.2', 'lxmf>=0.0.6', 'urwid>=2.1.2'], install_requires=['rns>=0.2.3', 'lxmf>=0.0.7', 'urwid>=2.1.2'],
python_requires='>=3.5', python_requires='>=3.6',
) )