commit 4740f05a17f9ef34bec549ae49d72b400c8b587b
Author: Damian Johnson <atagar(a)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 ->