[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [nyx/master] Rewrite how we handle connection details
commit ed2b57107c30e913c31283e1fbcae38f7ce10f3b
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sun Sep 6 12:09:30 2015 -0700
Rewrite how we handle connection details
When the user presses 'enter' we show a subdialog with more detailed
information. Rewriting how we do this, pulling display logic from
ConnectionLine into a draw submethod.
This also simplifies our connection tracker, merging get_relay_fingerprint()
and get_all_relay_fingerprints().
---
nyx/__init__.py | 1 +
nyx/connection_panel.py | 243 ++++++++++++++++++-----------------------------
nyx/log_panel.py | 10 +-
nyx/util/tracker.py | 41 ++------
4 files changed, 105 insertions(+), 190 deletions(-)
diff --git a/nyx/__init__.py b/nyx/__init__.py
index 65e3c71..078955d 100644
--- a/nyx/__init__.py
+++ b/nyx/__init__.py
@@ -12,6 +12,7 @@ __license__ = 'GPLv3'
__all__ = [
'arguments',
'config_panel',
+ 'connection_panel',
'controller',
'header_panel',
'log_panel',
diff --git a/nyx/connection_panel.py b/nyx/connection_panel.py
index f133ff8..92d4fed 100644
--- a/nyx/connection_panel.py
+++ b/nyx/connection_panel.py
@@ -146,7 +146,7 @@ class ConnectionEntry(Entry):
if self._connection.remote_port == hs_config['HiddenServicePort']:
return Category.HIDDEN
- fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprint(self._connection.remote_address, self._connection.remote_port)
+ fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprints(self._connection.remote_address).get(self._connection.remote_port)
if fingerprint:
for circ in controller.get_circuits([]):
@@ -174,7 +174,7 @@ class ConnectionEntry(Entry):
controller = tor_controller()
if controller.is_user_traffic_allowed().inbound:
- return len(nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(self._connection.remote_address)) == 0
+ return len(nyx.util.tracker.get_consensus_tracker().get_relay_fingerprints(self._connection.remote_address)) == 0
elif self.get_type() == Category.EXIT:
# DNS connections exiting us aren't private (since they're hitting our
# resolvers). Everything else is.
@@ -233,7 +233,7 @@ class ConnectionLine(object):
"""
if self._entry.get_type() in (Category.OUTBOUND, Category.CIRCUIT, Category.DIRECTORY, Category.EXIT):
- my_fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprint(self.connection.remote_address, self.connection.remote_port)
+ my_fingerprint = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprints(self.connection.remote_address).get(self.connection.remote_port)
return my_fingerprint if my_fingerprint else default
else:
return default # inbound connections don't have an ORPort we can resolve
@@ -310,19 +310,6 @@ class ConnectionLine(object):
return draw_entry
- @lru_cache()
- def get_details(self, width):
- """
- Provides details on the connection, correlated against available consensus
- data.
-
- Arguments:
- width - available space to display in
- """
-
- detail_format = (curses.A_BOLD, CONFIG['attr.connection.category_color'].get(self._entry.get_type(), 'white'))
- return [(line, detail_format) for line in self._get_detail_content(width)]
-
def get_etc_content(self, width, listing_type):
"""
Provides the optional content for the connection.
@@ -488,82 +475,6 @@ class ConnectionLine(object):
return LABEL_FORMAT % (src, dst, etc, padding)
- def _get_detail_content(self, width):
- """
- Provides a list with detailed information for this connection.
-
- Arguments:
- width - max length of lines
- """
-
- lines = [''] * 7
- lines[0] = 'address: %s' % self.get_destination_label(width - 11)
- lines[1] = 'locale: %s' % ('??' if self._entry.is_private() else self.get_locale('??'))
-
- # Remaining data concerns the consensus results, with three possible cases:
- # - if there's a single match then display its details
- # - if there's multiple potential relays then list all of the combinations
- # of ORPorts / Fingerprints
- # - if no consensus data is available then say so (probably a client or
- # exit connection)
-
- fingerprint = self.get_fingerprint()
- controller = tor_controller()
-
- if fingerprint:
- lines[1] = '%-13sfingerprint: %s' % (lines[1], fingerprint) # append fingerprint to second line
-
- router_status_entry = controller.get_network_status(fingerprint, None)
- server_descriptor = controller.get_server_descriptor(fingerprint, None)
-
- if router_status_entry:
- dir_port_label = 'dirport: %s' % router_status_entry.dir_port if router_status_entry.dir_port else ''
- lines[2] = 'nickname: %-25s orport: %-10s %s' % (router_status_entry.nickname, router_status_entry.or_port, dir_port_label)
- lines[3] = 'published: %s' % router_status_entry.published.strftime("%H:%M %m/%d/%Y")
- lines[4] = 'flags: %s' % ', '.join(router_status_entry.flags)
-
- if server_descriptor:
- policy_label = server_descriptor.exit_policy.summary() if server_descriptor.exit_policy else 'unknown'
- lines[5] = 'exit policy: %s' % policy_label
- lines[3] = '%-35s os: %-14s version: %s' % (lines[3], server_descriptor.operating_system, server_descriptor.tor_version)
-
- if server_descriptor.contact:
- lines[6] = 'contact: %s' % server_descriptor.contact
- else:
- all_matches = nyx.util.tracker.get_consensus_tracker().get_all_relay_fingerprints(self.connection.remote_address)
-
- if all_matches:
- # multiple matches
- lines[2] = 'Multiple matches, possible fingerprints are:'
-
- for i in range(len(all_matches)):
- is_last_line = i == 3
-
- relay_port, relay_fingerprint = all_matches[i]
- line_text = '%i. or port: %-5s fingerprint: %s' % (i + 1, relay_port, relay_fingerprint)
-
- # if there's multiple lines remaining at the end then give a count
-
- remaining_relays = len(all_matches) - i
-
- if is_last_line and remaining_relays > 1:
- line_text = '... %i more' % remaining_relays
-
- lines[3 + i] = line_text
-
- if is_last_line:
- break
- else:
- # no consensus entry for this ip address
- lines[2] = 'No consensus data found'
-
- # crops any lines that are too long
-
- for i in range(len(lines)):
- lines[i] = str_tools.crop(lines[i], width - 2)
-
- return lines
-
def get_destination_label(self, max_length, include_locale = False):
"""
Provides a short description of the destination. This is made up of two
@@ -665,14 +576,6 @@ class CircHeaderLine(ConnectionLine):
return ''
- @lru_cache()
- def get_details(self, width):
- if not self.is_built:
- detail_format = (curses.A_BOLD, CONFIG['attr.connection.category_color'].get(self._entry.get_type(), 'white'))
- return [('Building Circuit...', detail_format)]
- else:
- return ConnectionLine.get_details(self, width)
-
class CircLine(ConnectionLine):
"""
@@ -791,7 +694,6 @@ class ConnectionPanel(panel.Panel, threading.Thread):
self._scroller = ui_tools.Scroller(True)
self._entries = [] # last fetched display entries
- self._entry_lines = [] # individual lines rendered from the entries listing
self._show_details = False # presents the details panel if true
self._last_update = -1 # time the content was last revised
@@ -915,7 +817,6 @@ class ConnectionPanel(panel.Panel, threading.Thread):
return ''
self._entries.sort(key = lambda i: [sort_value(i, attr) for attr in CONFIG['features.connection.order']])
- self._entry_lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
def get_listing_type(self):
"""
@@ -970,7 +871,8 @@ class ConnectionPanel(panel.Panel, threading.Thread):
if self._show_details:
page_height -= (DETAILS_HEIGHT + 1)
- is_changed = self._scroller.handle_key(key, self._entry_lines, page_height)
+ lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
+ is_changed = self._scroller.handle_key(key, lines, page_height)
if is_changed:
self.redraw(True)
@@ -1018,7 +920,8 @@ class ConnectionPanel(panel.Panel, threading.Thread):
self.redraw(True)
while True:
- selection = self.get_selection()
+ lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
+ selection = self._scroller.get_cursor_selection(lines)
if not selection:
break
@@ -1109,68 +1012,40 @@ class ConnectionPanel(panel.Panel, threading.Thread):
return options
- def get_selection(self):
- """
- Provides the currently selected connection entry.
- """
-
- return self._scroller.get_cursor_selection(self._entry_lines)
-
def draw(self, width, height):
with self._vals_lock:
- # if we don't have any contents then refuse to show details
-
- if not self._entries:
- self._show_details = False
-
- # extra line when showing the detail panel is for the bottom border
-
- detail_panel_offset = DETAILS_HEIGHT + 1 if self._show_details else 0
- is_scrollbar_visible = len(self._entry_lines) > height - detail_panel_offset - 1
-
- scroll_location = self._scroller.get_scroll_location(self._entry_lines, height - detail_panel_offset - 1)
- cursor_selection = self.get_selection()
-
- # draws the detail panel if currently displaying it
+ lines = list(itertools.chain.from_iterable([entry.get_lines() for entry in self._entries]))
+ selected = self._scroller.get_cursor_selection(lines)
- if self._show_details and cursor_selection:
- # This is a solid border unless the scrollbar is visible, in which case a
- # 'T' pipe connects the border to the bar.
-
- ui_tools.draw_box(self, 0, 0, width, DETAILS_HEIGHT + 2)
-
- if is_scrollbar_visible:
- self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
-
- draw_entries = cursor_selection.get_details(width)
-
- for i in range(min(len(draw_entries), DETAILS_HEIGHT)):
- self.addstr(1 + i, 2, draw_entries[i][0], *draw_entries[i][1])
-
- # title label with connection counts
+ details_offset = DETAILS_HEIGHT + 1 if self._show_details else 0
+ is_scrollbar_visible = len(lines) > height - details_offset - 1
+ scroll_location = self._scroller.get_scroll_location(lines, height - details_offset - 1)
if self.is_title_visible():
- self._draw_title(self._entries)
+ self._draw_title(self._entries, self._show_details)
+
+ if self._show_details and selected:
+ self._draw_details(selected, width, is_scrollbar_visible)
scroll_offset = 0
if is_scrollbar_visible:
scroll_offset = 2
- self.add_scroll_bar(scroll_location, scroll_location + height - detail_panel_offset - 1, len(self._entry_lines), 1 + detail_panel_offset)
+ self.add_scroll_bar(scroll_location, scroll_location + height - details_offset - 1, len(lines), 1 + details_offset)
if self.is_paused() or not self._is_tor_running:
current_time = self.get_pause_time()
else:
current_time = time.time()
- for line_number in range(scroll_location, len(self._entry_lines)):
- entry_line = self._entry_lines[line_number]
+ for line_number in range(scroll_location, len(lines)):
+ entry_line = lines[line_number]
# hilighting if this is the selected line
- extra_format = curses.A_STANDOUT if entry_line == cursor_selection else curses.A_NORMAL
+ extra_format = curses.A_STANDOUT if entry_line == selected else curses.A_NORMAL
- draw_line = line_number + detail_panel_offset + 1 - scroll_location
+ draw_line = line_number + details_offset + 1 - scroll_location
prefix = entry_line.get_listing_prefix()
@@ -1188,17 +1063,79 @@ class ConnectionPanel(panel.Panel, threading.Thread):
if draw_line >= height:
break
- def _draw_title(self, entries):
- if self._show_details:
- title = 'Connection Details:'
+ def _draw_title(self, entries, showing_details):
+ """
+ Panel title with the number of connections we presently have.
+ """
+
+ if showing_details:
+ self.addstr(0, 0, 'Connection Details:', curses.A_STANDOUT)
elif not entries:
- title = 'Connections:'
+ self.addstr(0, 0, 'Connections:', curses.A_STANDOUT)
else:
counts = collections.Counter([entry.get_type() for entry in entries])
count_labels = ['%i %s' % (counts[category], category.lower()) for category in Category if counts[category]]
- title = 'Connections (%s):' % ', '.join(count_labels)
+ self.addstr(0, 0, 'Connections (%s):' % ', '.join(count_labels), curses.A_STANDOUT)
+
+ def _draw_details(self, selected, width, is_scrollbar_visible):
+ """
+ Shows detailed information about the selected connection.
+ """
+
+ attr = (CONFIG['attr.connection.category_color'].get(selected._entry.get_type(), 'white'), curses.A_BOLD)
+
+ if isinstance(selected, CircHeaderLine) and not selected.is_built:
+ self.addstr(1, 2, 'Building Circuit...', *attr)
+ else:
+ self.addstr(1, 2, 'address: %s' % selected.get_destination_label(width - 11), *attr)
+ self.addstr(2, 2, 'locale: %s' % ('??' if selected._entry.is_private() else selected.get_locale('??')), *attr)
+
+ matches = nyx.util.tracker.get_consensus_tracker().get_relay_fingerprints(selected.connection.remote_address)
+
+ if not matches:
+ self.addstr(3, 2, 'No consensus data found', *attr)
+ elif len(matches) == 1 or selected.connection.remote_port in matches:
+ controller = tor_controller()
+ fingerprint = matches.values()[0] if len(matches) == 1 else matches[selected.connection.remote_port]
+ router_status_entry = controller.get_network_status(fingerprint, None)
+
+ self.addstr(2, 15, 'fingerprint: %s' % fingerprint, *attr)
+
+ if router_status_entry:
+ dir_port_label = 'dirport: %s' % router_status_entry.dir_port if router_status_entry.dir_port else ''
+ self.addstr(3, 2, 'nickname: %-25s orport: %-10s %s' % (router_status_entry.nickname, router_status_entry.or_port, dir_port_label), *attr)
+ self.addstr(4, 2, 'published: %s' % router_status_entry.published.strftime("%H:%M %m/%d/%Y"), *attr)
+ self.addstr(5, 2, 'flags: %s' % ', '.join(router_status_entry.flags), *attr)
+
+ server_descriptor = controller.get_server_descriptor(fingerprint, None)
+
+ if server_descriptor:
+ policy_label = server_descriptor.exit_policy.summary() if server_descriptor.exit_policy else 'unknown'
+ self.addstr(6, 2, 'exit policy: %s' % policy_label, *attr)
+ self.addstr(4, 38, 'os: %-14s version: %s' % (server_descriptor.operating_system, server_descriptor.tor_version), *attr)
+
+ if server_descriptor.contact:
+ self.addstr(7, 2, 'contact: %s' % server_descriptor.contact, *attr)
+ else:
+ self.addstr(3, 2, 'Multiple matches, possible fingerprints are:', *attr)
+
+ for i, port in enumerate(sorted(matches.keys())):
+ is_last_line, remaining_relays = i == 3, len(matches) - i
+
+ if not is_last_line or remaining_relays == 1:
+ self.addstr(4 + i, 2, '%i. or port: %-5s fingerprint: %s' % (i + 1, port, matches[port]), *attr)
+ else:
+ self.addstr(4 + i, 2, '... %i more' % remaining_relays, *attr)
+
+ if is_last_line:
+ break
+
+ # draw the border, with a 'T' pipe if connecting with the scrollbar
+
+ ui_tools.draw_box(self, 0, 0, width, DETAILS_HEIGHT + 2)
- self.addstr(0, 0, title, curses.A_STANDOUT)
+ if is_scrollbar_visible:
+ self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
def stop(self):
"""
@@ -1248,7 +1185,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
exit_port = entry_line.connection.remote_port
self._exit_port_usage[exit_port] = self._exit_port_usage.get(exit_port, 0) + 1
- self._entries, self._entry_lines = new_entries, list(itertools.chain.from_iterable([entry.get_lines() for entry in new_entries]))
+ self._entries = new_entries
self.set_sort_order()
self._last_resource_fetch = current_resolution_count
diff --git a/nyx/log_panel.py b/nyx/log_panel.py
index 4912cd7..dedfc99 100644
--- a/nyx/log_panel.py
+++ b/nyx/log_panel.py
@@ -281,12 +281,12 @@ class LogPanel(panel.Panel, threading.Thread):
last_content_height = int(self._last_content_height)
show_duplicates = self._show_duplicates
- is_scroll_bar_visible = last_content_height > height - 1
+ is_scrollbar_visible = last_content_height > height - 1
- if is_scroll_bar_visible:
+ if is_scrollbar_visible:
self.add_scroll_bar(scroll, scroll + height - 1, last_content_height, 1)
- x, y = 3 if is_scroll_bar_visible else 1, 1 - scroll
+ x, y = 3 if is_scrollbar_visible else 1, 1 - scroll
# group entries by date, filtering out those that aren't visible
@@ -333,9 +333,9 @@ class LogPanel(panel.Panel, threading.Thread):
force_redraw_reason = 'estimate was off by %i' % content_height_delta
elif new_content_height > height and self._scroll + height - 1 > new_content_height:
force_redraw_reason = 'scrolled off the bottom of the page'
- elif not is_scroll_bar_visible and new_content_height > height - 1:
+ elif not is_scrollbar_visible and new_content_height > height - 1:
force_redraw_reason = "scroll bar wasn't previously visible"
- elif is_scroll_bar_visible and new_content_height <= height - 1:
+ elif is_scrollbar_visible and new_content_height <= height - 1:
force_redraw_reason = "scroll bar shouldn't be visible"
else:
force_redraw = False
diff --git a/nyx/util/tracker.py b/nyx/util/tracker.py
index 88a53c3..237a01c 100644
--- a/nyx/util/tracker.py
+++ b/nyx/util/tracker.py
@@ -31,8 +31,7 @@ Background tasks for gathering information about the tor process.
ConsensusTracker - performant lookups for consensus related information
|- update - updates the consensus information we're based on
|- get_relay_nickname - provides the nickname for a given relay
- |- get_relay_fingerprint - provides the relay running at a location
- |- get_all_relay_fingerprints - provides all relays running at a location
+ |- get_relay_fingerprints - provides relays running at a location
+- get_relay_address - provides the address a relay is running at
.. data:: Resources
@@ -821,47 +820,25 @@ class ConsensusTracker(object):
return self._nickname_cache.get(fingerprint)
- def get_relay_fingerprint(self, address, port = None):
+ def get_relay_fingerprints(self, address):
"""
- Provides the relay running at a given location. If there's multiple relays
- and no port is provided to disambiguate then this returns **None**.
+ Provides the relays running at a given location.
:param str address: address to be checked
- :param int port: optional ORPort to match against
- :returns: **str** with the fingerprint of the relay running there
+ :returns: **dict** of ORPorts to their fingerprint
"""
controller = tor_controller()
if address == controller.get_info('address', None):
- if not port or port in controller.get_ports(stem.control.Listener.OR, []):
- return controller.get_info('fingerprint', None)
+ fingerprint = controller.get_info('fingerprint', None)
+ ports = controller.get_ports(stem.control.Listener.OR, None)
- matches = self._fingerprint_cache.get(address, [])
+ if fingerprint and ports:
+ return dict([(port, fingerprint) for port in ports])
- if len(matches) == 1:
- match_port, match_fingerprint = matches[0]
- return match_fingerprint if (not port or port == match_port) else None
- elif len(matches) > 1 and port:
- # there's multiple matches and we have a port to disambiguate with
-
- for match_port, match_fingerprint in matches:
- if port == match_port:
- return match_fingerprint
-
- return None
-
- def get_all_relay_fingerprints(self, address):
- """
- Provides a [(port, fingerprint)...] tuple of all relays on a given address.
-
- :param str address: address to be checked
-
- :returns: **list** of port/fingerprint tuples running on it
- """
-
- return self._fingerprint_cache.get(address, [])
+ return dict([(port, fp) for (port, fp) in self._fingerprint_cache.get(address, [])])
def get_relay_address(self, fingerprint, default):
"""
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits