[tor-commits] [arm/release] Reimplementing all panel pausing functionality

atagar at torproject.org atagar at torproject.org
Sun Jul 17 06:08:17 UTC 2011


commit 4740f05a17f9ef34bec549ae49d72b400c8b587b
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun May 1 21:06:01 2011 -0700

    Reimplementing all panel pausing functionality
    
    Each panel had a custom implementation for their pausing functionality, using
    an attribute / buffer pattern to juggle their paused vs unpaused states. This
    was confusing, particularly in the graph panel where we needed whole GraphStats
    buffer instances.
    
    This replaces those functions with a far saner implementation in their common
    util parent to make much of this work transparent.
---
 src/cli/connections/connPanel.py   |   32 ++++-----
 src/cli/controller.py              |   11 ++-
 src/cli/graphing/bandwidthStats.py |    5 ++
 src/cli/graphing/connStats.py      |    4 +
 src/cli/graphing/graphPanel.py     |  135 +++++++++++++++---------------------
 src/cli/graphing/resourceStats.py  |    4 +
 src/cli/headerPanel.py             |   28 +++-----
 src/cli/logPanel.py                |   56 +++++----------
 src/util/panel.py                  |  102 +++++++++++++++++++++++++++
 src/util/torTools.py               |    2 +-
 10 files changed, 219 insertions(+), 160 deletions(-)

diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py
index 7e12232..5f4f036 100644
--- a/src/cli/connections/connPanel.py
+++ b/src/cli/connections/connPanel.py
@@ -55,8 +55,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     
     self._lastUpdate = -1       # time the content was last revised
     self._isTorRunning = True   # indicates if tor is currently running or not
-    self._isPaused = True       # prevents updates if true
-    self._pauseTime = None      # time when the panel was paused
+    self._haltTime = None       # time when tor was stopped
     self._halt = False          # terminates thread if true
     self._cond = threading.Condition()  # used for pausing the thread
     self.valsLock = threading.RLock()
@@ -90,27 +89,19 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     
     self._isTorRunning = eventType == torTools.State.INIT
     
-    if self._isPaused or not self._isTorRunning:
-      if not self._pauseTime: self._pauseTime = time.time()
-    else: self._pauseTime = None
+    if self._isTorRunning: self._haltTime = None
+    else: self._haltTime = time.time()
     
     self.redraw(True)
   
-  def setPaused(self, isPause):
+  def getPauseTime(self):
     """
-    If true, prevents the panel from updating.
+    Provides the time Tor stopped if it isn't running. Otherwise this is the
+    time we were last paused.
     """
     
-    if not self._isPaused == isPause:
-      self._isPaused = isPause
-      
-      if isPause or not self._isTorRunning:
-        if not self._pauseTime: self._pauseTime = time.time()
-      else: self._pauseTime = None
-      
-      # redraws so the display reflects any changes between the last update
-      # and being paused
-      self.redraw(True)
+    if self._haltTime: return self._haltTime
+    else: return panel.Panel.getPauseTime(self)
   
   def setSortOrder(self, ordering = None):
     """
@@ -180,7 +171,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     while not self._halt:
       currentTime = time.time()
       
-      if self._isPaused or not self._isTorRunning or currentTime - lastDraw < self._config["features.connection.refreshRate"]:
+      if self.isPaused() or not self._isTorRunning or currentTime - lastDraw < self._config["features.connection.refreshRate"]:
         self._cond.acquire()
         if not self._halt: self._cond.wait(0.2)
         self._cond.release()
@@ -224,7 +215,10 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       scrollOffset = 3
       self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._entryLines), 1 + detailPanelOffset)
     
-    currentTime = self._pauseTime if self._pauseTime else time.time()
+    if self.isPaused() or not self._isTorRunning:
+      currentTime = self.getPauseTime()
+    else: currentTime = time.time()
+    
     for lineNum in range(scrollLoc, len(self._entryLines)):
       entryLine = self._entryLines[lineNum]
       
diff --git a/src/cli/controller.py b/src/cli/controller.py
index 8543ccb..08d7b17 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -45,8 +45,6 @@ PAGES = [
   ["config"],
   ["torrc"]]
 
-PAUSEABLE = ["header", "graph", "log", "conn"]
-
 CONFIG = {"log.torrc.readFailed": log.WARN,
           "features.graph.type": 1,
           "features.config.prepopulateEditValues": True,
@@ -142,6 +140,9 @@ class Popup(panel.Panel):
   def __init__(self, stdscr, height):
     panel.Panel.__init__(self, stdscr, "popup", 0, height)
   
+  def setPaused(self, isPause):
+    panel.Panel.setPaused(self, isPause, True)
+  
   # The following methods are to emulate old panel functionality (this was the
   # only implementations to use these methods and will require a complete
   # rewrite when refactoring gets here)
@@ -217,7 +218,11 @@ def setPauseState(panels, monitorIsPaused, currentPage, overwrite=False):
   reguardless of the monitor is paused or not.
   """
   
