commit 30f9ad4e138848cc012f45842a972d2065612eb7
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Apr 27 08:32:40 2011 -0700
fix: Minor refactoring for moved/removed resources
Missed a couple spots where the connPanel and /src/interface were referenced.
---
README | 17 ++++++++---------
src/cli/__init__.py | 2 +-
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/README b/README
index 7beb7e3..50657a1 100644
--- a/README
+++ b/…
[View More]README
@@ -139,14 +139,7 @@ Layout:
torConfigDesc.txt - fallback descriptions of Tor's configuration options
uninstall - removal script
- interface/
- connections/
- __init__.py
- connPanel.py - (page 2) lists the active tor connections
- circEntry.py - circuit entries in the connection panel
- connEntry.py - individual connections to or from the system
- entries.py - common parent for connPanel display entries
-
+ cli/
graphing/
__init__.py
graphPanel.py - (page 1) presents graphs for data instances
@@ -154,13 +147,19 @@ Layout:
psStats.py - tracks system information (such as cpu/memory usage)
connStats.py - tracks number of tor connections
+ connections/
+ __init__.py
+ connPanel.py - (page 2) lists the active tor connections
+ circEntry.py - circuit entries in the connection panel
+ connEntry.py - individual connections to or from the system
+ entries.py - common parent for connPanel display entries
+
__init__.py
controller.py - main display loop, handling input and layout
headerPanel.py - top of all pages, providing general information
descriptorPopup.py - (popup) displays connection descriptor data
logPanel.py - (page 1) displays tor, arm, and torctl events
- connPanel.py - (page 2) deprecated counterpart for connections/*
configPanel.py - (page 3) editor panel for the tor configuration
torrcPanel.py - (page 4) displays torrc and validation
diff --git a/src/cli/__init__.py b/src/cli/__init__.py
index 0f11fc1..171af09 100644
--- a/src/cli/__init__.py
+++ b/src/cli/__init__.py
@@ -2,5 +2,5 @@
Panels, popups, and handlers comprising the arm user interface.
"""
-__all__ = ["configPanel", "connPanel", "controller", "descriptorPopup", "headerPanel", "logPanel", "torrcPanel"]
+__all__ = ["configPanel", "controller", "descriptorPopup", "headerPanel", "logPanel", "torrcPanel"]
[View Less]
commit f75b0c9f47ea4932cbdfea888cab55af7397c5a6
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Apr 27 09:41:40 2011 -0700
Delaying connection resolution until after init
This is a huge performance fix, dropping the startup time from 0.84 seconds to
0.14 (83% improvement). Arm had been fetching connection contents on init,
blocking the first redraw. The fix is to both move connection init into its
runtime thread *and* delay its execution until after …
[View More]the initial redraw. This
hides the call latency in the period that we're usually waiting for user input
anyway.
There's still perceived latency if the user immediately switches to the
connections page, but this is both unavoidable and unusual.
---
src/cli/connections/connPanel.py | 13 ++++++++++---
1 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py
index 8339f88..7e12232 100644
--- a/src/cli/connections/connPanel.py
+++ b/src/cli/connections/connPanel.py
@@ -71,9 +71,6 @@ class ConnectionPanel(panel.Panel, threading.Thread):
# rate limits appResolver queries to once per update
self.appResolveSinceUpdate = False
- self._update() # populates initial entries
- self._resolveApps(False) # resolves initial applications
-
# mark the initially exitsing connection uptimes as being estimates
for entry in self._entries:
if isinstance(entry, connEntry.ConnectionEntry):
@@ -170,6 +167,16 @@ class ConnectionPanel(panel.Panel, threading.Thread):
"""
lastDraw = time.time() - 1
+
+ # Fetches out initial connection results. The wait is so this doesn't
+ # run during arm's interface initialization (otherwise there's a
+ # noticeable pause before the first redraw).
+ self._cond.acquire()
+ self._cond.wait(0.2)
+ self._cond.release()
+ self._update() # populates initial entries
+ self._resolveApps(False) # resolves initial applications
+
while not self._halt:
currentTime = time.time()
[View Less]
commit c4454e05284d36fb6ecafc9c2f33c7b0a9ffab43
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Apr 27 21:46:21 2011 -0700
fix: Stacktrace from unjoined thread at shutdown
There's a rare (but repeated) stack trace due to an unjoined thread at
shutdown:
Exception in thread Thread-3 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
File "/usr/lib/python2.6/threading.py", line 525, in __bootstrap_inner
File …
[View More]"/home/atagar/Desktop/arm/src/util/sysTools.py", line 542, in run
File "/usr/lib/python2.6/threading.py", line 135, in release
<type 'exceptions.TypeError'>: 'NoneType' object is not callable
This isn't harmful, but it is annoying. This change is attempting to be extra
careful that all resourceTracker threads have been joined, and that the
controller is stopped beforehand (preventing stray BW events from triggering
additional queries).
The stacktrace _seems_ to be more commonly caused by long-running arm
processes, but I don't have a reliable repro case.
---
src/cli/controller.py | 13 ++++++-------
src/cli/graphing/resourceStats.py | 4 ++--
src/util/sysTools.py | 21 ++++-----------------
3 files changed, 12 insertions(+), 26 deletions(-)
diff --git a/src/cli/controller.py b/src/cli/controller.py
index 22d1772..8543ccb 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -825,20 +825,19 @@ def drawTorMonitor(stdscr, startTime, loggedEvents, isBlindMode):
panels["conn"].join()
panels["log"].join()
- # joins on utility daemon threads - this might take a moment since
- # the internal threadpools being joined might be sleeping
conn = torTools.getConn()
- myPid = conn.getMyPid()
+ conn.close() # joins on TorCtl event thread
- resourceTracker = sysTools.getResourceTracker(myPid) if (myPid and sysTools.isTrackerAlive(myPid)) else None
+ # joins on utility daemon threads - this might take a moment since
+ # the internal threadpools being joined might be sleeping
+ resourceTrackers = sysTools.RESOURCE_TRACKERS.values()
resolver = connections.getResolver("tor") if connections.isResolverAlive("tor") else None
- if resourceTracker: resourceTracker.stop()
+ for tracker in resourceTrackers: tracker.stop()
if resolver: resolver.stop() # sets halt flag (returning immediately)
hostnames.stop() # halts and joins on hostname worker thread pool
- if resourceTracker: resourceTracker.join()
+ for tracker in resourceTrackers: tracker.join()
if resolver: resolver.join() # joins on halted resolver
- conn.close() # joins on TorCtl event thread
break
elif key == curses.KEY_LEFT or key == curses.KEY_RIGHT:
# switch page
diff --git a/src/cli/graphing/resourceStats.py b/src/cli/graphing/resourceStats.py
index f26d5c1..a9a8aee 100644
--- a/src/cli/graphing/resourceStats.py
+++ b/src/cli/graphing/resourceStats.py
@@ -36,9 +36,9 @@ class ResourceStats(graphPanel.GraphStats):
primary, secondary = 0, 0
if self.queryPid:
- resourceTracker = sysTools.getResourceTracker(self.queryPid)
+ resourceTracker = sysTools.getResourceTracker(self.queryPid, True)
- if not resourceTracker.lastQueryFailed():
+ if resourceTracker and not resourceTracker.lastQueryFailed():
primary, _, secondary, _ = resourceTracker.getResourceUsage()
primary *= 100 # decimal percentage to whole numbers
secondary /= 1048576 # translate size to MB so axis labels are short
diff --git a/src/util/sysTools.py b/src/util/sysTools.py
index 8d95733..2775194 100644
--- a/src/util/sysTools.py
+++ b/src/util/sysTools.py
@@ -347,27 +347,13 @@ def call(command, cacheAge=0, suppressExc=False, quiet=True):
return results
-def isTrackerAlive(pid):
- """
- Provides true if a running, singleton instance exists for the given pid,
- false otherwise.
-
- Arguments:
- pid - pid of the process being tracked
- """
-
- if pid in RESOURCE_TRACKERS:
- if RESOURCE_TRACKERS[pid].isAlive(): return True
- else: del RESOURCE_TRACKERS[pid]
-
- return False
-
-def getResourceTracker(pid):
+def getResourceTracker(pid, noSpawn = False):
"""
Provides a running singleton ResourceTracker instance for the given pid.
Arguments:
- pid - pid of the process being tracked
+ pid - pid of the process being tracked
+ noSpawn - returns None rather than generating a singleton instance if True
"""
if pid in RESOURCE_TRACKERS:
@@ -375,6 +361,7 @@ def getResourceTracker(pid):
if tracker.isAlive(): return tracker
else: del RESOURCE_TRACKERS[pid]
+ if noSpawn: return None
tracker = ResourceTracker(pid, CONFIG["queries.resourceUsage.rate"])
RESOURCE_TRACKERS[pid] = tracker
tracker.start()
[View Less]
commit 1c907f900b09043c15b2fbdf0d87cdd67f111c18
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Apr 27 21:20:54 2011 -0700
fix: Missed setup.py when renaming interface->cli
The setup.py still referenced interface, causing arm to fail to install.
---
setup.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/setup.py b/setup.py
index 9777da1..cb0069f 100644
--- a/setup.py
+++ b/setup.py
@@ -75,7 +75,7 @@ if "install" in sys.argv:
# …
[View More]When installing we include a bundled copy of TorCtl. However, when creating
# a deb we have a dependency on the python-torctl package instead:
# http://packages.debian.org/unstable/main/python-torctl
-installPackages = ['arm', 'arm.interface', 'arm.interface.graphing', 'arm.interface.connections', 'arm.util']
+installPackages = ['arm', 'arm.cli', 'arm.cli.graphing', 'arm.cli.connections', 'arm.util']
if not isDebInstall: installPackages.append('arm.TorCtl')
setup(name='arm',
[View Less]
commit 03aa67dcb5589e626306e89ef3dc92eb9c300de8
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Apr 27 21:13:05 2011 -0700
fix: Misparsing circut paths for Tor < 0.2.2.1
For Tor versions prior to 0.2.2.1 our circuit paths could contain nickname-only
entries. These were being misparsed (expecting them to always start with
fingerprints), resulting in screwed up circuit entries in the connection panel.
Caught by asn.
---
src/util/torTools.py | 67 +…
[View More]+++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 65 insertions(+), 2 deletions(-)
diff --git a/src/util/torTools.py b/src/util/torTools.py
index 5d8ffd8..b527f1e 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -330,6 +330,7 @@ class Controller(TorCtl.PostEventListener):
self._fingerprintLookupCache = {} # lookup cache with (ip, port) -> fingerprint mappings
self._fingerprintsAttachedCache = None # cache of relays we're connected to
self._nicknameLookupCache = {} # lookup cache with fingerprint -> nickname mappings
+ self._nicknameToFpLookupCache = {} # lookup cache with nickname -> fingerprint mappings
self._consensusLookupCache = {} # lookup cache with network status entries
self._descriptorLookupCache = {} # lookup cache with relay descriptors
self._isReset = False # internal flag for tracking resets
@@ -389,6 +390,7 @@ class Controller(TorCtl.PostEventListener):
self._fingerprintLookupCache = {}
self._fingerprintsAttachedCache = None
self._nicknameLookupCache = {}
+ self._nicknameToFpLookupCache = {}
self._consensusLookupCache = {}
self._descriptorLookupCache = {}
@@ -1164,6 +1166,44 @@ class Controller(TorCtl.PostEventListener):
return result
+ def getNicknameFingerprint(self, relayNickname):
+ """
+ Provides the fingerprint associated with the given relay. This provides
+ None if no such relay exists.
+
+ Arguments:
+ relayNickname - nickname of the relay
+ """
+
+ self.connLock.acquire()
+
+ result = None
+ if self.isAlive():
+ # determine the nickname 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 ->
+ # r torexp2 NPfjt8Vjr+drcbbFLQONN3KapNo LxoHteGax7ZNYh/9g/FF8I617fY 2011-04-27 15:20:35 141.161.20.50 9001 0
+ # decode base64 of "NPfjt8Vjr+drcbbFLQONN3KapNo=" ->
+ # "4\xf7\xe3\xb7\xc5c\xaf\xe7kq\xb6\xc5-\x03\x8d7r\x9a\xa4\xda"
+ # encode hex of the above ->
+ # "34f7e3b7c563afe76b71b6c52d038d37729aa4da"
+
+ relayFingerprint = None
+ consensusEntry = self.getInfo("ns/name/%s" % relayNickname)
+ if consensusEntry:
+ encodedFp = consensusEntry.split()[2]
+ decodedFp = (encodedFp + "=").decode('base64').encode('hex')
+ relayFingerprint = decodedFp.upper()
+
+ self._nicknameToFpLookupCache[relayNickname] = relayFingerprint
+
+ result = self._nicknameToFpLookupCache[relayNickname]
+
+ self.connLock.release()
+
+ return result
+
def addEventListener(self, listener):
"""
Directs further tor controller events to callback functions of the
@@ -1433,6 +1473,7 @@ class Controller(TorCtl.PostEventListener):
self._fingerprintLookupCache = {}
self._fingerprintsAttachedCache = None
self._nicknameLookupCache = {}
+ self._nicknameToFpLookupCache = {}
self._consensusLookupCache = {}
if self._fingerprintMappings != None:
@@ -1870,6 +1911,12 @@ class Controller(TorCtl.PostEventListener):
# 91 BUILT $E4AE6E2FE320FBBD31924E8577F3289D4BE0B4AD=Qwerty PURPOSE=GENERAL
# would belong to a single hop circuit, most likely fetching the
# consensus via a directory mirror.
+ #
+ # The path is made up of "$<fingerprint>[=<nickname]" entries for new
+ # versions of Tor, but in versions prior to 0.2.2.1-alpha this was
+ # just "$<fingerprint>" OR <nickname>. The dolar sign can't be used in
+ # nicknames so this can be used to differentiate.
+
circStatusResults = self.getInfo("circuit-status")
if circStatusResults == "":
@@ -1885,8 +1932,24 @@ class Controller(TorCtl.PostEventListener):
# 5 LAUNCHED PURPOSE=TESTING
if len(lineComp) < 4: continue
- path = tuple([hopEntry[1:41] for hopEntry in lineComp[2].split(",")])
- result.append((int(lineComp[0]), lineComp[1], lineComp[3][8:], path))
+ path = []
+ for hopEntry in lineComp[2].split(","):
+ if hopEntry[0] == "$": path.append(hopEntry[1:41])
+ else:
+ relayFingerprint = self.getNicknameFingerprint(hopEntry)
+
+ # It shouldn't be possible for this lookup to fail, but we
+ # need to fill something (callers won't expect our own client
+ # paths to have unknown relays). If this turns out to be wrong
+ # then log a warning.
+
+ if relayFingerprint: path.append(relayFingerprint)
+ else:
+ msg = "Unable to determine the fingerprint for a relay in our own circuit: %s" % hopEntry
+ log.log(log.WARN, msg)
+ path.append("0" * 40)
+
+ result.append((int(lineComp[0]), lineComp[1], lineComp[3][8:], tuple(path)))
elif key == "hsPorts":
result = []
hsOptions = self.getOptionMap("HiddenServiceOptions")
[View Less]
commit 8ec467eb5caeb4972e12a988a805d93f511bd192
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon May 2 19:59:39 2011 -0700
Renaming our process to "arm <input args>"
Our process was called "python ./src/starter.py <input args>" which made it a
pita to identify, use killall on, etc. Using some ctypes hacks to overwrite it
with an intuitive name.
---
src/starter.py | 8 +++
src/util/procname.py | 122 +++++++++++++++++++++++++++++++++++…
[View More]+++++++++++++++
2 files changed, 130 insertions(+), 0 deletions(-)
diff --git a/src/starter.py b/src/starter.py
index c0a0270..365676c 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -387,5 +387,13 @@ if __name__ == '__main__':
util.log.log(CONFIG["log.savingDebugLog"], "Saving a debug log to '%s' (please check it for sensitive information before sharing)" % LOG_DUMP_PATH)
_dumpConfig()
+ # Attempts to rename our process from "python setup.py <input args>" to
+ # "arm <input args>"
+
+ try:
+ from util import procname
+ procname.renameProcess("arm %s" % " ".join(sys.argv[1:]))
+ except: pass
+
cli.controller.startTorMonitor(time.time() - initTime, expandedEvents, param["startup.blindModeEnabled"])
diff --git a/src/util/procname.py b/src/util/procname.py
new file mode 100644
index 0000000..7641c5a
--- /dev/null
+++ b/src/util/procname.py
@@ -0,0 +1,122 @@
+# Module to allow for arbitrary renaming of our python process. This is mostly
+# based on:
+# http://www.rhinocerus.net/forum/lang-python/569677-setting-program-name-lik…
+# and an adaptation by Jake: https://github.com/ioerror/chameleon
+#
+# A cleaner implementation is available at:
+# https://github.com/cream/libs/blob/b38970e2a6f6d2620724c828808235be0445b799…
+# but I'm not quite clear on their implementation, and it only does targeted
+# argument replacement (ie, replace argv[0], argv[1], etc but with a string
+# the same size).
+
+import os
+import sys
+import ctypes
+import ctypes.util
+
+from util import sysTools
+
+# flag for setting the process name, found in '/usr/include/linux/prctl.h'
+PR_SET_NAME = 15
+
+# Maximum number of characters we'll set the process name to. Evidently this
+# cap was simply chosen since it didn't cause a segfault for its author.
+MAX_CHAR = 1608
+
+argc_t = ctypes.POINTER(ctypes.c_char_p)
+
+Py_GetArgcArgv = ctypes.pythonapi.Py_GetArgcArgv
+Py_GetArgcArgv.restype = None
+Py_GetArgcArgv.argtypes = [ctypes.POINTER(ctypes.c_int),
+ ctypes.POINTER(argc_t)]
+
+# tracks the last name we've changed the process to
+currentProcessName = None
+
+def renameProcess(processName):
+ """
+ Renames our current process from "python <args>" to a custom name.
+
+ Arguments:
+ processName - new name for our process
+ """
+
+ _setArgv(processName)
+ if sys.platform == "linux2":
+ _setPrctlName(processName)
+ elif sys.platform == "freebsd7":
+ _setProcTitle(processName)
+
+def _setArgv(processName):
+ """
+ Overwrites our argv in a similar fashion to how it's done in C with:
+ strcpy(argv[0], "new_name");
+ """
+
+ global currentProcessName
+
+ argv = ctypes.c_int(0)
+ argc = argc_t()
+ Py_GetArgcArgv(argv, ctypes.pointer(argc))
+
+ # The original author did the memset for 256, while Jake did it for the
+ # processName length (capped at 1608). I'm not sure of the reasons for
+ # either of these limits, but setting it to anything higher than than the
+ # length of the null terminated process name should be pointless, so opting
+ # for Jake's implementation on this.
+
+ if currentProcessName == None:
+ # Using argv via...
+ # currentProcessName = " ".join(["python"] + sys.argv)
+ #
+ # doesn't do the trick since this will miss interpretor arguments like...
+ # python -W ignore::DeprecationWarning myScript.py
+ #
+ # hence simply getting an outside opinion of our command.
+
+ psResults = sysTools.call("ps -p %i -o args" % os.getpid())
+
+ if len(psResults) == 2:
+ # output looks like:
+ # COMMAND
+ # python ./src/starter.py
+
+ currentProcessName = psResults[1]
+
+ if not currentProcessName:
+ raise IOError("unable to determine our process name")
+
+ # we need to fill extra space with null characters, otherwise the process
+ # will end with a '?' when you run ps against it
+ if len(currentProcessName) > len(processName):
+ processName += "\0" * (len(currentProcessName) - len(processName))
+
+ size = min(len(processName), MAX_CHAR)
+ ctypes.memset(argc.contents, 0, size + 1) # null terminate the string's end
+ ctypes.memmove(argc.contents, processName, size)
+ currentProcessName = processName[:MAX_CHAR]
+
+def _setPrctlName(processName):
+ """
+ Sets the prctl name, which is used by top and killall. This appears to be
+ Linux specific and has the max of 15 characters. Source:
+ http://stackoverflow.com/questions/564695/is-there-a-way-to-change-effectiv…
+ """
+
+ libc = ctypes.CDLL(ctypes.util.find_library("c"))
+ nameBuffer = ctypes.create_string_buffer(len(processName)+1)
+ nameBuffer.value = processName
+ libc.prctl(PR_SET_NAME, ctypes.byref(nameBuffer), 0, 0, 0)
+
+def _setProcTitle(processName):
+ """
+ BSD specific calls (should be compataible with both FreeBSD and OpenBSD:
+ http://fxr.watson.org/fxr/source/gen/setproctitle.c?v=FREEBSD-LIBC
+ http://www.rootr.net/man/man/setproctitle/3
+ """
+
+ libc = ctypes.CDLL(ctypes.util.find_library("c"))
+ nameBuffer = ctypes.create_string_buffer(len(processName)+1)
+ nameBuffer.value = processName
+ libc.setproctitle(ctypes.byref(nameBuffer))
+
[View Less]
commit eb78be9beeee01dcf137e60559f5ac26c3fe2c13
Author: Damian Johnson <atagar(a)torproject.org>
Date: Mon May 2 21:04:17 2011 -0700
fix: Making the process name setter safer/better
fixes include:
- just using memset rather than maually filling with null chars
- using \0 arg dividers
- erroring out rather than clobber env (ie, refuse to use a process name longer than the original argv)
---
src/starter.py | 2 +-
src/util/procname.py | 25 ++++++++++++…
[View More]-------------
2 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/src/starter.py b/src/starter.py
index 365676c..5b73a73 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -392,7 +392,7 @@ if __name__ == '__main__':
try:
from util import procname
- procname.renameProcess("arm %s" % " ".join(sys.argv[1:]))
+ procname.renameProcess("arm\0%s" % "\0".join(sys.argv[1:]))
except: pass
cli.controller.startTorMonitor(time.time() - initTime, expandedEvents, param["startup.blindModeEnabled"])
diff --git a/src/util/procname.py b/src/util/procname.py
index 7641c5a..14a82b8 100644
--- a/src/util/procname.py
+++ b/src/util/procname.py
@@ -19,10 +19,6 @@ from util import sysTools
# flag for setting the process name, found in '/usr/include/linux/prctl.h'
PR_SET_NAME = 15
-# Maximum number of characters we'll set the process name to. Evidently this
-# cap was simply chosen since it didn't cause a segfault for its author.
-MAX_CHAR = 1608
-
argc_t = ctypes.POINTER(ctypes.c_char_p)
Py_GetArgcArgv = ctypes.pythonapi.Py_GetArgcArgv
@@ -32,6 +28,7 @@ Py_GetArgcArgv.argtypes = [ctypes.POINTER(ctypes.c_int),
# tracks the last name we've changed the process to
currentProcessName = None
+maxNameLength = -1
def renameProcess(processName):
"""
@@ -53,7 +50,7 @@ def _setArgv(processName):
strcpy(argv[0], "new_name");
"""
- global currentProcessName
+ global currentProcessName, maxNameLength
argv = ctypes.c_int(0)
argc = argc_t()
@@ -66,7 +63,7 @@ def _setArgv(processName):
# for Jake's implementation on this.
if currentProcessName == None:
- # Using argv via...
+ # Getting argv via...
# currentProcessName = " ".join(["python"] + sys.argv)
#
# doesn't do the trick since this will miss interpretor arguments like...
@@ -82,18 +79,20 @@ def _setArgv(processName):
# python ./src/starter.py
currentProcessName = psResults[1]
+ maxNameLength = len(currentProcessName)
if not currentProcessName:
raise IOError("unable to determine our process name")
- # we need to fill extra space with null characters, otherwise the process
- # will end with a '?' when you run ps against it
- if len(currentProcessName) > len(processName):
- processName += "\0" * (len(currentProcessName) - len(processName))
+ if len(processName) > maxNameLength:
+ msg = "can't rename process to something longer than our initial name since this would overwrite memory used for the env"
+ raise IOError(msg)
+
+ # space we need to clear
+ zeroSize = max(len(currentProcessName), len(processName))
- size = min(len(processName), MAX_CHAR)
- ctypes.memset(argc.contents, 0, size + 1) # null terminate the string's end
- ctypes.memmove(argc.contents, processName, size)
+ ctypes.memset(argc.contents, 0, zeroSize + 1) # null terminate the string's end
+ ctypes.memmove(argc.contents, processName, len(processName))
currentProcessName = processName[:MAX_CHAR]
def _setPrctlName(processName):
[View Less]
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 …
[View More]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 ->
[View Less]