[tor-commits] [nyx/master] Use 'with' for log panel locking

atagar at torproject.org atagar at torproject.org
Tue May 5 05:42:06 UTC 2015


commit 68cef741685f139c852f55b9f25d57cad78aa78f
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Apr 15 10:03:31 2015 -0700

    Use 'with' for log panel locking
    
    Safer locking so unexpected exceptions won't cause us to have an unreleased
    lock.
---
 nyx/log_panel.py |  520 ++++++++++++++++++++++++++----------------------------
 1 file changed, 251 insertions(+), 269 deletions(-)

diff --git a/nyx/log_panel.py b/nyx/log_panel.py
index dc63af8..e691b29 100644
--- a/nyx/log_panel.py
+++ b/nyx/log_panel.py
@@ -316,35 +316,32 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     Clears the event log and repopulates it from the nyx and tor backlogs.
     """
 
-    self.vals_lock.acquire()
+    with self.vals_lock:
+      # clears the event log
 
-    # clears the event log
+      self.msg_log = []
 
-    self.msg_log = []
+      # fetches past tor events from log file, if available
 
-    # fetches past tor events from log file, if available
+      if CONFIG['features.log.prepopulate']:
+        set_runlevels = list(set.intersection(set(self.logged_events), set(list(log.Runlevel))))
+        read_limit = CONFIG['features.log.prepopulateReadLimit']
 
-    if CONFIG['features.log.prepopulate']:
-      set_runlevels = list(set.intersection(set(self.logged_events), set(list(log.Runlevel))))
-      read_limit = CONFIG['features.log.prepopulateReadLimit']
+        logging_location = log_file_path()
 
-      logging_location = log_file_path()
-
-      if logging_location:
-        try:
-          for entry in read_tor_log(logging_location, read_limit):
-            if entry.type in set_runlevels:
-              self.msg_log.append(entry)
-        except IOError as exc:
-          log.info('Unable to read log located at %s: %s' % (logging_location, exc))
-        except ValueError as exc:
-          log.info(str(exc))
-
-    # crops events that are either too old, or more numerous than the caching size
+        if logging_location:
+          try:
+            for entry in read_tor_log(logging_location, read_limit):
+              if entry.type in set_runlevels:
+                self.msg_log.append(entry)
+          except IOError as exc:
+            log.info('Unable to read log located at %s: %s' % (logging_location, exc))
+          except ValueError as exc:
+            log.info(str(exc))
 
-    self._trim_events(self.msg_log)
+      # crops events that are either too old, or more numerous than the caching size
 
-    self.vals_lock.release()
+      self._trim_events(self.msg_log)
 
   def set_duplicate_visability(self, is_visible):
     """
@@ -394,18 +391,16 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
         log.error('Unable to write to log file: %s' % exc.strerror)
         self.log_file = None
 
-    self.vals_lock.acquire()
-    self.msg_log.insert(0, event)
-    self._trim_events(self.msg_log)
-
-    # notifies the display that it has new content
+    with self.vals_lock:
+      self.msg_log.insert(0, event)
+      self._trim_events(self.msg_log)
 
-    if not self.regex_filter or self.regex_filter.search(event.display_message):
-      self._cond.acquire()
-      self._cond.notifyAll()
-      self._cond.release()
+      # notifies the display that it has new content
 
-    self.vals_lock.release()
+      if not self.regex_filter or self.regex_filter.search(event.display_message):
+        self._cond.acquire()
+        self._cond.notifyAll()
+        self._cond.release()
 
   def set_logged_events(self, event_types):
     """
@@ -418,15 +413,13 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     if event_types == self.logged_events:
       return
 
-    self.vals_lock.acquire()
-
-    # configures the controller to listen for these tor events, and provides
-    # back a subset without anything we're failing to listen to
+    with self.vals_lock:
+      # configures the controller to listen for these tor events, and provides
+      # back a subset without anything we're failing to listen to
 
-    set_types = self.set_event_listening(event_types)
-    self.logged_events = set_types
-    self.redraw(True)
-    self.vals_lock.release()
+      set_types = self.set_event_listening(event_types)
+      self.logged_events = set_types
+      self.redraw(True)
 
   def get_filter(self):
     """