-  for key in PAUSEABLE: panels[key].setPaused(overwrite or monitorIsPaused or (key not in PAGES[currentPage] and key not in PAGE_S))
+  allPanels = list(PAGE_S)
+  for pagePanels in PAGES:
+    allPanels += pagePanels
+  
+  for key in allPanels: panels[key].setPaused(overwrite or monitorIsPaused or (key not in PAGES[currentPage] and key not in PAGE_S))
 
 def showMenu(stdscr, popup, title, options, initialSelection):
   """
diff --git a/src/cli/graphing/bandwidthStats.py b/src/cli/graphing/bandwidthStats.py
index 2864dd8..9be52e8 100644
--- a/src/cli/graphing/bandwidthStats.py
+++ b/src/cli/graphing/bandwidthStats.py
@@ -35,6 +35,7 @@ class BandwidthStats(graphPanel.GraphStats):
   def __init__(self, config=None):
     graphPanel.GraphStats.__init__(self)
     
+    self.inputConfig = config
     self._config = dict(DEFAULT_CONFIG)
     if config:
       config.update(self._config, {"features.graph.bw.accounting.rate": 1})
@@ -73,6 +74,10 @@ class BandwidthStats(graphPanel.GraphStats):
     if writeTotal and writeTotal.isdigit():
       self.initialSecondaryTotal = int(writeTotal) / 1024 # Bytes -> KB
   
+  def clone(self, newCopy=None):
+    if not newCopy: newCopy = BandwidthStats(self.inputConfig)
+    return graphPanel.GraphStats.clone(self, newCopy)
+  
   def resetListener(self, conn, eventType):
     # updates title parameters and accounting status if they changed
     self._titleStats = []     # force reset of title
diff --git a/src/cli/graphing/connStats.py b/src/cli/graphing/connStats.py
index 51227b7..7f0dc18 100644
--- a/src/cli/graphing/connStats.py
+++ b/src/cli/graphing/connStats.py
@@ -20,6 +20,10 @@ class ConnStats(graphPanel.GraphStats):
     self.resetListener(conn, torTools.State.INIT) # initialize port values
     conn.addStatusListener(self.resetListener)
   
+  def clone(self, newCopy=None):
+    if not newCopy: newCopy = ConnStats()
+    return graphPanel.GraphStats.clone(self, newCopy)
+  
   def resetListener(self, conn, eventType):
     if eventType == torTools.State.INIT:
       self.orPort = conn.getOption("ORPort", "0")
diff --git a/src/cli/graphing/graphPanel.py b/src/cli/graphing/graphPanel.py
index e4b493d..238e163 100644
--- a/src/cli/graphing/graphPanel.py
+++ b/src/cli/graphing/graphPanel.py
@@ -60,7 +60,7 @@ class GraphStats(TorCtl.PostEventListener):
   time and timescale parameters use the labels defined in UPDATE_INTERVALS.
   """
   
-  def __init__(self, isPauseBuffer=False):
+  def __init__(self):
     """
     Initializes parameters needed to present a graph.
     """
@@ -69,11 +69,7 @@ class GraphStats(TorCtl.PostEventListener):
     
     # panel to be redrawn when updated (set when added to GraphPanel)
     self._graphPanel = None
-    
-    # mirror instance used to track updates when paused
-    self.isPaused, self.isPauseBuffer = False, isPauseBuffer
-    if isPauseBuffer: self._pauseBuffer = None
-    else: self._pauseBuffer = GraphStats(True)
+    self.isSelected = False
     
     # tracked stats
     self.tick = 0                                 # number of processed events
@@ -95,6 +91,26 @@ class GraphStats(TorCtl.PostEventListener):
       self.primaryCounts[i] = (self.maxCol + 1) * [0]
       self.secondaryCounts[i] = (self.maxCol + 1) * [0]
   
+  def clone(self, newCopy=None):
+    """
+    Provides a deep copy of this instance.
+    
+    Arguments:
+      newCopy - base instance to build copy off of
+    """
+    
+    if not newCopy: newCopy = GraphStats()
+    newCopy.tick = self.tick
+    newCopy.lastPrimary = self.lastPrimary
+    newCopy.lastSecondary = self.lastSecondary
+    newCopy.primaryTotal = self.primaryTotal
+    newCopy.secondaryTotal = self.secondaryTotal
+    newCopy.maxPrimary = dict(self.maxPrimary)
+    newCopy.maxSecondary = dict(self.maxSecondary)
+    newCopy.primaryCounts = copy.deepcopy(self.primaryCounts)
+    newCopy.secondaryCounts = copy.deepcopy(self.secondaryCounts)
+    return newCopy
+  
   def eventTick(self):
     """
     Called when it's time to process another event. All graphs use tor BW
@@ -109,7 +125,7 @@ class GraphStats(TorCtl.PostEventListener):
     being redrawn.
     """
     
-    if self._graphPanel and not self.isPauseBuffer and not self.isPaused:
+    if self._graphPanel and self.isSelected and not self._graphPanel.isPaused():
       # use the minimum of the current refresh rate and the panel's
       updateRate = UPDATE_INTERVALS[self._graphPanel.updateInterval][1]
       return (self.tick + 1) % min(updateRate, self.getRefreshRate()) == 0
@@ -165,78 +181,40 @@ class GraphStats(TorCtl.PostEventListener):
     
     pass
   
-  def setPaused(self, isPause):
-    """
-    If true, prevents bandwidth updates from being presented. This is a no-op
-    if a pause buffer.
-    """
-    
-    if isPause == self.isPaused or self.isPauseBuffer: return
-    self.isPaused = isPause
-    
-    if self.isPaused: active, inactive = self._pauseBuffer, self
-    else: active, inactive = self, self._pauseBuffer
-    self._parameterSwap(active, inactive)
-  
   def bandwidth_event(self, event):
     self.eventTick()
   
-  def _parameterSwap(self, active, inactive):
-    """
-    Either overwrites parameters of pauseBuffer or with the current values or
-    vice versa. This is a helper method for setPaused and should be overwritten
-    to append with additional parameters that need to be preserved when paused.
-    """
-    
-    # The pause buffer is constructed as a GraphStats instance which will
-    # become problematic if this is overridden by any implementations (which
-    # currently isn't the case). If this happens then the pause buffer will
-    # need to be of the requester's type (not quite sure how to do this
-    # gracefully...).
-    
-    active.tick = inactive.tick
-    active.lastPrimary = inactive.lastPrimary
-    active.lastSecondary = inactive.lastSecondary
-    active.primaryTotal = inactive.primaryTotal
-    active.secondaryTotal = inactive.secondaryTotal
-    active.maxPrimary = dict(inactive.maxPrimary)
-    active.maxSecondary = dict(inactive.maxSecondary)
-    active.primaryCounts = copy.deepcopy(inactive.primaryCounts)
-    active.secondaryCounts = copy.deepcopy(inactive.secondaryCounts)
-  
   def _processEvent(self, primary, secondary):
     """
     Includes new stats in graphs and notifies associated GraphPanel of changes.
     """
     
-    if self.isPaused: self._pauseBuffer._processEvent(primary, secondary)
-    else:
-      isRedraw = self.isNextTickRedraw()
+    isRedraw = self.isNextTickRedraw()
+    
+    self.lastPrimary, self.lastSecondary = primary, secondary
+    self.primaryTotal += primary
+    self.secondaryTotal += secondary
+    
+    # updates for all time intervals
+    self.tick += 1
+    for i in range(len(UPDATE_INTERVALS)):
+      lable, timescale = UPDATE_INTERVALS[i]
       