@@ -447,10 +440,9 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     if log_filter == self.regex_filter:
       return
 
-    self.vals_lock.acquire()
-    self.regex_filter = log_filter
-    self.redraw(True)
-    self.vals_lock.release()
+    with self.vals_lock:
+      self.regex_filter = log_filter
+      self.redraw(True)
 
   def make_filter_selection(self, selected_option):
     """
@@ -549,10 +541,9 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     Clears the contents of the event log.
     """
 
-    self.vals_lock.acquire()
-    self.msg_log = []
-    self.redraw(True)
-    self.vals_lock.release()
+    with self.vals_lock:
+      self.msg_log = []
+      self.redraw(True)
 
   def save_snapshot(self, path):
     """
@@ -577,19 +568,16 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       raise IOError("unable to make directory '%s'" % base_dir)
 
     snapshot_file = open(path, 'w')
-    self.vals_lock.acquire()
-
-    try:
-      for entry in self.msg_log:
-        is_visible = not self.regex_filter or self.regex_filter.search(entry.display_message)
 
-        if is_visible:
-          snapshot_file.write(entry.display_message + '\n')
+    with self.vals_lock:
+      try:
+        for entry in self.msg_log:
+          is_visible = not self.regex_filter or self.regex_filter.search(entry.display_message)
 
-      self.vals_lock.release()
-    except Exception as exc:
-      self.vals_lock.release()
-      raise exc
+          if is_visible:
+            snapshot_file.write(entry.display_message + '\n')
+      except Exception as exc:
+        raise exc
 
   def handle_key(self, key):
     if key.is_scroll():
@@ -597,15 +585,13 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       new_scroll = ui_tools.get_scroll_position(key, self.scroll, page_height, self.last_content_height)
 
       if self.scroll != new_scroll:
-        self.vals_lock.acquire()
-        self.scroll = new_scroll
-        self.redraw(True)
-        self.vals_lock.release()
+        with self.vals_lock:
+          self.scroll = new_scroll
+          self.redraw(True)
     elif key.match('u'):
-      self.vals_lock.acquire()
-      self.set_duplicate_visability(not CONFIG['features.log.showDuplicateEntries'])
-      self.redraw(True)
-      self.vals_lock.release()
+      with self.vals_lock:
+        self.set_duplicate_visability(not CONFIG['features.log.showDuplicateEntries'])
+        self.redraw(True)
     elif key.match('c'):
       msg = 'This will clear the log. Are you sure (c again to confirm)?'
       key_press = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
@@ -669,173 +655,171 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
     current_log = self.get_attr('msg_log')
 
-    self.vals_lock.acquire()
-    self._last_logged_events, self._last_update = list(current_log), time.time()
+    with self.vals_lock:
+      self._last_logged_events, self._last_update = list(current_log), time.time()
 
-    # draws the top label
+      # draws the top label
 
-    if self.is_title_visible():
-      self.addstr(0, 0, self._get_title(width), curses.A_STANDOUT)
+      if self.is_title_visible():
+        self.addstr(0, 0, self._get_title(width), curses.A_STANDOUT)
 
-    # restricts scroll location to valid bounds
+      # restricts scroll location to valid bounds
 
-    self.scroll = max(0, min(self.scroll, self.last_content_height - height + 1))
+      self.scroll = max(0, min(self.scroll, self.last_content_height - height + 1))
 
-    # draws left-hand scroll bar if content's longer than the height
+      # draws left-hand scroll bar if content's longer than the height
 
-    msg_indent, divider_indent = 1, 0  # offsets for scroll bar
-    is_scroll_bar_visible = self.last_content_height > height - 1
+      msg_indent, divider_indent = 1, 0  # offsets for scroll bar
+      is_scroll_bar_visible = self.last_content_height > height - 1
 
-    if is_scroll_bar_visible:
-      msg_indent, divider_indent = 3, 2
-      self.add_scroll_bar(self.scroll, self.scroll + height - 1, self.last_content_height, 1)
+      if is_scroll_bar_visible:
+        msg_indent, divider_indent = 3, 2
+        self.add_scroll_bar(self.scroll, self.scroll + height - 1, self.last_content_height, 1)
 
-    # draws log entries
+      # draws log entries
 
-    line_count = 1 - self.scroll
-    seen_first_date_divider = False
-    divider_attr, duplicate_attr = (curses.A_BOLD, 'yellow'), (curses.A_BOLD, 'green')
+      line_count = 1 - self.scroll
+      seen_first_date_divider = False
+      divider_attr, duplicate_attr = (curses.A_BOLD, 'yellow'), (curses.A_BOLD, 'green')
 
-    is_dates_shown = self.regex_filter is None and CONFIG['features.log.showDateDividers']
-    event_log = get_daybreaks(current_log, self.is_paused()) if is_dates_shown else list(current_log)
+      is_dates_shown = self.regex_filter is None and CONFIG['features.log.showDateDividers']
+      event_log = get_daybreaks(current_log, self.is_paused()) if is_dates_shown else list(current_log)
 
-    if not CONFIG['features.log.showDuplicateEntries']:
-      deduplicated_log = get_duplicates(event_log)
+      if not CONFIG['features.log.showDuplicateEntries']:
+        deduplicated_log = get_duplicates(event_log)
 
-      if deduplicated_log is None:
-        log.warn('Deduplication took too long. Its current implementation has difficulty handling large logs so disabling it to keep the interface responsive.')
-        self.set_duplicate_visability(True)
+        if deduplicated_log is None:
+          log.warn('Deduplication took too long. Its current implementation has difficulty handling large logs so disabling it to keep the interface responsive.')
+          self.set_duplicate_visability(True)
+          deduplicated_log = [(entry, 0) for entry in event_log]
+      else:
         deduplicated_log = [(entry, 0) for entry in event_log]
-    else:
-      deduplicated_log = [(entry, 0) for entry in event_log]
 
-    # determines if we have the minimum width to show date dividers
+      # determines if we have the minimum width to show date dividers
 
-    show_daybreaks = width - divider_indent >= 3
+      show_daybreaks = width - divider_indent >= 3
 
-    while deduplicated_log:
-      entry, duplicate_count = deduplicated_log.pop(0)
+      while deduplicated_log:
+        entry, duplicate_count = deduplicated_log.pop(0)
 
-      if self.regex_filter and not self.regex_filter.search(entry.display_message):
-        continue  # filter doesn't match log message - skip
+        if self.regex_filter and not self.regex_filter.search(entry.display_message):
+          continue  # filter doesn't match log message - skip
 
-      # checks if we should be showing a divider with the date
+        # checks if we should be showing a divider with the date
 
-      if entry.type == DAYBREAK_EVENT:
-        # bottom of the divider
+        if entry.type == DAYBREAK_EVENT:
+          # bottom of the divider
 
-        if seen_first_date_divider:
-          if line_count >= 1 and line_count < height and show_daybreaks:
-            self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
-            self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
-            self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
+          if seen_first_date_divider:
+            if line_count >= 1 and line_count < height and show_daybreaks:
+              self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
+              self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
+              self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
 
-          line_count += 1
+            line_count += 1
 
-        # top of the divider
+          # top of the divider
 
-        if line_count >= 1 and line_count < height and show_daybreaks:
-          time_label = time.strftime(' %B %d, %Y ', time.localtime(entry.timestamp))
-          self.addch(line_count, divider_indent, curses.ACS_ULCORNER, *divider_attr)
-          self.addch(line_count, divider_indent + 1, curses.ACS_HLINE, *divider_attr)
-          self.addstr(line_count, divider_indent + 2, time_label, curses.A_BOLD, *divider_attr)
-
-          line_length = width - divider_indent - len(time_label) - 3
-          self.hline(line_count, divider_indent + len(time_label) + 2, line_length, *divider_attr)
-          self.addch(line_count, divider_indent + len(time_label) + 2 + line_length, curses.ACS_URCORNER, *divider_attr)
-
-        seen_first_date_divider = True
-        line_count += 1
-      else:
-        # entry contents to be displayed, tuples of the form:
-        # (msg, formatting, includeLinebreak)
-
-        display_queue = []
+          if line_count >= 1 and line_count < height and show_daybreaks:
+            time_label = time.strftime(' %B %d, %Y ', time.localtime(entry.timestamp))
+            self.addch(line_count, divider_indent, curses.ACS_ULCORNER, *divider_attr)
+            self.addch(line_count, divider_indent + 1, curses.ACS_HLINE, *divider_attr)
+            self.addstr(line_count, divider_indent + 2, time_label, curses.A_BOLD, *divider_attr)
 
-        msg_comp = entry.display_message.split('\n')
+            line_length = width - divider_indent - len(time_label) - 3
+            self.hline(line_count, divider_indent + len(time_label) + 2, line_length, *divider_attr)
+            self.addch(line_count, divider_indent + len(time_label) + 2 + line_length, curses.ACS_URCORNER, *divider_attr)
 
-        for i in range(len(msg_comp)):
-          font = curses.A_BOLD if 'ERR' in entry.type else curses.A_NORMAL  # emphasizes ERR messages
-          display_queue.append((msg_comp[i].strip(), (font, CONFIG['attr.log_color'].get(entry.type, 'white')), i != len(msg_comp) - 1))
+          seen_first_date_divider = True
+          line_count += 1
+        else:
+          # entry contents to be displayed, tuples of the form:
+          # (msg, formatting, includeLinebreak)
 
-        if duplicate_count:
-          plural_label = 's' if duplicate_count > 1 else ''
-          duplicate_msg = DUPLICATE_MSG % (duplicate_count, plural_label)
-          display_queue.append((duplicate_msg, duplicate_attr, False))
+          display_queue = []
 
-        cursor_location, line_offset = msg_indent, 0
-        max_entries_per_line = CONFIG['features.log.max_lines_per_entry']
+          msg_comp = entry.display_message.split('\n')
 
-        while display_queue:
-          msg, format, include_break = display_queue.pop(0)
-          draw_line = line_count + line_offset
+          for i in range(len(msg_comp)):
+            font = curses.A_BOLD if 'ERR' in entry.type else curses.A_NORMAL  # emphasizes ERR messages
+            display_queue.append((msg_comp[i].strip(), (font, CONFIG['attr.log_color'].get(entry.type, 'white')), i != len(msg_comp) - 1))
 
-          if line_offset == max_entries_per_line:
-            break
+          if duplicate_count:
+            plural_label = 's' if duplicate_count > 1 else ''
+            duplicate_msg = DUPLICATE_MSG % (duplicate_count, plural_label)
+            display_queue.append((duplicate_msg, duplicate_attr, False))
 
-          max_msg_size = width - cursor_location - 1
+          cursor_location, line_offset = msg_indent, 0
+          max_entries_per_line = CONFIG['features.log.max_lines_per_entry']
 
-          if len(msg) > max_msg_size:
-            # message is too long - break it up
-            if line_offset == max_entries_per_line - 1:
-              msg = str_tools.crop(msg, max_msg_size)
-            else:
-              msg, remainder = str_tools.crop(msg, max_msg_size, 4, 4, str_tools.Ending.HYPHEN, True)
-              display_queue.insert(0, (remainder.strip(), format, include_break))
+          while display_queue:
+            msg, format, include_break = display_queue.pop(0)
+            draw_line = line_count + line_offset
 
-            include_break = True
+            if line_offset == max_entries_per_line:
+              break
 
-          if draw_line < height and draw_line >= 1:
-            if seen_first_date_divider and width - divider_indent >= 3 and show_daybreaks:
-              self.addch(draw_line, divider_indent, curses.ACS_VLINE, *divider_attr)
-              self.addch(draw_line, width - 1, curses.ACS_VLINE, *divider_attr)
+            max_msg_size = width - cursor_location - 1
 
-            self.addstr(draw_line, cursor_location, msg, *format)
+            if len(msg) > max_msg_size:
+              # message is too long - break it up
+              if line_offset == max_entries_per_line - 1:
+                msg = str_tools.crop(msg, max_msg_size)
+              else:
+                msg, remainder = str_tools.crop(msg, max_msg_size, 4, 4, str_tools.Ending.HYPHEN, True)
+                display_queue.insert(0, (remainder.strip(), format, include_break))
 
-          cursor_location += len(msg)
+              include_break = True
 
-          if include_break or not display_queue:
-            line_offset += 1
-            cursor_location = msg_indent + ENTRY_INDENT
+            if draw_line < height and draw_line >= 1:
+              if seen_first_date_divider and width - divider_indent >= 3 and show_daybreaks:
+                self.addch(draw_line, divider_indent, curses.ACS_VLINE, *divider_attr)
+                self.addch(draw_line, width - 1, curses.ACS_VLINE, *divider_attr)
 
-        line_count += line_offset
+              self.addstr(draw_line, cursor_location, msg, *format)
 
-      # if this is the last line and there's room, then draw the bottom of the divider
+            cursor_location += len(msg)
 
-      if not deduplicated_log and seen_first_date_divider:
-        if line_count < height and show_daybreaks:
-          self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
-          self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
-          self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
+            if include_break or not display_queue:
+              line_offset += 1
+              cursor_location = msg_indent + ENTRY_INDENT
 
-        line_count += 1
+          line_count += line_offset
 
-    # redraw the display if...
-    # - last_content_height was off by too much
-    # - we're off the bottom of the page
+        # if this is the last line and there's room, then draw the bottom of the divider
 
-    new_content_height = line_count + self.scroll - 1
-    content_height_delta = abs(self.last_content_height - new_content_height)
-    force_redraw, force_redraw_reason = True, ''
+        if not deduplicated_log and seen_first_date_divider:
+          if line_count < height and show_daybreaks:
+            self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
+            self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
+            self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
 
-    if content_height_delta >= CONTENT_HEIGHT_REDRAW_THRESHOLD:
-      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:
-      force_redraw_reason = "scroll bar wasn't previously visible"
-    elif is_scroll_bar_visible and new_content_height <= height - 1:
-      force_redraw_reason = "scroll bar shouldn't be visible"
-    else:
-      force_redraw = False
+          line_count += 1
 
-    self.last_content_height = new_content_height
+      # redraw the display if...
+      # - last_content_height was off by too much
+      # - we're off the bottom of the page
+
+      new_content_height = line_count + self.scroll - 1
+      content_height_delta = abs(self.last_content_height - new_content_height)
+      force_redraw, force_redraw_reason = True, ''
+
+      if content_height_delta >= CONTENT_HEIGHT_REDRAW_THRESHOLD:
+        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:
+        force_redraw_reason = "scroll bar wasn't previously visible"
+      elif is_scroll_bar_visible and new_content_height <= height - 1:
+        force_redraw_reason = "scroll bar shouldn't be visible"
+      else:
+        force_redraw = False
 
-    if force_redraw:
-      log.debug('redrawing the log panel with the corrected content height (%s)' % force_redraw_reason)
-      self.redraw(True)
+      self.last_content_height = new_content_height
 
-    self.vals_lock.release()
+      if force_redraw:
+        log.debug('redrawing the log panel with the corrected content height (%s)' % force_redraw_reason)
+        self.redraw(True)
 
   def redraw(self, force_redraw=False, block=False):
     # determines if the content needs to be redrawn or not
@@ -957,110 +941,108 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     # usually the attributes used to make the label are decently static, so
     # provide cached results if they're unchanged
 
-    self.vals_lock.acquire()
-    current_pattern = self.regex_filter.pattern if self.regex_filter else None
-    is_unchanged = self._title_args[0] == self.logged_events
-    is_unchanged &= self._title_args[1] == current_pattern
-    is_unchanged &= self._title_args[2] == width
+    with self.vals_lock:
+      current_pattern = self.regex_filter.pattern if self.regex_filter else None
+      is_unchanged = self._title_args[0] == self.logged_events
+      is_unchanged &= self._title_args[1] == current_pattern
+      is_unchanged &= self._title_args[2] == width
 
-    if is_unchanged:
-      self.vals_lock.release()
-      return self._title_cache
+      if is_unchanged:
+        return self._title_cache
 
-    events_list = list(self.logged_events)
+      events_list = list(self.logged_events)
 
-    if not events_list:
-      if not current_pattern:
-        panel_label = 'Events:'
+      if not events_list:
+        if not current_pattern:
+          panel_label = 'Events:'
+        else:
+          label_pattern = str_tools.crop(current_pattern, width - 18)
+          panel_label = 'Events (filter: %s):' % label_pattern
       else:
-        label_pattern = str_tools.crop(current_pattern, width - 18)
-        panel_label = 'Events (filter: %s):' % label_pattern
-    else:
-      # does the following with all runlevel types (tor, nyx, and stem):
-      # - pulls to the start of the list
-      # - condenses range if there's three or more in a row (ex. "NYX_INFO - WARN")
-      # - condense further if there's identical runlevel ranges for multiple
-      #   types (ex. "NOTICE - ERR, NYX_NOTICE - ERR" becomes "TOR/NYX NOTICE - ERR")
-
-      tmp_runlevels = []  # runlevels pulled from the list (just the runlevel part)
-      runlevel_ranges = []  # tuple of type, start_level, end_level for ranges to be consensed
-
-      # reverses runlevels and types so they're appended in the right order
-
-      reversed_runlevels = list(log.Runlevel)
-      reversed_runlevels.reverse()
-
-      for prefix in ('NYX_', ''):
-        # blank ending runlevel forces the break condition to be reached at the end
-        for runlevel in reversed_runlevels + ['']:
-          event_type = prefix + runlevel
-          if runlevel and event_type in events_list:
-            # runlevel event found, move to the tmp list
-            events_list.remove(event_type)
-            tmp_runlevels.append(runlevel)
-          elif tmp_runlevels:
-            # adds all tmp list entries to the start of events_list
-            if len(tmp_runlevels) >= 3:
-              # save condense sequential runlevels to be added later
-              runlevel_ranges.append((prefix, tmp_runlevels[-1], tmp_runlevels[0]))
-            else:
-              # adds runlevels individaully
-              for tmp_runlevel in tmp_runlevels:
-                events_list.insert(0, prefix + tmp_runlevel)
+        # does the following with all runlevel types (tor, nyx, and stem):
+        # - pulls to the start of the list
+        # - condenses range if there's three or more in a row (ex. "NYX_INFO - WARN")
+        # - condense further if there's identical runlevel ranges for multiple
+        #   types (ex. "NOTICE - ERR, NYX_NOTICE - ERR" becomes "TOR/NYX NOTICE - ERR")
+
+        tmp_runlevels = []  # runlevels pulled from the list (just the runlevel part)
+        runlevel_ranges = []  # tuple of type, start_level, end_level for ranges to be consensed
+
+        # reverses runlevels and types so they're appended in the right order
+
+        reversed_runlevels = list(log.Runlevel)
+        reversed_runlevels.reverse()
+
+        for prefix in ('NYX_', ''):
+          # blank ending runlevel forces the break condition to be reached at the end
+          for runlevel in reversed_runlevels + ['']:
+            event_type = prefix + runlevel
+            if runlevel and event_type in events_list:
+              # runlevel event found, move to the tmp list
+              events_list.remove(event_type)
+              tmp_runlevels.append(runlevel)
+            elif tmp_runlevels:
+              # adds all tmp list entries to the start of events_list
+              if len(tmp_runlevels) >= 3:
+                # save condense sequential runlevels to be added later
+                runlevel_ranges.append((prefix, tmp_runlevels[-1], tmp_runlevels[0]))
+              else:
+                # adds runlevels individaully
+                for tmp_runlevel in tmp_runlevels:
+                  events_list.insert(0, prefix + tmp_runlevel)
 
-            tmp_runlevels = []
+              tmp_runlevels = []
 
-      # adds runlevel ranges, condensing if there's identical ranges
+        # adds runlevel ranges, condensing if there's identical ranges
 
-      for i in range(len(runlevel_ranges)):
-        if runlevel_ranges[i]:
-          prefix, start_level, end_level = runlevel_ranges[i]
+        for i in range(len(runlevel_ranges)):
+          if runlevel_ranges[i]:
+            prefix, start_level, end_level = runlevel_ranges[i]
 
-          # check for matching ranges
+            # check for matching ranges
 
-          matches = []
+            matches = []
 
-          for j in range(i + 1, len(runlevel_ranges)):
-            if runlevel_ranges[j] and runlevel_ranges[j][1] == start_level and runlevel_ranges[j][2] == end_level:
-              matches.append(runlevel_ranges[j])
-              runlevel_ranges[j] = None
+            for j in range(i + 1, len(runlevel_ranges)):
+              if runlevel_ranges[j] and runlevel_ranges[j][1] == start_level and runlevel_ranges[j][2] == end_level:
+                matches.append(runlevel_ranges[j])
+                runlevel_ranges[j] = None
 
-          if matches:
-            # strips underscores and replaces empty entries with "TOR"
+            if matches:
+              # strips underscores and replaces empty entries with "TOR"
 
-            prefixes = [entry[0] for entry in matches] + [prefix]
+              prefixes = [entry[0] for entry in matches] + [prefix]
 
-            for k in range(len(prefixes)):
-              if prefixes[k] == '':
-                prefixes[k] = 'TOR'
-              else:
-                prefixes[k] = prefixes[k].replace('_', '')
+              for k in range(len(prefixes)):
+                if prefixes[k] == '':
+                  prefixes[k] = 'TOR'
+                else:
+                  prefixes[k] = prefixes[k].replace('_', '')
 
-            events_list.insert(0, '%s %s - %s' % ('/'.join(prefixes), start_level, end_level))
-          else:
-            events_list.insert(0, '%s%s - %s' % (prefix, start_level, end_level))
+              events_list.insert(0, '%s %s - %s' % ('/'.join(prefixes), start_level, end_level))
+            else:
+              events_list.insert(0, '%s%s - %s' % (prefix, start_level, end_level))
 
-      # truncates to use an ellipsis if too long, for instance:
+        # truncates to use an ellipsis if too long, for instance:
 
-      attr_label = ', '.join(events_list)
+        attr_label = ', '.join(events_list)
 
-      if current_pattern:
-        attr_label += ' - filter: %s' % current_pattern
+        if current_pattern:
+          attr_label += ' - filter: %s' % current_pattern
 
-      attr_label = str_tools.crop(attr_label, width - 10, 1)
+        attr_label = str_tools.crop(attr_label, width - 10, 1)
 
-      if attr_label:
-        attr_label = ' (%s)' % attr_label
+        if attr_label:
+          attr_label = ' (%s)' % attr_label
 
-      panel_label = 'Events%s:' % attr_label
+        panel_label = 'Events%s:' % attr_label
 
-    # cache results and return
+      # cache results and return
 
-    self._title_cache = panel_label
-    self._title_args = (list(self.logged_events), current_pattern, width)
-    self.vals_lock.release()
+      self._title_cache = panel_label
+      self._title_args = (list(self.logged_events), current_pattern, width)
 
-    return panel_label
+      return panel_label
 
   def _trim_events(self, event_listing):
     """





More information about the tor-commits mailing list