-      self.lastPrimary, self.lastSecondary = primary, secondary
-      self.primaryTotal += primary
-      self.secondaryTotal += secondary
+      self.primaryCounts[i][0] += primary
+      self.secondaryCounts[i][0] += secondary
       
-      # updates for all time intervals
-      self.tick += 1
-      for i in range(len(UPDATE_INTERVALS)):
-        lable, timescale = UPDATE_INTERVALS[i]
+      if self.tick % timescale == 0:
+        self.maxPrimary[i] = max(self.maxPrimary[i], self.primaryCounts[i][0] / timescale)
+        self.primaryCounts[i][0] /= timescale
+        self.primaryCounts[i].insert(0, 0)
+        del self.primaryCounts[i][self.maxCol + 1:]
         
-        self.primaryCounts[i][0] += primary
-        self.secondaryCounts[i][0] += secondary
-        
-        if self.tick % timescale == 0:
-          self.maxPrimary[i] = max(self.maxPrimary[i], self.primaryCounts[i][0] / timescale)
-          self.primaryCounts[i][0] /= timescale
-          self.primaryCounts[i].insert(0, 0)
-          del self.primaryCounts[i][self.maxCol + 1:]
-          
-          self.maxSecondary[i] = max(self.maxSecondary[i], self.secondaryCounts[i][0] / timescale)
-          self.secondaryCounts[i][0] /= timescale
-          self.secondaryCounts[i].insert(0, 0)
-          del self.secondaryCounts[i][self.maxCol + 1:]
-      
-      if isRedraw: self._graphPanel.redraw(True)
+        self.maxSecondary[i] = max(self.maxSecondary[i], self.secondaryCounts[i][0] / timescale)
+        self.secondaryCounts[i][0] /= timescale
+        self.secondaryCounts[i].insert(0, 0)
+        del self.secondaryCounts[i][self.maxCol + 1:]
+    
+    if isRedraw: self._graphPanel.redraw(True)
 
 class GraphPanel(panel.Panel):
   """
@@ -252,7 +230,7 @@ class GraphPanel(panel.Panel):
     self.currentDisplay = None    # label of the stats currently being displayed
     self.stats = {}               # available stats (mappings of label -> instance)
     self.showLabel = True         # shows top label if true, hides otherwise
-    self.isPaused = False
+    self.setPauseAttr("stats")
   
   def getHeight(self):
     """
@@ -279,7 +257,7 @@ class GraphPanel(panel.Panel):
     """ Redraws graph panel """
     
     if self.currentDisplay:
-      param = self.stats[self.currentDisplay]
+      param = self.getAttr("stats")[self.currentDisplay]
       graphCol = min((width - 10) / 2, param.maxCol)
       
       primaryColor = uiTools.getColor(param.getColor(True))
@@ -387,21 +365,18 @@ class GraphPanel(panel.Panel):
     """
     
     if label != self.currentDisplay:
-      if self.currentDisplay: self.stats[self.currentDisplay].setPaused(True)
+      if self.currentDisplay: self.stats[self.currentDisplay].isSelected = False
       
       if not label:
         self.currentDisplay = None
       elif label in self.stats.keys():
         self.currentDisplay = label
-        self.stats[label].setPaused(self.isPaused)
+        self.stats[self.currentDisplay].isSelected = True
       else: raise ValueError("Unrecognized stats label: %s" % label)
   
-  def setPaused(self, isPause):
-    """
-    If true, prevents bandwidth updates from being presented.
-    """
-    
-    if isPause == self.isPaused: return
-    self.isPaused = isPause
-    if self.currentDisplay: self.stats[self.currentDisplay].setPaused(self.isPaused)
+  def copyAttr(self, attr):
+    if attr == "stats":
+      # uses custom clone method to copy GraphStats instances
+      return dict([(key, self.stats[key].clone()) for key in self.stats])
+    else: return panel.Panel.copyAttr(self, isPause)
 
diff --git a/src/cli/graphing/resourceStats.py b/src/cli/graphing/resourceStats.py
index a9a8aee..e028874 100644
--- a/src/cli/graphing/resourceStats.py
+++ b/src/cli/graphing/resourceStats.py
@@ -14,6 +14,10 @@ class ResourceStats(graphPanel.GraphStats):
     graphPanel.GraphStats.__init__(self)
     self.queryPid = torTools.getConn().getMyPid()
   
+  def clone(self, newCopy=None):
+    if not newCopy: newCopy = ResourceStats()
+    return graphPanel.GraphStats.clone(self, newCopy)
+  
   def getTitle(self, width):
     return "System Resources:"
   
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index f653299..102ef64 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -61,7 +61,6 @@ class HeaderPanel(panel.Panel, threading.Thread):
     
     self._isTorConnected = True
     self._lastUpdate = -1       # time the content was last revised
-    self._isPaused = False      # prevents updates if true
     self._halt = False          # terminates thread if true
     self._cond = threading.Condition()  # used for pausing the thread
     
@@ -174,9 +173,9 @@ class HeaderPanel(panel.Panel, threading.Thread):
     
     uptimeLabel = ""
     if self.vals["tor/startTime"]:
-      if self._haltTime:
+      if self.isPaused() or not self._isTorConnected:
         # freeze the uptime when paused or the tor process is stopped
-        uptimeLabel = uiTools.getShortTimeLabel(self._haltTime - self.vals["tor/startTime"])
+        uptimeLabel = uiTools.getShortTimeLabel(self.getPauseTime() - self.vals["tor/startTime"])
       else:
         uptimeLabel = uiTools.getShortTimeLabel(time.time() - self.vals["tor/startTime"])
     
@@ -263,21 +262,14 @@ class HeaderPanel(panel.Panel, threading.Thread):
     
     self.valsLock.release()
   
-  def setPaused(self, isPause):
+  def getPauseTime(self):
     """
-    If true, prevents updates from being presented.
+    Provides the time Tor stopped if it isn't running. Otherwise this is the
+    time we were last paused.
     """
     
-    if not self._isPaused == isPause:
-      self._isPaused = isPause
-      if self._isTorConnected:
-        if isPause: self._haltTime = time.time()
-        else: self._haltTime = None
-      
-      # Redraw now so we'll be displaying the state right when paused
-      # (otherwise the uptime might be off by a second, and change when
-      # the panel's redrawn for other reasons).
-      self.redraw(True)
+    if self._haltTime: return self._haltTime
+    else: return panel.Panel.getPauseTime(self)
   
   def run(self):
     """
@@ -288,7 +280,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     while not self._halt:
       currentTime = time.time()
       
-      if self._isPaused or currentTime - lastDraw < 1 or not self._isTorConnected:
+      if self.isPaused() or currentTime - lastDraw < 1 or not self._isTorConnected:
         self._cond.acquire()
         if not self._halt: self._cond.wait(0.2)
         self._cond.release()
@@ -332,9 +324,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     
     if eventType == torTools.State.INIT:
       self._isTorConnected = True
-      if self._isPaused: self._haltTime = time.time()
-      else: self._haltTime = None
-      
+      self._haltTime = None
       self._update(True)
       self.redraw(True)
     elif eventType == torTools.State.CLOSED:
diff --git a/src/cli/logPanel.py b/src/cli/logPanel.py
index 86e680f..7c1c19a 100644
--- a/src/cli/logPanel.py
+++ b/src/cli/logPanel.py
@@ -542,14 +542,13 @@ class LogPanel(panel.Panel, threading.Thread):
     # collapses duplicate log entries if false, showing only the most recent
     self.showDuplicates = self._config["features.log.showDuplicateEntries"]
     
+    self.setPauseAttr("msgLog")         # tracks the message log when we're paused
     self.msgLog = []                    # log entries, sorted by the timestamp
     self.loggedEvents = loggedEvents    # events we're listening to
     self.regexFilter = None             # filter for presented log events (no filtering if None)
     self.lastContentHeight = 0          # height of the rendered content when last drawn
     self.logFile = None                 # file log messages are saved to (skipped if None)
     self.scroll = 0
-    self._isPaused = False
-    self._pauseBuffer = []              # location where messages are buffered if paused
     
     self._lastUpdate = -1               # time the content was last revised
     self._halt = False                  # terminates thread if true
@@ -557,7 +556,7 @@ class LogPanel(panel.Panel, threading.Thread):
     
     # restricts concurrent write access to attributes used to draw the display
     # and pausing:
-    # msgLog, loggedEvents, regexFilter, scroll, _pauseBuffer
+    # msgLog, loggedEvents, regexFilter, scroll
     self.valsLock = threading.RLock()
     
     # cached parameters (invalidated if arguments for them change)
@@ -654,23 +653,17 @@ class LogPanel(panel.Panel, threading.Thread):
         log.log(self._config["log.logPanel.logFileWriteFailed"], "Unable to write to log file: %s" % sysTools.getFileErrorMsg(exc))
         self.logFile = None
     
-    if self._isPaused:
-      self.valsLock.acquire()
-      self._pauseBuffer.insert(0, event)
-      self._trimEvents(self._pauseBuffer)
-      self.valsLock.release()
-    else:
-      self.valsLock.acquire()
-      self.msgLog.insert(0, event)
-      self._trimEvents(self.msgLog)
-      
-      # notifies the display that it has new content
-      if not self.regexFilter or self.regexFilter.search(event.getDisplayMessage()):
-        self._cond.acquire()
-        self._cond.notifyAll()
-        self._cond.release()
-      
-      self.valsLock.release()
+    self.valsLock.acquire()
+    self.msgLog.insert(0, event)
+    self._trimEvents(self.msgLog)
+    
+    # notifies the display that it has new content
+    if not self.regexFilter or self.regexFilter.search(event.getDisplayMessage()):
+      self._cond.acquire()
+      self._cond.notifyAll()
+      self._cond.release()
+    
+    self.valsLock.release()
   
   def _registerArmEvent(self, level, msg, eventTime):
     eventColor = RUNLEVEL_EVENT_COLOR[level]
@@ -763,29 +756,16 @@ class LogPanel(panel.Panel, threading.Thread):
       self.redraw(True)
       self.valsLock.release()
   
-  def setPaused(self, isPause):
-    """
-    If true, prevents message log from being updated with new events.
-    """
-    
-    if isPause == self._isPaused: return
-    
-    self._isPaused = isPause
-    if self._isPaused: self._pauseBuffer = []
-    else:
-      self.valsLock.acquire()
-      self.msgLog = (self._pauseBuffer + self.msgLog)[:self._config["cache.logPanel.size"]]
-      self.redraw(True)
-      self.valsLock.release()
-  
   def draw(self, width, height):
     """
     Redraws message log. Entries stretch to use available space and may
     contain up to two lines. Starts with newest entries.
     """
     
+    currentLog = self.getAttr("msgLog")
+    
     self.valsLock.acquire()
-    self._lastLoggedEvents, self._lastUpdate = list(self.msgLog), time.time()
+    self._lastLoggedEvents, self._lastUpdate = list(currentLog), time.time()
     
     # draws the top label
     self.addstr(0, 0, self._getTitle(width), curses.A_STANDOUT)
@@ -806,7 +786,7 @@ class LogPanel(panel.Panel, threading.Thread):
     dividerAttr, duplicateAttr = curses.A_BOLD | uiTools.getColor("yellow"), curses.A_BOLD | uiTools.getColor("green")
     
     isDatesShown = self.regexFilter == None and self._config["features.log.showDateDividers"]
-    eventLog = getDaybreaks(self.msgLog, self._isPaused) if isDatesShown else list(self.msgLog)
+    eventLog = getDaybreaks(currentLog, self.isPaused()) if isDatesShown else list(currentLog)
     if not self.showDuplicates:
       deduplicatedLog = getDuplicates(eventLog)
       
@@ -950,7 +930,7 @@ class LogPanel(panel.Panel, threading.Thread):
       maxLogUpdateRate = self._config["features.log.maxRefreshRate"] / 1000.0
       
       sleepTime = 0
-      if (self.msgLog == self._lastLoggedEvents and lastDay == currentDay) or self._isPaused:
+      if (self.msgLog == self._lastLoggedEvents and lastDay == currentDay) or self.isPaused():
         sleepTime = 5
       elif timeSinceReset < maxLogUpdateRate:
         sleepTime = max(0.05, maxLogUpdateRate - timeSinceReset)
diff --git a/src/util/panel.py b/src/util/panel.py
index f286622..4f8763c 100644
--- a/src/util/panel.py
+++ b/src/util/panel.py
@@ -3,6 +3,8 @@ Wrapper for safely working with curses subwindows.
 """
 
 import sys
+import copy
+import time
 import traceback
 import curses
 from threading import RLock
@@ -59,6 +61,16 @@ class Panel():
     self.panelName = name
     self.parent = parent
     self.visible = True
+    
+    # Attributes for pausing. The pauseAttr contains variables our getAttr
+    # method is tracking, and the pause buffer has copies of the values from
+    # when we were last unpaused (unused unless we're paused).
+    
+    self.paused = False
+    self.pauseAttr = []
+    self.pauseBuffer = {}
+    self.pauseTime = -1
+    
     self.top = top
     self.height = height
     self.width = width
@@ -117,6 +129,96 @@ class Panel():
     
     self.visible = isVisible
   
+  def isPaused(self):
+    """
+    Provides if the panel's configured to be paused or not.
+    """
+    
+    return self.paused
+  
+  def setPauseAttr(self, attr):
+    """
+    Configures the panel to track the given attribute so that getAttr provides
+    the value when it was last unpaused (or its current value if we're
+    currently unpaused). For instance...
+    
+    > self.setPauseAttr("myVar")
+    > self.myVar = 5
+    > self.myVar = 6 # self.getAttr("myVar") -> 6
+    > self.setPaused(True)
+    > self.myVar = 7 # self.getAttr("myVar") -> 6
+    > self.setPaused(False)
+    > self.myVar = 7 # self.getAttr("myVar") -> 7
+    
+    Arguments:
+      attr - parameter to be tracked for getAttr
+    """
+    
+    self.pauseAttr.append(attr)
+    self.pauseBuffer[attr] = self.copyAttr(attr)
+  
+  def getAttr(self, attr):
+    """
+    Provides the value of the given attribute when we were last unpaused. If
+    we're currently unpaused then this is the current value. If untracked this
+    returns None.
+    
+    Arguments:
+      attr - local variable to be returned
+    """
+    
+    if not attr in self.pauseAttr: return None
+    elif self.isPaused(): return self.pauseBuffer[attr]
+    else: return self.__dict__.get(attr)
+  
+  def copyAttr(self, attr):
+    """
+    Provides a duplicate of the given configuration value, suitable for the
+    pause buffer.
+    
+    Arguments:
+      attr - parameter to be provided back
+    """
+    
+    currentValue = self.__dict__.get(attr)
+    return copy.copy(currentValue)
+  
+  def setPaused(self, isPause, suppressRedraw = False):
+    """
+    Toggles if the panel is paused or not. This causes the panel to be redrawn
+    when toggling is pause state unless told to do otherwise. This is
+    important when pausing since otherwise the panel's display could change
+    when redrawn for other reasons.
+    
+    This returns True if the panel's pause state was changed, False otherwise.
+    
+    Arguments:
+      isPause        - freezes the state of the pause attributes if true, makes
+                       them editable otherwise
+      suppressRedraw - if true then this will never redraw the panel
+    """
+    
+    if isPause != self.paused:
+      if isPause: self.pauseTime = time.time()
+      self.paused = isPause
+      
+      if isPause:
+        # copies tracked attributes so we know what they were before pausing
+        for attr in self.pauseAttr:
+          self.pauseBuffer[attr] = self.copyAttr(attr)
+      
+      if not suppressRedraw: self.redraw(True)
+      return True
+    else: return False
+  
+  def getPauseTime(self):
+    """
+    Provides the time that we were last paused, returning -1 if we've never
+    been paused.
+    """
+    
+    return self.pauseTime
+  
   def getTop(self):
     """
     Provides the position subwindows are placed at within its parent.
diff --git a/src/util/torTools.py b/src/util/torTools.py
index b527f1e..12af772 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -1179,7 +1179,7 @@ class Controller(TorCtl.PostEventListener):
     
     result = None
     if self.isAlive():
-      # determine the nickname if it isn't yet cached
+      # determine the fingerprint if it isn't yet cached
       if not relayNickname in self._nicknameToFpLookupCache:
         # Fingerprints are base64 encoded hex with an extra '='. For instance...
         # GETINFO ns/name/torexp2 ->





More information about the tor-commits mailing list