[tor-commits] [arm/master] Removing all trailing whitespace

atagar at torproject.org atagar at torproject.org
Mon Sep 16 22:17:30 UTC 2013


commit 058dee1d2ae6570d2ab1eb7254783c276d32c838
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Sep 16 14:51:48 2013 -0700

    Removing all trailing whitespace
    
    We'll be swapping over to stem's conventions which are closer to PEP8. Starting
    by removing all trailing whitespace.
---
 arm/configPanel.py                 |  216 ++++++++---------
 arm/connections/circEntry.py       |   78 +++----
 arm/connections/connEntry.py       |  338 +++++++++++++--------------
 arm/connections/connPanel.py       |  252 ++++++++++----------
 arm/connections/countPopup.py      |   40 ++--
 arm/connections/descriptorPopup.py |   82 +++----
 arm/connections/entries.py         |   66 +++---
 arm/controller.py                  |  262 ++++++++++-----------
 arm/graphing/bandwidthStats.py     |  160 ++++++-------
 arm/graphing/connStats.py          |   24 +-
 arm/graphing/graphPanel.py         |  190 +++++++--------
 arm/graphing/resourceStats.py      |   18 +-
 arm/headerPanel.py                 |  192 +++++++--------
 arm/logPanel.py                    |  450 ++++++++++++++++++------------------
 arm/menu/actions.py                |  122 +++++-----
 arm/menu/item.py                   |   84 +++----
 arm/menu/menu.py                   |   58 ++---
 arm/popups.py                      |  110 ++++-----
 arm/prereq.py                      |   46 ++--
 arm/torrcPanel.py                  |  104 ++++-----
 arm/util/__init__.py               |    4 +-
 arm/util/connections.py            |  250 ++++++++++----------
 arm/util/hostnames.py              |   96 ++++----
 arm/util/panel.py                  |  292 +++++++++++------------
 arm/util/sysTools.py               |   88 +++----
 arm/util/textInput.py              |   68 +++---
 arm/util/torConfig.py              |  390 +++++++++++++++----------------
 arm/util/torTools.py               |  448 +++++++++++++++++------------------
 arm/util/uiTools.py                |  172 +++++++-------
 setup.py                           |   24 +-
 30 files changed, 2362 insertions(+), 2362 deletions(-)

diff --git a/arm/configPanel.py b/arm/configPanel.py
index 9ae4fa8..c0fb974 100644
--- a/arm/configPanel.py
+++ b/arm/configPanel.py
@@ -69,7 +69,7 @@ def getFieldFromLabel(fieldLabel):
   Converts field labels back to their enumeration, raising a ValueError if it
   doesn't exist.
   """
-  
+
   for entryEnum in FIELD_ATTR:
     if fieldLabel == FIELD_ATTR[entryEnum][0]:
       return entryEnum
@@ -78,18 +78,18 @@ class ConfigEntry():
   """
   Configuration option in the panel.
   """
-  
+
   def __init__(self, option, type, isDefault):
     self.fields = {}
     self.fields[Field.OPTION] = option
     self.fields[Field.TYPE] = type
     self.fields[Field.IS_DEFAULT] = isDefault
-    
+
     # Fetches extra infromation from external sources (the arm config and tor
     # man page). These are None if unavailable for this config option.
     summary = torConfig.getConfigSummary(option)
     manEntry = torConfig.getConfigDescription(option)
-    
+
     if manEntry:
       self.fields[Field.MAN_ENTRY] = manEntry.index
       self.fields[Field.CATEGORY] = manEntry.category
@@ -100,49 +100,49 @@ class ConfigEntry():
       self.fields[Field.CATEGORY] = torConfig.Category.UNKNOWN
       self.fields[Field.ARG_USAGE] = ""
       self.fields[Field.DESCRIPTION] = ""
-    
+
     # uses the full man page description if a summary is unavailable
     self.fields[Field.SUMMARY] = summary if summary != None else self.fields[Field.DESCRIPTION]
-    
+
     # cache of what's displayed for this configuration option
     self.labelCache = None
     self.labelCacheArgs = None
-  
+
   def get(self, field):
     """
     Provides back the value in the given field.
-    
+
     Arguments:
       field - enum for the field to be provided back
     """
-    
+
     if field == Field.VALUE: return self._getValue()
     else: return self.fields[field]
-  
+
   def getAll(self, fields):
     """
     Provides back a list with the given field values.
-    
+
     Arguments:
       field - enums for the fields to be provided back
     """
-    
+
     return [self.get(field) for field in fields]
-  
+
   def getLabel(self, optionWidth, valueWidth, summaryWidth):
     """
     Provides display string of the configuration entry with the given
     constraints on the width of the contents.
-    
+
     Arguments:
       optionWidth  - width of the option column
       valueWidth   - width of the value column
       summaryWidth - width of the summary column
     """
-    
+
     # Fetching the display entries is very common so this caches the values.
     # Doing this substantially drops cpu usage when scrolling (by around 40%).
-    
+
     argSet = (optionWidth, valueWidth, summaryWidth)
     if not self.labelCache or self.labelCacheArgs != argSet:
       optionLabel = uiTools.cropStr(self.get(Field.OPTION), optionWidth)
@@ -151,26 +151,26 @@ class ConfigEntry():
       lineTextLayout = "%%-%is %%-%is %%-%is" % (optionWidth, valueWidth, summaryWidth)
       self.labelCache = lineTextLayout % (optionLabel, valueLabel, summaryLabel)
       self.labelCacheArgs = argSet
-    
+
     return self.labelCache
-  
+
   def isUnset(self):
     """
     True if we have no value, false otherwise.
     """
-    
+
     confValue = torTools.getConn().getOption(self.get(Field.OPTION), [], True)
     return not bool(confValue)
-  
+
   def _getValue(self):
     """
     Provides the current value of the configuration entry, taking advantage of
     the torTools caching to effectively query the accurate value. This uses the
     value's type to provide a user friendly representation if able.
     """
-    
+
     confValue = ", ".join(torTools.getConn().getOption(self.get(Field.OPTION), [], True))
-    
+
     # provides nicer values for recognized types
     if not confValue: confValue = "<none>"
     elif self.get(Field.TYPE) == "Boolean" and confValue in ("0", "1"):
@@ -179,7 +179,7 @@ class ConfigEntry():
       confValue = str_tools.get_size_label(int(confValue))
     elif self.get(Field.TYPE) == "TimeInterval" and confValue.isdigit():
       confValue = str_tools.get_time_label(int(confValue), is_long = True)
-    
+
     return confValue
 
 class ConfigPanel(panel.Panel):
@@ -187,48 +187,48 @@ class ConfigPanel(panel.Panel):
   Renders a listing of the tor or arm configuration state, allowing options to
   be selected and edited.
   """
-  
+
   def __init__(self, stdscr, configType):
     panel.Panel.__init__(self, stdscr, "configuration", 0)
-    
+
     self.configType = configType
     self.confContents = []
     self.confImportantContents = []
     self.scroller = uiTools.Scroller(True)
     self.valsLock = threading.RLock()
-    
+
     # shows all configuration options if true, otherwise only the ones with
     # the 'important' flag are shown
     self.showAll = False
-    
+
     # initializes config contents if we're connected
     conn = torTools.getConn()
     conn.addStatusListener(self.resetListener)
     if conn.isAlive(): self.resetListener(None, stem.control.State.INIT, None)
-  
+
   def resetListener(self, controller, eventType, _):
     # fetches configuration options if a new instance, otherewise keeps our
     # current contents
-    
+
     if eventType == stem.control.State.INIT:
       self._loadConfigOptions()
-  
+
   def _loadConfigOptions(self):
     """
     Fetches the configuration options available from tor or arm.
     """
-    
+
     self.confContents = []
     self.confImportantContents = []
-    
+
     if self.configType == State.TOR:
       conn, configOptionLines = torTools.getConn(), []
       customOptions = torConfig.getCustomOptions()
       configOptionQuery = conn.getInfo("config/names", None)
-      
+
       if configOptionQuery:
         configOptionLines = configOptionQuery.strip().split("\n")
-      
+
       for line in configOptionLines:
         # lines are of the form "<option> <type>[ <documentation>]", like:
         # UseEntryGuards Boolean
@@ -236,83 +236,83 @@ class ConfigPanel(panel.Panel):
         # 0.2.1.25)
         lineComp = line.strip().split(" ")
         confOption, confType = lineComp[0], lineComp[1]
-        
+
         # skips private and virtual entries if not configured to show them
         if not CONFIG["features.config.state.showPrivateOptions"] and confOption.startswith("__"):
           continue
         elif not CONFIG["features.config.state.showVirtualOptions"] and confType == "Virtual":
           continue
-        
+
         self.confContents.append(ConfigEntry(confOption, confType, not confOption in customOptions))
     elif self.configType == State.ARM:
       # loaded via the conf utility
       armConf = conf.get_config("arm")
       for key in armConf.keys():
         pass # TODO: implement
-    
+
     # mirror listing with only the important configuration options
     self.confImportantContents = []
     for entry in self.confContents:
       if torConfig.isImportant(entry.get(Field.OPTION)):
         self.confImportantContents.append(entry)
-    
+
     # if there aren't any important options then show everything
     if not self.confImportantContents:
       self.confImportantContents = self.confContents
-    
+
     self.setSortOrder() # initial sorting of the contents
-  
+
   def getSelection(self):
     """
     Provides the currently selected entry.
     """
-    
+
     return self.scroller.getCursorSelection(self._getConfigOptions())
-  
+
   def setFiltering(self, isFiltered):
     """
     Sets if configuration options are filtered or not.
-    
+
     Arguments:
       isFiltered - if true then only relatively important options will be
                    shown, otherwise everything is shown
     """
-    
+
     self.showAll = not isFiltered
-  
+
   def setSortOrder(self, ordering = None):
     """
     Sets the configuration attributes we're sorting by and resorts the
     contents.
-    
+
     Arguments:
       ordering - new ordering, if undefined then this resorts with the last
                  set ordering
     """
-    
+
     self.valsLock.acquire()
     if ordering: CONFIG["features.config.order"] = ordering
     self.confContents.sort(key=lambda i: (i.getAll(CONFIG["features.config.order"])))
     self.confImportantContents.sort(key=lambda i: (i.getAll(CONFIG["features.config.order"])))
     self.valsLock.release()
-  
+
   def showSortDialog(self):
     """
     Provides the sort dialog for our configuration options.
     """
-    
+
     # set ordering for config options
     titleLabel = "Config Option Ordering:"
     options = [FIELD_ATTR[field][0] for field in Field]
     oldSelection = [FIELD_ATTR[field][0] for field in CONFIG["features.config.order"]]
     optionColors = dict([FIELD_ATTR[field] for field in Field])
     results = popups.showSortDialog(titleLabel, options, oldSelection, optionColors)
-    
+
     if results:
       # converts labels back to enums
       resultEnums = [getFieldFromLabel(label) for label in results]
       self.setSortOrder(resultEnums)
-  
+
   def handleKey(self, key):
     self.valsLock.acquire()
     isKeystrokeConsumed = True
@@ -321,25 +321,25 @@ class ConfigPanel(panel.Panel):
       detailPanelHeight = CONFIG["features.config.selectionDetails.height"]
       if detailPanelHeight > 0 and detailPanelHeight + 2 <= pageHeight:
         pageHeight -= (detailPanelHeight + 1)
-      
+
       isChanged = self.scroller.handleKey(key, self._getConfigOptions(), pageHeight)
       if isChanged: self.redraw(True)
     elif uiTools.isSelectionKey(key) and self._getConfigOptions():
       # Prompts the user to edit the selected configuration value. The
       # interface is locked to prevent updates between setting the value
       # and showing any errors.
-      
+
       panel.CURSES_LOCK.acquire()
       try:
         selection = self.getSelection()
         configOption = selection.get(Field.OPTION)
         if selection.isUnset(): initialValue = ""
         else: initialValue = selection.get(Field.VALUE)
-        
+
         promptMsg = "%s Value (esc to cancel): " % configOption
         isPrepopulated = CONFIG["features.config.prepopulateEditValues"]
         newValue = popups.inputPrompt(promptMsg, initialValue if isPrepopulated else "")
-        
+
         if newValue != None and newValue != initialValue:
           try:
             if selection.get(Field.TYPE) == "Boolean":
@@ -349,16 +349,16 @@ class ConfigPanel(panel.Panel):
             elif selection.get(Field.TYPE) == "LineList":
               # setOption accepts list inputs when there's multiple values
               newValue = newValue.split(",")
-            
+
             torTools.getConn().setOption(configOption, newValue)
-            
+
             # forces the label to be remade with the new value
             selection.labelCache = None
-            
+
             # resets the isDefault flag
             customOptions = torConfig.getCustomOptions()
             selection.fields[Field.IS_DEFAULT] = not configOption in customOptions
-            
+
             self.redraw(True)
           except Exception, exc:
             popups.showMsg("%s (press any key)" % exc)
@@ -372,41 +372,41 @@ class ConfigPanel(panel.Panel):
     elif key == ord('v') or key == ord('V'):
       self.showWriteDialog()
     else: isKeystrokeConsumed = False
-    
+
     self.valsLock.release()
     return isKeystrokeConsumed
-  
+
   def showWriteDialog(self):
     """
     Provies an interface to confirm if the configuration is saved and, if so,
     where.
     """
-    
+
     # display a popup for saving the current configuration
     configLines = torConfig.getCustomOptions(True)
     popup, width, height = popups.init(len(configLines) + 2)
     if not popup: return
-    
+
     try:
       # displayed options (truncating the labels if there's limited room)
       if width >= 30: selectionOptions = ("Save", "Save As...", "Cancel")
       else: selectionOptions = ("Save", "Save As", "X")
-      
+
       # checks if we can show options beside the last line of visible content
       isOptionLineSeparate = False
       lastIndex = min(height - 2, len(configLines) - 1)
-      
+
       # if we don't have room to display the selection options and room to
       # grow then display the selection options on its own line
       if width < (30 + len(configLines[lastIndex])):
         popup.setHeight(height + 1)
         popup.redraw(True) # recreates the window instance
         newHeight, _ = popup.getPreferredSize()
-        
+
         if newHeight > height:
           height = newHeight
           isOptionLineSeparate = True
-      
+
       key, selection = 0, 2
       while not uiTools.isSelectionKey(key):
         # if the popup has been resized then recreate it (needed for the
@@ -415,70 +415,70 @@ class ConfigPanel(panel.Panel):
         if (height, width) != (newHeight, newWidth):
           height, width = newHeight, newWidth
           popup.redraw(True)
-        
+
         # if there isn't room to display the popup then cancel it
         if height <= 2:
           selection = 2
           break
-        
+
         popup.win.erase()
         popup.win.box()
         popup.addstr(0, 0, "Configuration being saved:", curses.A_STANDOUT)
-        
+
         visibleConfigLines = height - 3 if isOptionLineSeparate else height - 2
         for i in range(visibleConfigLines):
           line = uiTools.cropStr(configLines[i], width - 2)
-          
+
           if " " in line:
             option, arg = line.split(" ", 1)
             popup.addstr(i + 1, 1, option, curses.A_BOLD | uiTools.getColor("green"))
             popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD | uiTools.getColor("cyan"))
           else:
             popup.addstr(i + 1, 1, line, curses.A_BOLD | uiTools.getColor("green"))
-        
+
         # draws selection options (drawn right to left)
         drawX = width - 1
         for i in range(len(selectionOptions) - 1, -1, -1):
           optionLabel = selectionOptions[i]
           drawX -= (len(optionLabel) + 2)
-          
+
           # if we've run out of room then drop the option (this will only
           # occure on tiny displays)
           if drawX < 1: break
-          
+
           selectionFormat = curses.A_STANDOUT if i == selection else curses.A_NORMAL
           popup.addstr(height - 2, drawX, "[")
           popup.addstr(height - 2, drawX + 1, optionLabel, selectionFormat | curses.A_BOLD)
           popup.addstr(height - 2, drawX + len(optionLabel) + 1, "]")
-          
+
           drawX -= 1 # space gap between the options
-        
+
         popup.win.refresh()
-        
+
         key = arm.controller.getController().getScreen().getch()
         if key == curses.KEY_LEFT: selection = max(0, selection - 1)
         elif key == curses.KEY_RIGHT: selection = min(len(selectionOptions) - 1, selection + 1)
-      
+
       if selection in (0, 1):
         loadedTorrc, promptCanceled = torConfig.getTorrc(), False
         try: configLocation = loadedTorrc.getConfigLocation()
         except IOError: configLocation = ""
-        
+
         if selection == 1:
           # prompts user for a configuration location
           configLocation = popups.inputPrompt("Save to (esc to cancel): ", configLocation)
           if not configLocation: promptCanceled = True
-        
+
         if not promptCanceled:
           try:
             torConfig.saveConf(configLocation, configLines)
             msg = "Saved configuration to %s" % configLocation
           except IOError, exc:
             msg = "Unable to save configuration (%s)" % exc.strerror
-          
+
           popups.showMsg(msg, 2)
     finally: popups.finalize()
-  
+
   def getHelp(self):
     options = []
     options.append(("up arrow", "scroll up a line", None))
@@ -490,10 +490,10 @@ class ConfigPanel(panel.Panel):
     options.append(("a", "toggle option filtering", None))
     options.append(("s", "sort ordering", None))
     return options
-  
+
   def draw(self, width, height):
     self.valsLock.acquire()
-    
+
     # panel with details for the current selection
     detailPanelHeight = CONFIG["features.config.selectionDetails.height"]
     isScrollbarVisible = False
@@ -510,68 +510,68 @@ class ConfigPanel(panel.Panel):
       scrollLoc = self.scroller.getScrollLoc(self._getConfigOptions(), height - 1 - detailPanelHeight)
       cursorSelection = self.getSelection()
       isScrollbarVisible = len(self._getConfigOptions()) > height - detailPanelHeight - 1
-      
+
       if cursorSelection != None:
         self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, isScrollbarVisible)
-    
+
     # draws the top label
     if self.isTitleVisible():
       configType = "Tor" if self.configType == State.TOR else "Arm"
       hiddenMsg = "press 'a' to hide most options" if self.showAll else "press 'a' to show all options"
       titleLabel = "%s Configuration (%s):" % (configType, hiddenMsg)
       self.addstr(0, 0, titleLabel, curses.A_STANDOUT)
-    
+
     # draws left-hand scroll bar if content's longer than the height
     scrollOffset = 1
     if isScrollbarVisible:
       scrollOffset = 3
       self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self._getConfigOptions()), 1 + detailPanelHeight)
-    
+
     optionWidth = CONFIG["features.config.state.colWidth.option"]
     valueWidth = CONFIG["features.config.state.colWidth.value"]
     descriptionWidth = max(0, width - scrollOffset - optionWidth - valueWidth - 2)
-    
+
     # if the description column is overly long then use its space for the
     # value instead
     if descriptionWidth > 80:
       valueWidth += descriptionWidth - 80
       descriptionWidth = 80
-    
+
     for lineNum in range(scrollLoc, len(self._getConfigOptions())):
       entry = self._getConfigOptions()[lineNum]
       drawLine = lineNum + detailPanelHeight + 1 - scrollLoc
-      
+
       lineFormat = curses.A_NORMAL if entry.get(Field.IS_DEFAULT) else curses.A_BOLD
       if entry.get(Field.CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(Field.CATEGORY)])
       if entry == cursorSelection: lineFormat |= curses.A_STANDOUT
-      
+
       lineText = entry.getLabel(optionWidth, valueWidth, descriptionWidth)
       self.addstr(drawLine, scrollOffset, lineText, lineFormat)
-      
+
       if drawLine >= height: break
-    
+
     self.valsLock.release()
-  
+
   def _getConfigOptions(self):
     return self.confContents if self.showAll else self.confImportantContents
-  
+
   def _drawSelectionPanel(self, selection, width, detailPanelHeight, isScrollbarVisible):
     """
     Renders a panel for the selected configuration option.
     """
-    
+
     # This is a solid border unless the scrollbar is visible, in which case a
     # 'T' pipe connects the border to the bar.
     uiTools.drawBox(self, 0, 0, width, detailPanelHeight + 1)
     if isScrollbarVisible: self.addch(detailPanelHeight, 1, curses.ACS_TTEE)
-    
+
     selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[selection.get(Field.CATEGORY)])
-    
+
     # first entry:
     # <option> (<category> Option)
     optionLabel =" (%s Option)" % selection.get(Field.CATEGORY)
     self.addstr(1, 2, selection.get(Field.OPTION) + optionLabel, selectionFormat)
-    
+
     # second entry:
     # Value: <value> ([default|custom], <type>, usage: <argument usage>)
     if detailPanelHeight >= 3:
@@ -580,28 +580,28 @@ class ConfigPanel(panel.Panel):
       valueAttr.append(selection.get(Field.TYPE))
       valueAttr.append("usage: %s" % (selection.get(Field.ARG_USAGE)))
       valueAttrLabel = ", ".join(valueAttr)
-      
+
       valueLabelWidth = width - 12 - len(valueAttrLabel)
       valueLabel = uiTools.cropStr(selection.get(Field.VALUE), valueLabelWidth)
-      
+
       self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat)
-    
+
     # remainder is filled with the man page description
     descriptionHeight = max(0, detailPanelHeight - 3)
     descriptionContent = "Description: " + selection.get(Field.DESCRIPTION)
-    
+
     for i in range(descriptionHeight):
       # checks if we're done writing the description
       if not descriptionContent: break
-      
+
       # there's a leading indent after the first line
       if i > 0: descriptionContent = "  " + descriptionContent
-      
+
       # we only want to work with content up until the next newline
       if "\n" in descriptionContent:
         lineContent, descriptionContent = descriptionContent.split("\n", 1)
       else: lineContent, descriptionContent = descriptionContent, ""
-      
+
       if i != descriptionHeight - 1:
         # there's more lines to display
         msg, remainder = uiTools.cropStr(lineContent, width - 3, 4, 4, uiTools.Ending.HYPHEN, True)
@@ -609,6 +609,6 @@ class ConfigPanel(panel.Panel):
       else:
         # this is the last line, end it with an ellipse
         msg = uiTools.cropStr(lineContent, width - 3, 4, 4)
-      
+
       self.addstr(3 + i, 2, msg, selectionFormat)
 
diff --git a/arm/connections/circEntry.py b/arm/connections/circEntry.py
index 6c809b1..cef6820 100644
--- a/arm/connections/circEntry.py
+++ b/arm/connections/circEntry.py
@@ -16,56 +16,56 @@ from arm.util import torTools, uiTools
 class CircEntry(connEntry.ConnectionEntry):
   def __init__(self, circuitID, status, purpose, path):
     connEntry.ConnectionEntry.__init__(self, "127.0.0.1", "0", "127.0.0.1", "0")
-    
+
     self.circuitID = circuitID
     self.status = status
-    
+
     # drops to lowercase except the first letter
     if len(purpose) >= 2:
       purpose = purpose[0].upper() + purpose[1:].lower()
-    
+
     self.lines = [CircHeaderLine(self.circuitID, purpose)]
-    
+
     # Overwrites attributes of the initial line to make it more fitting as the
     # header for our listing.
-    
+
     self.lines[0].baseType = connEntry.Category.CIRCUIT
-    
+
     self.update(status, path)
-  
+
   def update(self, status, path):
     """
     Our status and path can change over time if the circuit is still in the
     process of being built. Updates these attributes of our relay.
-    
+
     Arguments:
       status - new status of the circuit
       path   - list of fingerprints for the series of relays involved in the
                circuit
     """
-    
+
     self.status = status
     self.lines = [self.lines[0]]
     conn = torTools.getConn()
-    
+
     if status == "BUILT" and not self.lines[0].isBuilt:
       exitIp, exitORPort = conn.getRelayAddress(path[-1], ("192.168.0.1", "0"))
       self.lines[0].setExit(exitIp, exitORPort, path[-1])
-    
+
     for i in range(len(path)):
       relayFingerprint = path[i]
       relayIp, relayOrPort = conn.getRelayAddress(relayFingerprint, ("192.168.0.1", "0"))
-      
+
       if i == len(path) - 1:
         if status == "BUILT": placementType = "Exit"
         else: placementType = "Extending"
       elif i == 0: placementType = "Guard"
       else: placementType = "Middle"
-      
+
       placementLabel = "%i / %s" % (i + 1, placementType)
-      
+
       self.lines.append(CircLine(relayIp, relayOrPort, relayFingerprint, placementLabel))
-    
+
     self.lines[-1].isLast = True
 
 class CircHeaderLine(connEntry.ConnectionLine):
@@ -73,40 +73,40 @@ class CircHeaderLine(connEntry.ConnectionLine):
   Initial line of a client entry. This has the same basic format as connection
   lines except that its etc field has circuit attributes.
   """
-  
+
   def __init__(self, circuitID, purpose):
     connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", "0.0.0.0", "0", False, False)
     self.circuitID = circuitID
     self.purpose = purpose
     self.isBuilt = False
-  
+
   def setExit(self, exitIpAddr, exitPort, exitFingerprint):
     connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", exitIpAddr, exitPort, False, False)
     self.isBuilt = True
     self.foreign.fingerprintOverwrite = exitFingerprint
-  
+
   def getType(self):
     return connEntry.Category.CIRCUIT
-  
+
   def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False):
     if not self.isBuilt: return "Building..."
     return connEntry.ConnectionLine.getDestinationLabel(self, maxLength, includeLocale, includeHostname)
-  
+
   def getEtcContent(self, width, listingType):
     """
     Attempts to provide all circuit related stats. Anything that can't be
     shown completely (not enough room) is dropped.
     """
-    
+
     etcAttr = ["Purpose: %s" % self.purpose, "Circuit ID: %i" % self.circuitID]
-    
+
     for i in range(len(etcAttr), -1, -1):
       etcLabel = ", ".join(etcAttr[:i])
       if len(etcLabel) <= width:
         return ("%%-%is" % width) % etcLabel
-    
+
     return ""
-  
+
   def getDetails(self, width):
     if not self.isBuilt:
       detailFormat = curses.A_BOLD | uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()])
@@ -119,60 +119,60 @@ class CircLine(connEntry.ConnectionLine):
   otherwise makes use of the ConnectionLine attributes (for the detail display,
   caching, etc).
   """
-  
+
   def __init__(self, fIpAddr, fPort, fFingerprint, placementLabel):
     connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", fIpAddr, fPort)
     self.foreign.fingerprintOverwrite = fFingerprint
     self.placementLabel = placementLabel
     self.includePort = False
-    
+
     # determines the sort of left hand bracketing we use
     self.isLast = False
-  
+
   def getType(self):
     return connEntry.Category.CIRCUIT
-  
+
   def getListingPrefix(self):
     if self.isLast: return (ord(' '), curses.ACS_LLCORNER, curses.ACS_HLINE, ord(' '))
     else: return (ord(' '), curses.ACS_VLINE, ord(' '), ord(' '))
-  
+
   def getListingEntry(self, width, currentTime, listingType):
     """
     Provides the [(msg, attr)...] listing for this relay in the circuilt
     listing. Lines are composed of the following components:
       <bracket> <dst> <etc> <placement label>
-    
+
     The dst and etc entries largely match their ConnectionEntry counterparts.
-    
+
     Arguments:
       width       - maximum length of the line
       currentTime - the current unix time (ignored)
       listingType - primary attribute we're listing connections by
     """
-    
+
     return entries.ConnectionPanelLine.getListingEntry(self, width, currentTime, listingType)
-  
+
   def _getListingEntry(self, width, currentTime, listingType):
     lineFormat = uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()])
-    
+
     # The required widths are the sum of the following:
     # initial space (1 character)
     # bracketing (3 characters)
     # placementLabel (14 characters)
     # gap between etc and placement label (5 characters)
-    
+
     baselineSpace = 14 + 5
-    
+
     dst, etc = "", ""
     if listingType == entries.ListingType.IP_ADDRESS:
       # TODO: include hostname when that's available
       # dst width is derived as:
       # src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char
       dst = "%-53s" % self.getDestinationLabel(53, includeLocale = True)
-      
+
       # fills the nickname into the empty space here
       dst = "%s%-25s   " % (dst[:25], uiTools.cropStr(self.foreign.getNickname(), 25, 0))
-      
+
       etc = self.getEtcContent(width - baselineSpace - len(dst), listingType)
     elif listingType == entries.ListingType.HOSTNAME:
       # min space for the hostname is 40 characters
@@ -189,7 +189,7 @@ class CircLine(connEntry.ConnectionLine):
       etc = self.getEtcContent(width - baselineSpace - 56, listingType)
       dstLayout = "%%-%is" % (width - baselineSpace - len(etc))
       dst = dstLayout % self.foreign.getNickname()
-    
+
     return ((dst + etc, lineFormat),
             (" " * (width - baselineSpace - len(dst) - len(etc) + 5), lineFormat),
             ("%-14s" % self.placementLabel, lineFormat))
diff --git a/arm/connections/connEntry.py b/arm/connections/connEntry.py
index 8ade8e5..bd83cca 100644
--- a/arm/connections/connEntry.py
+++ b/arm/connections/connEntry.py
@@ -51,43 +51,43 @@ class Endpoint:
   thin wrapper for torUtil functions, making use of its caching for
   performance.
   """
-  
+
   def __init__(self, ipAddr, port):
     self.ipAddr = ipAddr
     self.port = port
-    
+
     # if true, we treat the port as an definitely not being an ORPort when
     # searching for matching fingerprints (otherwise we use it to possably
     # narrow results when unknown)
     self.isNotORPort = True
-    
+
     # if set then this overwrites fingerprint lookups
     self.fingerprintOverwrite = None
-  
+
   def getIpAddr(self):
     """
     Provides the IP address of the endpoint.
     """
-    
+
     return self.ipAddr
-  
+
   def getPort(self):
     """
     Provides the port of the endpoint.
     """
-    
+
     return self.port
-  
+
   def getHostname(self, default = None):
     """
     Provides the hostname associated with the relay's address. This is a
     non-blocking call and returns None if the address either can't be resolved
     or hasn't been resolved yet.
-    
+
     Arguments:
       default - return value if no hostname is available
     """
-    
+
     # TODO: skipping all hostname resolution to be safe for now
     #try:
     #  myHostname = hostnames.resolve(self.ipAddr)
@@ -97,52 +97,52 @@ class Endpoint:
     #
     #if not myHostname: return default
     #else: return myHostname
-    
+
     return default
-  
+
   def getLocale(self, default=None):
     """
     Provides the two letter country code for the IP address' locale.
-    
+
     Arguments:
       default - return value if no locale information is available
     """
-    
+
     conn = torTools.getConn()
     return conn.getInfo("ip-to-country/%s" % self.ipAddr, default)
-  
+
   def getFingerprint(self):
     """
     Provides the fingerprint of the relay, returning "UNKNOWN" if it can't be
     determined.
     """
-    
+
     if self.fingerprintOverwrite:
       return self.fingerprintOverwrite
-    
+
     conn = torTools.getConn()
     myFingerprint = conn.getRelayFingerprint(self.ipAddr)
-    
+
     # If there were multiple matches and our port is likely the ORPort then
     # try again with that to narrow the results.
     if not myFingerprint and not self.isNotORPort:
       myFingerprint = conn.getRelayFingerprint(self.ipAddr, self.port)
-    
+
     if myFingerprint: return myFingerprint
     else: return "UNKNOWN"
-  
+
   def getNickname(self):
     """
     Provides the nickname of the relay, retuning "UNKNOWN" if it can't be
     determined.
     """
-    
+
     myFingerprint = self.getFingerprint()
-    
+
     if myFingerprint != "UNKNOWN":
       conn = torTools.getConn()
       myNickname = conn.getRelayNickname(myFingerprint)
-      
+
       if myNickname: return myNickname
       else: return "UNKNOWN"
     else: return "UNKNOWN"
@@ -153,16 +153,16 @@ class ConnectionEntry(entries.ConnectionPanelEntry):
   concern real connections so it includes the inbound, outbound, directory,
   application, and controller categories.
   """
-  
+
   def __init__(self, lIpAddr, lPort, fIpAddr, fPort):
     entries.ConnectionPanelEntry.__init__(self)
     self.lines = [ConnectionLine(lIpAddr, lPort, fIpAddr, fPort)]
-  
+
   def getSortValue(self, attr, listingType):
     """
     Provides the value of a single attribute used for sorting purposes.
     """
-    
+
     connLine = self.lines[0]
     if attr == entries.SortAttr.IP_ADDRESS:
       if connLine.isPrivate(): return SCRUBBED_IP_VAL # orders at the end
@@ -192,44 +192,44 @@ class ConnectionLine(entries.ConnectionPanelLine):
   """
   Display component of the ConnectionEntry.
   """
-  
+
   def __init__(self, lIpAddr, lPort, fIpAddr, fPort, includePort=True, includeExpandedIpAddr=True):
     entries.ConnectionPanelLine.__init__(self)
-    
+
     self.local = Endpoint(lIpAddr, lPort)
     self.foreign = Endpoint(fIpAddr, fPort)
     self.startTime = time.time()
     self.isInitialConnection = False
-    
+
     # overwrite the local fingerprint with ours
     conn = torTools.getConn()
     self.local.fingerprintOverwrite = conn.getInfo("fingerprint", None)
-    
+
     # True if the connection has matched the properties of a client/directory
     # connection every time we've checked. The criteria we check is...
     #   client    - first hop in an established circuit
     #   directory - matches an established single-hop circuit (probably a
     #               directory mirror)
-    
+
     self._possibleClient = True
     self._possibleDirectory = True
-    
+
     # attributes for SOCKS, HIDDEN, and CONTROL connections
     self.appName = None
     self.appPid = None
     self.isAppResolving = False
-    
+
     myOrPort = conn.getOption("ORPort", None)
     myDirPort = conn.getOption("DirPort", None)
     mySocksPort = conn.getOption("SocksPort", "9050")
     myCtlPort = conn.getOption("ControlPort", None)
     myHiddenServicePorts = conn.getHiddenServicePorts()
-    
+
     # the ORListenAddress can overwrite the ORPort
     listenAddr = conn.getOption("ORListenAddress", None)
     if listenAddr and ":" in listenAddr:
       myOrPort = listenAddr[listenAddr.find(":") + 1:]
-    
+
     if lPort in (myOrPort, myDirPort):
       self.baseType = Category.INBOUND
       self.local.isNotORPort = False
@@ -242,74 +242,74 @@ class ConnectionLine(entries.ConnectionPanelLine):
     else:
       self.baseType = Category.OUTBOUND
       self.foreign.isNotORPort = False
-    
+
     self.cachedType = None
-    
+
     # includes the port or expanded ip address field when displaying listing
     # information if true
     self.includePort = includePort
     self.includeExpandedIpAddr = includeExpandedIpAddr
-    
+
     # cached immutable values used for sorting
     self.sortIpAddr = connections.ipToInt(self.foreign.getIpAddr())
     self.sortPort = int(self.foreign.getPort())
-  
+
   def getListingEntry(self, width, currentTime, listingType):
     """
     Provides the tuple list for this connection's listing. Lines are composed
     of the following components:
       <src>  -->  <dst>     <etc>     <uptime> (<type>)
-    
+
     ListingType.IP_ADDRESS:
       src - <internal addr:port> --> <external addr:port>
       dst - <destination addr:port>
       etc - <fingerprint> <nickname>
-    
+
     ListingType.HOSTNAME:
       src - localhost:<port>
       dst - <destination hostname:port>
       etc - <destination addr:port> <fingerprint> <nickname>
-    
+
     ListingType.FINGERPRINT:
       src - localhost
       dst - <destination fingerprint>
       etc - <nickname> <destination addr:port>
-    
+
     ListingType.NICKNAME:
       src - <source nickname>
       dst - <destination nickname>
       etc - <fingerprint> <destination addr:port>
-    
+
     Arguments:
       width       - maximum length of the line
       currentTime - unix timestamp for what the results should consider to be
                     the current time
       listingType - primary attribute we're listing connections by
     """
-    
+
     # fetch our (most likely cached) display entry for the listing
     myListing = entries.ConnectionPanelLine.getListingEntry(self, width, currentTime, listingType)
-    
+
     # fill in the current uptime and return the results
     if CONFIG["features.connection.markInitialConnections"]:
       timePrefix = "+" if self.isInitialConnection else " "
     else: timePrefix = ""
-    
+
     timeLabel = timePrefix + "%5s" % str_tools.get_time_label(currentTime - self.startTime, 1)
     myListing[2] = (timeLabel, myListing[2][1])
-    
+
     return myListing
-  
+
   def isUnresolvedApp(self):
     """
     True if our display uses application information that hasn't yet been resolved.
     """
-    
+
     return self.appName == None and self.getType() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL)
-  
+
   def _getListingEntry(self, width, currentTime, listingType):
     entryType = self.getType()
-    
+
     # Lines are split into the following components in reverse:
     # init gap - " "
     # content  - "<src>  -->  <dst>     <etc>     "
@@ -317,10 +317,10 @@ class ConnectionLine(entries.ConnectionPanelLine):
     # preType  - " ("
     # category - "<type>"
     # postType - ")   "
-    
+
     lineFormat = uiTools.getColor(CATEGORY_COLOR[entryType])
     timeWidth = 6 if CONFIG["features.connection.markInitialConnections"] else 5
-    
+
     drawEntry = [(" ", lineFormat),
                  (self._getListingContent(width - (12 + timeWidth) - 1, listingType), lineFormat),
                  (" " * timeWidth, lineFormat),
@@ -328,41 +328,41 @@ class ConnectionLine(entries.ConnectionPanelLine):
                  (entryType.upper(), lineFormat | curses.A_BOLD),
                  (")" + " " * (9 - len(entryType)), lineFormat)]
     return drawEntry
-  
+
   def _getDetails(self, width):
     """
     Provides details on the connection, correlated against available consensus
     data.
-    
+
     Arguments:
       width - available space to display in
     """
-    
+
     detailFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[self.getType()])
     return [(line, detailFormat) for line in self._getDetailContent(width)]
-  
+
   def resetDisplay(self):
     entries.ConnectionPanelLine.resetDisplay(self)
     self.cachedType = None
-  
+
   def isPrivate(self):
     """
     Returns true if the endpoint is private, possibly belonging to a client
     connection or exit traffic.
     """
-    
+
     if not CONFIG["features.connection.showIps"]: return True
-    
+
     # This is used to scrub private information from the interface. Relaying
     # etiquette (and wiretapping laws) say these are bad things to look at so
     # DON'T CHANGE THIS UNLESS YOU HAVE A DAMN GOOD REASON!
-    
+
     myType = self.getType()
-    
+
     if myType == Category.INBOUND:
       # if we're a guard or bridge and the connection doesn't belong to a
       # known relay then it might be client traffic
-      
+
       conn = torTools.getConn()
       if "Guard" in conn.getMyFlags([]) or conn.getOption("BridgeRelay", None) == "1":
         allMatches = conn.getRelayFingerprint(self.foreign.getIpAddr(), getAllMatches = True)
@@ -370,23 +370,23 @@ class ConnectionLine(entries.ConnectionPanelLine):
     elif myType == Category.EXIT:
       # DNS connections exiting us aren't private (since they're hitting our
       # resolvers). Everything else, however, is.
-      
+
       # TODO: Ideally this would also double check that it's a UDP connection
       # (since DNS is the only UDP connections Tor will relay), however this
       # will take a bit more work to propagate the information up from the
       # connection resolver.
       return self.foreign.getPort() != "53"
-    
+
     # for everything else this isn't a concern
     return False
-  
+
   def getType(self):
     """
     Provides our best guess at the current type of the connection. This
     depends on consensus results, our current client circuits, etc. Results
     are cached until this entry's display is reset.
     """
-    
+
     # caches both to simplify the calls and to keep the type consistent until
     # we want to reflect changes
     if not self.cachedType:
@@ -395,79 +395,79 @@ class ConnectionLine(entries.ConnectionPanelLine):
         # - EXIT since this depends on the current consensus
         # - CIRCUIT if this is likely to belong to our guard usage
         # - DIRECTORY if this is a single-hop circuit (directory mirror?)
-        # 
+        #
         # The exitability, circuits, and fingerprints are all cached by the
         # torTools util keeping this a quick lookup.
-        
+
         conn = torTools.getConn()
         destFingerprint = self.foreign.getFingerprint()
-        
+
         if destFingerprint == "UNKNOWN":
           # Not a known relay. This might be an exit connection.
-          
+
           if conn.isExitingAllowed(self.foreign.getIpAddr(), self.foreign.getPort()):
             self.cachedType = Category.EXIT
         elif self._possibleClient or self._possibleDirectory:
           # This belongs to a known relay. If we haven't eliminated ourselves as
           # a possible client or directory connection then check if it still
           # holds true.
-          
+
           myCircuits = conn.getCircuits()
-          
+
           if self._possibleClient:
             # Checks that this belongs to the first hop in a circuit that's
             # either unestablished or longer than a single hop (ie, anything but
             # a built 1-hop connection since those are most likely a directory
             # mirror).
-            
+
             for _, status, _, path in myCircuits:
               if path and path[0] == destFingerprint and (status != "BUILT" or len(path) > 1):
                 self.cachedType = Category.CIRCUIT # matched a probable guard connection
-            
+
             # if we fell through, we can eliminate ourselves as a guard in the future
             if not self.cachedType:
               self._possibleClient = False
-          
+
           if self._possibleDirectory:
             # Checks if we match a built, single hop circuit.
-            
+
             for _, status, _, path in myCircuits:
               if path and path[0] == destFingerprint and status == "BUILT" and len(path) == 1:
                 self.cachedType = Category.DIRECTORY
-            
+
             # if we fell through, eliminate ourselves as a directory connection
             if not self.cachedType:
               self._possibleDirectory = False
-      
+
       if not self.cachedType:
         self.cachedType = self.baseType
-    
+
     return self.cachedType
-  
+
   def getEtcContent(self, width, listingType):
     """
     Provides the optional content for the connection.
-    
+
     Arguments:
       width       - maximum length of the line
       listingType - primary attribute we're listing connections by
     """
-    
+
     # for applications show the command/pid
     if self.getType() in (Category.SOCKS, Category.HIDDEN, Category.CONTROL):
       displayLabel = ""
-      
+
       if self.appName:
         if self.appPid: displayLabel = "%s (%s)" % (self.appName, self.appPid)
         else: displayLabel = self.appName
       elif self.isAppResolving:
         displayLabel = "resolving..."
       else: displayLabel = "UNKNOWN"
-      
+
       if len(displayLabel) < width:
         return ("%%-%is" % width) % displayLabel
       else: return ""
-    
+
     # for everything else display connection/consensus information
     dstAddress = self.getDestinationLabel(26, includeLocale = True)
     etc, usedSpace = "", 0
@@ -476,7 +476,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
         # show fingerprint (column width: 42 characters)
         etc += "%-40s  " % self.foreign.getFingerprint()
         usedSpace += 42
-      
+
       if width > usedSpace + 10 and CONFIG["features.connection.showColumn.nickname"]:
         # show nickname (column width: remainder)
         nicknameSpace = width - usedSpace
@@ -488,12 +488,12 @@ class ConnectionLine(entries.ConnectionPanelLine):
         # show destination ip/port/locale (column width: 28 characters)
         etc += "%-26s  " % dstAddress
         usedSpace += 28
-      
+
       if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]:
         # show fingerprint (column width: 42 characters)
         etc += "%-40s  " % self.foreign.getFingerprint()
         usedSpace += 42
-      
+
       if width > usedSpace + 17 and CONFIG["features.connection.showColumn.nickname"]:
         # show nickname (column width: min 17 characters, uses half of the remainder)
         nicknameSpace = 15 + (width - (usedSpace + 17)) / 2
@@ -504,18 +504,18 @@ class ConnectionLine(entries.ConnectionPanelLine):
       if width > usedSpace + 17:
         # show nickname (column width: min 17 characters, consumes any remaining space)
         nicknameSpace = width - usedSpace - 2
-        
+
         # if there's room then also show a column with the destination
         # ip/port/locale (column width: 28 characters)
         isIpLocaleIncluded = width > usedSpace + 45
         isIpLocaleIncluded &= CONFIG["features.connection.showColumn.destination"]
         if isIpLocaleIncluded: nicknameSpace -= 28
-        
+
         if CONFIG["features.connection.showColumn.nickname"]:
           nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0)
           etc += ("%%-%is  " % nicknameSpace) % nicknameLabel
           usedSpace += nicknameSpace + 2
-        
+
         if isIpLocaleIncluded:
           etc += "%-26s  " % dstAddress
           usedSpace += 28
@@ -524,40 +524,40 @@ class ConnectionLine(entries.ConnectionPanelLine):
         # show fingerprint (column width: 42 characters)
         etc += "%-40s  " % self.foreign.getFingerprint()
         usedSpace += 42
-      
+
       if width > usedSpace + 28 and CONFIG["features.connection.showColumn.destination"]:
         # show destination ip/port/locale (column width: 28 characters)
         etc += "%-26s  " % dstAddress
         usedSpace += 28
-    
+
     return ("%%-%is" % width) % etc
-  
+
   def _getListingContent(self, width, listingType):
     """
     Provides the source, destination, and extra info for our listing.
-    
+
     Arguments:
       width       - maximum length of the line
       listingType - primary attribute we're listing connections by
     """
-    
+
     conn = torTools.getConn()
     myType = self.getType()
     dstAddress = self.getDestinationLabel(26, includeLocale = True)
-    
+
     # The required widths are the sum of the following:
     # - room for LABEL_FORMAT and LABEL_MIN_PADDING (11 characters)
     # - base data for the listing
     # - that extra field plus any previous
-    
+
     usedSpace = len(LABEL_FORMAT % tuple([""] * 4)) + LABEL_MIN_PADDING
     localPort = ":%s" % self.local.getPort() if self.includePort else ""
-    
+
     src, dst, etc = "", "", ""
     if listingType == entries.ListingType.IP_ADDRESS:
       myExternalIpAddr = conn.getInfo("address", self.local.getIpAddr())
       addrDiffer = myExternalIpAddr != self.local.getIpAddr()
-      
+
       # Expanding doesn't make sense, if the connection isn't actually
       # going through Tor's external IP address. As there isn't a known
       # method for checking if it is, we're checking the type instead.
@@ -565,47 +565,47 @@ class ConnectionLine(entries.ConnectionPanelLine):
       # This isn't entirely correct. It might be a better idea to check if
       # the source and destination addresses are both private, but that might
       # not be perfectly reliable either.
-      
+
       isExpansionType = not myType in (Category.SOCKS, Category.HIDDEN, Category.CONTROL)
-      
+
       if isExpansionType: srcAddress = myExternalIpAddr + localPort
       else: srcAddress = self.local.getIpAddr() + localPort
-      
+
       if myType in (Category.SOCKS, Category.CONTROL):
         # Like inbound connections these need their source and destination to
         # be swapped. However, this only applies when listing by IP or hostname
         # (their fingerprint and nickname are both for us). Reversing the
         # fields here to keep the same column alignments.
-        
+
         src = "%-21s" % dstAddress
         dst = "%-26s" % srcAddress
       else:
         src = "%-21s" % srcAddress # ip:port = max of 21 characters
         dst = "%-26s" % dstAddress # ip:port (xx) = max of 26 characters
-      
+
       usedSpace += len(src) + len(dst) # base data requires 47 characters
-      
+
       # Showing the fingerprint (which has the width of 42) has priority over
       # an expanded address field. Hence check if we either have space for
       # both or wouldn't be showing the fingerprint regardless.
-      
+
       isExpandedAddrVisible = width > usedSpace + 28
       if isExpandedAddrVisible and CONFIG["features.connection.showColumn.fingerprint"]:
         isExpandedAddrVisible = width < usedSpace + 42 or width > usedSpace + 70
-      
+
       if addrDiffer and isExpansionType and isExpandedAddrVisible and self.includeExpandedIpAddr and CONFIG["features.connection.showColumn.expandedIp"]:
         # include the internal address in the src (extra 28 characters)
         internalAddress = self.local.getIpAddr() + localPort
-        
+
         # If this is an inbound connection then reverse ordering so it's:
         # <foreign> --> <external> --> <internal>
         # when the src and dst are swapped later
-        
+
         if myType == Category.INBOUND: src = "%-21s  -->  %s" % (src, internalAddress)
         else: src = "%-21s  -->  %s" % (internalAddress, src)
-        
+
         usedSpace += 28
-      
+
       etc = self.getEtcContent(width - usedSpace, listingType)
       usedSpace += len(etc)
     elif listingType == entries.ListingType.HOSTNAME:
@@ -615,10 +615,10 @@ class ConnectionLine(entries.ConnectionPanelLine):
       src = "localhost%-6s" % localPort
       usedSpace += len(src)
       minHostnameSpace = 40
-      
+
       etc = self.getEtcContent(width - usedSpace - minHostnameSpace, listingType)
       usedSpace += len(etc)
-      
+
       hostnameSpace = width - usedSpace
       usedSpace = width # prevents padding at the end
       if self.isPrivate():
@@ -626,7 +626,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
       else:
         hostname = self.foreign.getHostname(self.foreign.getIpAddr())
         portLabel = ":%-5s" % self.foreign.getPort() if self.includePort else ""
-        
+
         # truncates long hostnames and sets dst to <hostname>:<port>
         hostname = uiTools.cropStr(hostname, hostnameSpace, 0)
         dst = ("%%-%is" % hostnameSpace) % (hostname + portLabel)
@@ -635,9 +635,9 @@ class ConnectionLine(entries.ConnectionPanelLine):
       if myType == Category.CONTROL: dst = "localhost"
       else: dst = self.foreign.getFingerprint()
       dst = "%-40s" % dst
-      
+
       usedSpace += len(src) + len(dst) # base data requires 49 characters
-      
+
       etc = self.getEtcContent(width - usedSpace, listingType)
       usedSpace += len(etc)
     else:
@@ -646,204 +646,204 @@ class ConnectionLine(entries.ConnectionPanelLine):
       if myType == Category.CONTROL: dst = self.local.getNickname()
       else: dst = self.foreign.getNickname()
       minBaseSpace = 50
-      
+
       etc = self.getEtcContent(width - usedSpace - minBaseSpace, listingType)
       usedSpace += len(etc)
-      
+
       baseSpace = width - usedSpace
       usedSpace = width # prevents padding at the end
-      
+
       if len(src) + len(dst) > baseSpace:
         src = uiTools.cropStr(src, baseSpace / 3)
         dst = uiTools.cropStr(dst, baseSpace - len(src))
-      
+
       # pads dst entry to its max space
       dst = ("%%-%is" % (baseSpace - len(src))) % dst
-    
+
     if myType == Category.INBOUND: src, dst = dst, src
     padding = " " * (width - usedSpace + LABEL_MIN_PADDING)
     return LABEL_FORMAT % (src, dst, etc, padding)
-  
+
   def _getDetailContent(self, width):
     """
     Provides a list with detailed information for this connection.
-    
+
     Arguments:
       width - max length of lines
     """
-    
+
     lines = [""] * 7
     lines[0] = "address: %s" % self.getDestinationLabel(width - 11)
     lines[1] = "locale: %s" % ("??" if self.isPrivate() else self.foreign.getLocale("??"))
-    
+
     # Remaining data concerns the consensus results, with three possible cases:
     # - if there's a single match then display its details
     # - if there's multiple potential relays then list all of the combinations
     #   of ORPorts / Fingerprints
     # - if no consensus data is available then say so (probably a client or
     #   exit connection)
-    
+
     fingerprint = self.foreign.getFingerprint()
     conn = torTools.getConn()
-    
+
     if fingerprint != "UNKNOWN":
       # single match - display information available about it
       nsEntry = conn.getConsensusEntry(fingerprint)
       descEntry = conn.getDescriptorEntry(fingerprint)
-      
+
       # append the fingerprint to the second line
       lines[1] = "%-13sfingerprint: %s" % (lines[1], fingerprint)
-      
+
       if nsEntry:
         # example consensus entry:
         # r murble R8sCM1ar1sS2GulQYFVmvN95xsk RJr6q+wkTFG+ng5v2bdCbVVFfA4 2011-02-21 00:25:32 195.43.157.85 443 0
         # s Exit Fast Guard Named Running Stable Valid
         # w Bandwidth=2540
         # p accept 20-23,43,53,79-81,88,110,143,194,443
-        
+
         nsLines = nsEntry.split("\n")
-        
+
         firstLineComp = nsLines[0].split(" ")
         if len(firstLineComp) >= 9:
           _, nickname, _, _, pubDate, pubTime, _, orPort, dirPort = firstLineComp[:9]
         else: nickname, pubDate, pubTime, orPort, dirPort = "", "", "", "", ""
-        
+
         flags = "unknown"
         if len(nsLines) >= 2 and nsLines[1].startswith("s "):
           flags = nsLines[1][2:]
-        
+
         exitPolicy = conn.getRelayExitPolicy(fingerprint)
-        
+
         if exitPolicy: policyLabel = exitPolicy.summary()
         else: policyLabel = "unknown"
-        
+
         dirPortLabel = "" if dirPort == "0" else "dirport: %s" % dirPort
         lines[2] = "nickname: %-25s orport: %-10s %s" % (nickname, orPort, dirPortLabel)
         lines[3] = "published: %s %s" % (pubTime, pubDate)
         lines[4] = "flags: %s" % flags.replace(" ", ", ")
         lines[5] = "exit policy: %s" % policyLabel
-      
+
       if descEntry:
         torVersion, platform, contact = "", "", ""
-        
+
         for descLine in descEntry.split("\n"):
           if descLine.startswith("platform"):
             # has the tor version and platform, ex:
             # platform Tor 0.2.1.29 (r318f470bc5f2ad43) on Linux x86_64
-            
+
             torVersion = descLine[13:descLine.find(" ", 13)]
             platform = descLine[descLine.rfind(" on ") + 4:]
           elif descLine.startswith("contact"):
             contact = descLine[8:]
-            
+
             # clears up some highly common obscuring
             for alias in (" at ", " AT "): contact = contact.replace(alias, "@")
             for alias in (" dot ", " DOT "): contact = contact.replace(alias, ".")
-            
+
             break # contact lines come after the platform
-        
+
         lines[3] = "%-35s os: %-14s version: %s" % (lines[3], platform, torVersion)
-        
+
         # contact information is an optional field
         if contact: lines[6] = "contact: %s" % contact
     else:
       allMatches = conn.getRelayFingerprint(self.foreign.getIpAddr(), getAllMatches = True)
-      
+
       if allMatches:
         # multiple matches
         lines[2] = "Multiple matches, possible fingerprints are:"
-        
+
         for i in range(len(allMatches)):
           isLastLine = i == 3
-          
+
           relayPort, relayFingerprint = allMatches[i]
           lineText = "%i. or port: %-5s fingerprint: %s" % (i, relayPort, relayFingerprint)
-          
+
           # if there's multiple lines remaining at the end then give a count
           remainingRelays = len(allMatches) - i
           if isLastLine and remainingRelays > 1:
             lineText = "... %i more" % remainingRelays
-          
+
           lines[3 + i] = lineText
-          
+
           if isLastLine: break
       else:
         # no consensus entry for this ip address
         lines[2] = "No consensus data found"
-    
+
     # crops any lines that are too long
     for i in range(len(lines)):
       lines[i] = uiTools.cropStr(lines[i], width - 2)
-    
+
     return lines
-  
+
   def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False):
     """
     Provides a short description of the destination. This is made up of two
     components, the base <ip addr>:<port> and an extra piece of information in
     parentheses. The IP address is scrubbed from private connections.
-    
+
     Extra information is...
     - the port's purpose for exit connections
     - the locale and/or hostname if set to do so, the address isn't private,
       and isn't on the local network
     - nothing otherwise
-    
+
     Arguments:
       maxLength       - maximum length of the string returned
       includeLocale   - possibly includes the locale
       includeHostname - possibly includes the hostname
     """
-    
+
     # the port and port derived data can be hidden by config or without includePort
     includePort = self.includePort and (CONFIG["features.connection.showExitPort"] or self.getType() != Category.EXIT)
-    
+
     # destination of the connection
     ipLabel = "<scrubbed>" if self.isPrivate() else self.foreign.getIpAddr()
     portLabel = ":%s" % self.foreign.getPort() if includePort else ""
     dstAddress = ipLabel + portLabel
-    
+
     # Only append the extra info if there's at least a couple characters of
     # space (this is what's needed for the country codes).
     if len(dstAddress) + 5 <= maxLength:
       spaceAvailable = maxLength - len(dstAddress) - 3
-      
+
       if self.getType() == Category.EXIT and includePort:
         purpose = connections.getPortUsage(self.foreign.getPort())
-        
+
         if purpose:
           # BitTorrent is a common protocol to truncate, so just use "Torrent"
           # if there's not enough room.
           if len(purpose) > spaceAvailable and purpose == "BitTorrent":
             purpose = "Torrent"
-          
+
           # crops with a hyphen if too long
           purpose = uiTools.cropStr(purpose, spaceAvailable, endType = uiTools.Ending.HYPHEN)
-          
+
           dstAddress += " (%s)" % purpose
       elif not connections.isIpAddressPrivate(self.foreign.getIpAddr()):
         extraInfo = []
         conn = torTools.getConn()
-        
+
         if includeLocale and not conn.isGeoipUnavailable():
           foreignLocale = self.foreign.getLocale("??")
           extraInfo.append(foreignLocale)
           spaceAvailable -= len(foreignLocale) + 2
-        
+
         if includeHostname:
           dstHostname = self.foreign.getHostname()
-          
+
           if dstHostname:
             # determines the full space available, taking into account the ", "
             # dividers if there's multiple pieces of extra data
-            
+
             maxHostnameSpace = spaceAvailable - 2 * len(extraInfo)
             dstHostname = uiTools.cropStr(dstHostname, maxHostnameSpace)
             extraInfo.append(dstHostname)
             spaceAvailable -= len(dstHostname)
-        
+
         if extraInfo:
           dstAddress += " (%s)" % ", ".join(extraInfo)
-    
+
     return dstAddress[:maxLength]
 
diff --git a/arm/connections/connPanel.py b/arm/connections/connPanel.py
index 6dec45a..7d7864e 100644
--- a/arm/connections/connPanel.py
+++ b/arm/connections/connPanel.py
@@ -45,176 +45,176 @@ class ConnectionPanel(panel.Panel, threading.Thread):
   Listing of connections tor is making, with information correlated against
   the current consensus and other data sources.
   """
-  
+
   def __init__(self, stdscr):
     panel.Panel.__init__(self, stdscr, "connections", 0)
     threading.Thread.__init__(self)
     self.setDaemon(True)
-    
+
     # defaults our listing selection to fingerprints if ip address
     # displaying is disabled
     #
     # TODO: This is a little sucky in that it won't work if showIps changes
     # while we're running (... but arm doesn't allow for that atm)
-    
+
     if not CONFIG["features.connection.showIps"] and CONFIG["features.connection.listingType"] == 0:
       armConf = conf.get_config("arm")
       armConf.set("features.connection.listingType", enumeration.keys()[Listing.index_of(Listing.FINGERPRINT)])
-    
+
     self._scroller = uiTools.Scroller(True)
     self._title = "Connections:" # title line of the panel
     self._entries = []          # last fetched display entries
     self._entryLines = []       # individual lines rendered from the entries listing
     self._showDetails = False   # presents the details panel if true
-    
+
     self._lastUpdate = -1       # time the content was last revised
     self._isTorRunning = True   # indicates if tor is currently running or not
     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()
-    
+
     # Tracks exiting port and client country statistics
     self._clientLocaleUsage = {}
     self._exitPortUsage = {}
-    
+
     # If we're a bridge and been running over a day then prepopulates with the
     # last day's clients.
-    
+
     conn = torTools.getConn()
     bridgeClients = conn.getInfo("status/clients-seen", None)
-    
+
     if bridgeClients:
       # Response has a couple arguments...
       # TimeStarted="2011-08-17 15:50:49" CountrySummary=us=16,de=8,uk=8
-      
+
       countrySummary = None
       for arg in bridgeClients.split():
         if arg.startswith("CountrySummary="):
           countrySummary = arg[15:]
           break
-      
+
       if countrySummary:
         for entry in countrySummary.split(","):
           if re.match("^..=[0-9]+$", entry):
             locale, count = entry.split("=", 1)
             self._clientLocaleUsage[locale] = int(count)
-    
+
     # Last sampling received from the ConnectionResolver, used to detect when
     # it changes.
     self._lastResourceFetch = -1
-    
+
     # resolver for the command/pid associated with SOCKS, HIDDEN, and CONTROL connections
     self._appResolver = connections.AppResolver("arm")
-    
+
     # rate limits appResolver queries to once per update
     self.appResolveSinceUpdate = False
-    
+
     # mark the initially exitsing connection uptimes as being estimates
     for entry in self._entries:
       if isinstance(entry, connEntry.ConnectionEntry):
         entry.getLines()[0].isInitialConnection = True
-    
+
     # listens for when tor stops so we know to stop reflecting changes
     conn.addStatusListener(self.torStateListener)
-  
+
   def torStateListener(self, controller, eventType, _):
     """
     Freezes the connection contents when Tor stops.
     """
-    
+
     self._isTorRunning = eventType in (State.INIT, State.RESET)
-    
+
     if self._isTorRunning: self._haltTime = None
     else: self._haltTime = time.time()
-    
+
     self.redraw(True)
-  
+
   def getPauseTime(self):
     """
     Provides the time Tor stopped if it isn't running. Otherwise this is the
     time we were last paused.
     """
-    
+
     if self._haltTime: return self._haltTime
     else: return panel.Panel.getPauseTime(self)
-  
+
   def setSortOrder(self, ordering = None):
     """
     Sets the connection attributes we're sorting by and resorts the contents.
-    
+
     Arguments:
       ordering - new ordering, if undefined then this resorts with the last
                  set ordering
     """
-    
+
     self.valsLock.acquire()
-    
+
     if ordering:
       armConf = conf.get_config("arm")
-      
+
       ordering_keys = [entries.SortAttr.keys()[entries.SortAttr.index_of(v)] for v in ordering]
       armConf.set("features.connection.order", ", ".join(ordering_keys))
-    
+
     self._entries.sort(key=lambda i: (i.getSortValues(CONFIG["features.connection.order"], self.getListingType())))
-    
+
     self._entryLines = []
     for entry in self._entries:
       self._entryLines += entry.getLines()
     self.valsLock.release()
-  
+
   def getListingType(self):
     """
     Provides the priority content we list connections by.
     """
-    
+
     return CONFIG["features.connection.listingType"]
-  
+
   def setListingType(self, listingType):
     """
     Sets the priority information presented by the panel.
-    
+
     Arguments:
       listingType - Listing instance for the primary information to be shown
     """
-    
+
     if self.getListingType() == listingType: return
-    
+
     self.valsLock.acquire()
-    
+
     armConf = conf.get_config("arm")
     armConf.set("features.connection.listingType", Listing.keys()[Listing.index_of(listingType)])
-    
+
     # if we're sorting by the listing then we need to resort
     if entries.SortAttr.LISTING in CONFIG["features.connection.order"]:
       self.setSortOrder()
-    
+
     self.valsLock.release()
-  
+
   def isClientsAllowed(self):
     """
     True if client connections are permissable, false otherwise.
     """
-    
+
     conn = torTools.getConn()
     return "Guard" in conn.getMyFlags([]) or conn.getOption("BridgeRelay", None) == "1"
-  
+
   def isExitsAllowed(self):
     """
     True if exit connections are permissable, false otherwise.
     """
-    
+
     if not torTools.getConn().getOption("ORPort", None):
       return False # no ORPort
-    
+
     policy = torTools.getConn().getExitPolicy()
     return policy and policy.is_exiting_allowed()
-  
+
   def showSortDialog(self):
     """
     Provides the sort dialog for our connections.
     """
-    
+
     # set ordering for connection options
     titleLabel = "Connection Ordering:"
     options = list(entries.SortAttr)
@@ -222,10 +222,10 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     optionColors = dict([(attr, entries.SORT_COLORS[attr]) for attr in options])
     results = arm.popups.showSortDialog(titleLabel, options, oldSelection, optionColors)
     if results: self.setSortOrder(results)
-  
+
   def handleKey(self, key):
     self.valsLock.acquire()
-    
+
     isKeystrokeConsumed = True
     if uiTools.isScrollKey(key):
       pageHeight = self.getPreferredSize()[0] - 1
@@ -242,13 +242,13 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       title = "Resolver Util:"
       options = ["auto"] + list(connections.Resolver)
       connResolver = connections.getResolver("tor")
-      
+
       currentOverwrite = connResolver.overwriteResolver
       if currentOverwrite == None: oldSelection = 0
       else: oldSelection = options.index(currentOverwrite)
-      
+
       selection = arm.popups.showMenu(title, options, oldSelection)
-      
+
       # applies new setting
       if selection != -1:
         selectedOption = options[selection] if selection != 0 else None
@@ -257,13 +257,13 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       # provides a menu to pick the primary information we list connections by
       title = "List By:"
       options = list(entries.ListingType)
-      
+
       # dropping the HOSTNAME listing type until we support displaying that content
       options.remove(arm.connections.entries.ListingType.HOSTNAME)
-      
+
       oldSelection = options.index(self.getListingType())
       selection = arm.popups.showMenu(title, options, oldSelection)
-      
+
       # applies new setting
       if selection != -1: self.setListingType(options[selection])
     elif key == ord('d') or key == ord('D'):
@@ -274,17 +274,17 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     elif (key == ord('e') or key == ord('E')) and self.isExitsAllowed():
       countPopup.showCountDialog(countPopup.CountType.EXIT_PORT, self._exitPortUsage)
     else: isKeystrokeConsumed = False
-    
+
     self.valsLock.release()
     return isKeystrokeConsumed
-  
+
   def run(self):
     """
     Keeps connections listing updated, checking for new entries at a set rate.
     """
-    
+
     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).
@@ -293,10 +293,10 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     self._cond.release()
     self._update()            # populates initial entries
     self._resolveApps(False)  # resolves initial applications
-    
+
     while not self._halt:
       currentTime = time.time()
-      
+
       if self.isPaused() or not self._isTorRunning or currentTime - lastDraw < CONFIG["features.connection.refreshRate"]:
         self._cond.acquire()
         if not self._halt: self._cond.wait(0.2)
@@ -305,16 +305,16 @@ class ConnectionPanel(panel.Panel, threading.Thread):
         # updates content if their's new results, otherwise just redraws
         self._update()
         self.redraw(True)
-        
+
         # we may have missed multiple updates due to being paused, showing
         # another panel, etc so lastDraw might need to jump multiple ticks
         drawTicks = (time.time() - lastDraw) / CONFIG["features.connection.refreshRate"]
         lastDraw += CONFIG["features.connection.refreshRate"] * drawTicks
-  
+
   def getHelp(self):
     resolverUtil = connections.getResolver("tor").overwriteResolver
     if resolverUtil == None: resolverUtil = "auto"
-    
+
     options = []
     options.append(("up arrow", "scroll up a line", None))
     options.append(("down arrow", "scroll down a line", None))
@@ -322,141 +322,141 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     options.append(("page down", "scroll down a page", None))
     options.append(("enter", "show connection details", None))
     options.append(("d", "raw consensus descriptor", None))
-    
+
     if self.isClientsAllowed():
       options.append(("c", "client locale usage summary", None))
-    
+
     if self.isExitsAllowed():
       options.append(("e", "exit port usage summary", None))
-    
+
     options.append(("l", "listed identity", self.getListingType().lower()))
     options.append(("s", "sort ordering", None))
     options.append(("u", "resolving utility", resolverUtil))
     return options
-  
+
   def getSelection(self):
     """
     Provides the currently selected connection entry.
     """
-    
+
     return self._scroller.getCursorSelection(self._entryLines)
-  
+
   def draw(self, width, height):
     self.valsLock.acquire()
-    
+
     # if we don't have any contents then refuse to show details
     if not self._entries: self._showDetails = False
-    
+
     # extra line when showing the detail panel is for the bottom border
     detailPanelOffset = DETAILS_HEIGHT + 1 if self._showDetails else 0
     isScrollbarVisible = len(self._entryLines) > height - detailPanelOffset - 1
-    
+
     scrollLoc = self._scroller.getScrollLoc(self._entryLines, height - detailPanelOffset - 1)
     cursorSelection = self.getSelection()
-    
+
     # draws the detail panel if currently displaying it
     if self._showDetails and cursorSelection:
       # This is a solid border unless the scrollbar is visible, in which case a
       # 'T' pipe connects the border to the bar.
       uiTools.drawBox(self, 0, 0, width, DETAILS_HEIGHT + 2)
       if isScrollbarVisible: self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
-      
+
       drawEntries = cursorSelection.getDetails(width)
       for i in range(min(len(drawEntries), DETAILS_HEIGHT)):
         self.addstr(1 + i, 2, drawEntries[i][0], drawEntries[i][1])
-    
+
     # title label with connection counts
     if self.isTitleVisible():
       title = "Connection Details:" if self._showDetails else self._title
       self.addstr(0, 0, title, curses.A_STANDOUT)
-    
+
     scrollOffset = 0
     if isScrollbarVisible:
       scrollOffset = 2
       self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._entryLines), 1 + detailPanelOffset)
-    
+
     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]
-      
+
       # if this is an unresolved SOCKS, HIDDEN, or CONTROL entry then queue up
       # resolution for the applicaitions they belong to
       if isinstance(entryLine, connEntry.ConnectionLine) and entryLine.isUnresolvedApp():
         self._resolveApps()
-      
+
       # hilighting if this is the selected line
       extraFormat = curses.A_STANDOUT if entryLine == cursorSelection else curses.A_NORMAL
-      
+
       drawLine = lineNum + detailPanelOffset + 1 - scrollLoc
-      
+
       prefix = entryLine.getListingPrefix()
       for i in range(len(prefix)):
         self.addch(drawLine, scrollOffset + i, prefix[i])
-      
+
       xOffset = scrollOffset + len(prefix)
       drawEntry = entryLine.getListingEntry(width - scrollOffset - len(prefix), currentTime, self.getListingType())
-      
+
       for msg, attr in drawEntry:
         attr |= extraFormat
         self.addstr(drawLine, xOffset, msg, attr)
         xOffset += len(msg)
-      
+
       if drawLine >= height: break
-    
+
     self.valsLock.release()
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self._cond.acquire()
     self._halt = True
     self._cond.notifyAll()
     self._cond.release()
-  
+
   def _update(self):
     """
     Fetches the newest resolved connections.
     """
-    
+
     self.appResolveSinceUpdate = False
-    
+
     # if we don't have an initialized resolver then this is a no-op
     if not connections.isResolverAlive("tor"): return
-    
+
     connResolver = connections.getResolver("tor")
     currentResolutionCount = connResolver.getResolutionCount()
-    
+
     self.valsLock.acquire()
-    
+
     newEntries = [] # the new results we'll display
-    
+
     # Fetches new connections and client circuits...
     # newConnections  [(local ip, local port, foreign ip, foreign port)...]
     # newCircuits     {circuitID => (status, purpose, path)...}
-    
+
     newConnections = connResolver.getConnections()
     newCircuits = {}
-    
+
     for circuitID, status, purpose, path in torTools.getConn().getCircuits():
       # Skips established single-hop circuits (these are for directory
       # fetches, not client circuits)
       if not (status == "BUILT" and len(path) == 1):
         newCircuits[circuitID] = (status, purpose, path)
-    
+
     # Populates newEntries with any of our old entries that still exist.
     # This is both for performance and to keep from resetting the uptime
     # attributes. Note that CircEntries are a ConnectionEntry subclass so
     # we need to check for them first.
-    
+
     for oldEntry in self._entries:
       if isinstance(oldEntry, circEntry.CircEntry):
         newEntry = newCircuits.get(oldEntry.circuitID)
-        
+
         if newEntry:
           oldEntry.update(newEntry[0], newEntry[2])
           newEntries.append(oldEntry)
@@ -465,42 +465,42 @@ class ConnectionPanel(panel.Panel, threading.Thread):
         connLine = oldEntry.getLines()[0]
         connAttr = (connLine.local.getIpAddr(), connLine.local.getPort(),
                     connLine.foreign.getIpAddr(), connLine.foreign.getPort())
-        
+
         if connAttr in newConnections:
           newEntries.append(oldEntry)
           newConnections.remove(connAttr)
-    
+
     # Reset any display attributes for the entries we're keeping
     for entry in newEntries: entry.resetDisplay()
-    
+
     # Adds any new connection and circuit entries.
     for lIp, lPort, fIp, fPort in newConnections:
       newConnEntry = connEntry.ConnectionEntry(lIp, lPort, fIp, fPort)
       newConnLine = newConnEntry.getLines()[0]
-      
+
       if newConnLine.getType() != connEntry.Category.CIRCUIT:
         newEntries.append(newConnEntry)
-        
+
         # updates exit port and client locale usage information
         if newConnLine.isPrivate():
           if newConnLine.getType() == connEntry.Category.INBOUND:
             # client connection, update locale information
             clientLocale = newConnLine.foreign.getLocale()
-            
+
             if clientLocale:
               self._clientLocaleUsage[clientLocale] = self._clientLocaleUsage.get(clientLocale, 0) + 1
           elif newConnLine.getType() == connEntry.Category.EXIT:
             exitPort = newConnLine.foreign.getPort()
             self._exitPortUsage[exitPort] = self._exitPortUsage.get(exitPort, 0) + 1
-    
+
     for circuitID in newCircuits:
       status, purpose, path = newCircuits[circuitID]
       newEntries.append(circEntry.CircEntry(circuitID, status, purpose, path))
-    
+
     # Counts the relays in each of the categories. This also flushes the
     # type cache for all of the connections (in case its changed since last
     # fetched).
-    
+
     categoryTypes = list(connEntry.Category)
     typeCounts = dict((type, 0) for type in categoryTypes)
     for entry in newEntries:
@@ -508,53 +508,53 @@ class ConnectionPanel(panel.Panel, threading.Thread):
         typeCounts[entry.getLines()[0].getType()] += 1
       elif isinstance(entry, circEntry.CircEntry):
         typeCounts[connEntry.Category.CIRCUIT] += 1
-    
+
     # makes labels for all the categories with connections (ie,
     # "21 outbound", "1 control", etc)
     countLabels = []
-    
+
     for category in categoryTypes:
       if typeCounts[category] > 0:
         countLabels.append("%i %s" % (typeCounts[category], category.lower()))
-    
+
     if countLabels: self._title = "Connections (%s):" % ", ".join(countLabels)
     else: self._title = "Connections:"
-    
+
     self._entries = newEntries
-    
+
     self._entryLines = []
     for entry in self._entries:
       self._entryLines += entry.getLines()
-    
+
     self.setSortOrder()
     self._lastResourceFetch = currentResolutionCount
     self.valsLock.release()
-  
+
   def _resolveApps(self, flagQuery = True):
     """
     Triggers an asynchronous query for all unresolved SOCKS, HIDDEN, and
     CONTROL entries.
-    
+
     Arguments:
       flagQuery - sets a flag to prevent further call from being respected
                   until the next update if true
     """
-    
+
     if self.appResolveSinceUpdate or not CONFIG["features.connection.resolveApps"]: return
     unresolvedLines = [l for l in self._entryLines if isinstance(l, connEntry.ConnectionLine) and l.isUnresolvedApp()]
-    
+
     # get the ports used for unresolved applications
     appPorts = []
-    
+
     for line in unresolvedLines:
       appConn = line.local if line.getType() == connEntry.Category.HIDDEN else line.foreign
       appPorts.append(appConn.getPort())
-    
+
     # Queue up resolution for the unresolved ports (skips if it's still working
     # on the last query).
     if appPorts and not self._appResolver.isResolving:
       self._appResolver.resolve(appPorts)
-    
+
     # Fetches results. If the query finishes quickly then this is what we just
     # asked for, otherwise these belong to an earlier resolution.
     #
@@ -562,26 +562,26 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     # the lsof lookups aren't working on this platform or lacks permissions).
     # The isAppResolving flag lets the unresolved entries indicate if there's
     # a lookup in progress for them or not.
-    
+
     appResults = self._appResolver.getResults(0.2)
-    
+
     for line in unresolvedLines:
       isLocal = line.getType() == connEntry.Category.HIDDEN
       linePort = line.local.getPort() if isLocal else line.foreign.getPort()
-      
+
       if linePort in appResults:
         # sets application attributes if there's a result with this as the
         # inbound port
         for inboundPort, outboundPort, cmd, pid in appResults[linePort]:
           appPort = outboundPort if isLocal else inboundPort
-          
+
           if linePort == appPort:
             line.appName = cmd
             line.appPid = pid
             line.isAppResolving = False
       else:
         line.isAppResolving = self._appResolver.isResolving
-    
+
     if flagQuery:
       self.appResolveSinceUpdate = True
 
diff --git a/arm/connections/countPopup.py b/arm/connections/countPopup.py
index 34a779e..0c6d14a 100644
--- a/arm/connections/countPopup.py
+++ b/arm/connections/countPopup.py
@@ -19,26 +19,26 @@ def showCountDialog(countType, counts):
   """
   Provides a dialog with bar graphs and percentages for the given set of
   counts. Pressing any key closes the dialog.
-  
+
   Arguments:
     countType - type of counts being presented
     counts    - mapping of labels to counts
   """
-  
+
   isNoStats = not counts
   noStatsMsg = "Usage stats aren't available yet, press any key..."
-  
+
   if isNoStats:
     popup, width, height = arm.popups.init(3, len(noStatsMsg) + 4)
   else:
     popup, width, height = arm.popups.init(4 + max(1, len(counts)), 80)
   if not popup: return
-  
+
   try:
     control = arm.controller.getController()
-    
+
     popup.win.box()
-    
+
     # dialog title
     if countType == CountType.CLIENT_LOCALE:
       title = "Client Locales"
@@ -47,55 +47,55 @@ def showCountDialog(countType, counts):
     else:
       title = ""
       log.warn("Unrecognized count type: %s" % countType)
-    
+
     popup.addstr(0, 0, title, curses.A_STANDOUT)
-    
+
     if isNoStats:
       popup.addstr(1, 2, noStatsMsg, curses.A_BOLD | uiTools.getColor("cyan"))
     else:
       sortedCounts = sorted(counts.iteritems(), key=operator.itemgetter(1))
       sortedCounts.reverse()
-      
+
       # constructs string formatting for the max key and value display width
       keyWidth, valWidth, valueTotal = 3, 1, 0
       for k, v in sortedCounts:
         keyWidth = max(keyWidth, len(k))
         valWidth = max(valWidth, len(str(v)))
         valueTotal += v
-      
+
       # extra space since we're adding usage informaion
       if countType == CountType.EXIT_PORT:
         keyWidth += EXIT_USAGE_WIDTH
-      
+
       labelFormat = "%%-%is %%%ii (%%%%%%-2i)" % (keyWidth, valWidth)
-      
+
       for i in range(height - 4):
         k, v = sortedCounts[i]
-        
+
         # includes a port usage column
         if countType == CountType.EXIT_PORT:
           usage = connections.getPortUsage(k)
-          
+
           if usage:
             keyFormat = "%%-%is   %%s" % (keyWidth - EXIT_USAGE_WIDTH)
             k = keyFormat % (k, usage[:EXIT_USAGE_WIDTH - 3])
-        
+
         label = labelFormat % (k, v, v * 100 / valueTotal)
         popup.addstr(i + 1, 2, label, curses.A_BOLD | uiTools.getColor("green"))
-        
+
         # All labels have the same size since they're based on the max widths.
         # If this changes then this'll need to be the max label width.
         labelWidth = len(label)
-        
+
         # draws simple bar graph for percentages
         fillWidth = v * (width - 4 - labelWidth) / valueTotal
         for j in range(fillWidth):
           popup.addstr(i + 1, 3 + labelWidth + j, " ", curses.A_STANDOUT | uiTools.getColor("red"))
-      
+
       popup.addstr(height - 2, 2, "Press any key...")
-    
+
     popup.win.refresh()
-    
+
     curses.cbreak()
     control.getScreen().getch()
   finally: arm.popups.finalize()
diff --git a/arm/connections/descriptorPopup.py b/arm/connections/descriptorPopup.py
index a156280..d4ab918 100644
--- a/arm/connections/descriptorPopup.py
+++ b/arm/connections/descriptorPopup.py
@@ -28,46 +28,46 @@ def showDescriptorPopup(connPanel):
   Up, Down, Page Up, Page Down - scroll descriptor
   Right, Left - next / previous connection
   Enter, Space, d, D - close popup
-  
+
   Arguments:
     connPanel - connection panel providing the dialog
   """
-  
+
   # hides the title of the connection panel
   connPanel.setTitleVisible(False)
   connPanel.redraw(True)
-  
+
   control = arm.controller.getController()
   panel.CURSES_LOCK.acquire()
   isDone = False
-  
+
   try:
     while not isDone:
       selection = connPanel.getSelection()
       if not selection: break
-      
+
       fingerprint = selection.foreign.getFingerprint()
       if fingerprint == "UNKNOWN": fingerprint = None
-      
+
       displayText = getDisplayText(fingerprint)
       displayColor = arm.connections.connEntry.CATEGORY_COLOR[selection.getType()]
       showLineNumber = fingerprint != None
-      
+
       # determines the maximum popup size the displayText can fill
       pHeight, pWidth = getPreferredSize(displayText, connPanel.maxX, showLineNumber)
-      
+
       popup, _, height = arm.popups.init(pHeight, pWidth)
       if not popup: break
       scroll, isChanged = 0, True
-      
+
       try:
         while not isDone:
           if isChanged:
             draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber)
             isChanged = False
-          
+
           key = control.getScreen().getch()
-          
+
           if uiTools.isScrollKey(key):
             # TODO: This is a bit buggy in that scrolling is by displayText
             # lines rather than the displayed lines, causing issues when
@@ -76,9 +76,9 @@ def showDescriptorPopup(connPanel):
             # displayed. However, trying to correct this introduces a big can
             # of worms and after hours decided that this isn't worth the
             # effort...
-            
+
             newScroll = uiTools.getScrollPosition(key, scroll, height - 2, len(displayText))
-            
+
             if scroll != newScroll:
               scroll, isChanged = newScroll, True
           elif uiTools.isSelectionKey(key) or key in (ord('d'), ord('D')):
@@ -98,29 +98,29 @@ def getDisplayText(fingerprint):
   Provides the descriptor and consensus entry for a relay. This is a list of
   lines to be displayed by the dialog.
   """
-  
+
   if not fingerprint: return [UNRESOLVED_MSG]
   conn, description = torTools.getConn(), []
-  
+
   description.append("ns/id/%s" % fingerprint)
   consensusEntry = conn.getConsensusEntry(fingerprint)
-  
+
   if consensusEntry: description += consensusEntry.split("\n")
   else: description += [ERROR_MSG, ""]
-  
+
   description.append("desc/id/%s" % fingerprint)
   descriptorEntry = conn.getDescriptorEntry(fingerprint)
-  
+
   if descriptorEntry: description += descriptorEntry.split("\n")
   else: description += [ERROR_MSG]
-  
+
   return description
 
 def getPreferredSize(text, maxWidth, showLineNumber):
   """
   Provides the (height, width) tuple for the preferred size of the given text.
   """
-  
+
   width, height = 0, len(text) + 2
   lineNumWidth = int(math.log10(len(text))) + 1
   for line in text:
@@ -128,48 +128,48 @@ def getPreferredSize(text, maxWidth, showLineNumber):
     lineWidth = len(line) + 5
     if showLineNumber: lineWidth += lineNumWidth
     width = max(width, lineWidth)
-    
+
     # tracks number of extra lines that will be taken due to text wrap
     height += (lineWidth - 2) / maxWidth
-  
+
   return (height, width)
 
 def draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber):
   popup.win.erase()
   popup.win.box()
   xOffset = 2
-  
+
   if fingerprint: title = "Consensus Descriptor (%s):" % fingerprint
   else: title = "Consensus Descriptor:"
   popup.addstr(0, 0, title, curses.A_STANDOUT)
-  
+
   lineNumWidth = int(math.log10(len(displayText))) + 1
   isEncryptionBlock = False   # flag indicating if we're currently displaying a key
-  
+
   # checks if first line is in an encryption block
   for i in range(0, scroll):
     lineText = displayText[i].strip()
     if lineText in SIG_START_KEYS: isEncryptionBlock = True
     elif lineText in SIG_END_KEYS: isEncryptionBlock = False
-  
+
   drawLine, pageHeight = 1, popup.maxY - 2
   for i in range(scroll, scroll + pageHeight):
     lineText = displayText[i].strip()
     xOffset = 2
-    
+
     if showLineNumber:
       lineNumLabel = ("%%%ii" % lineNumWidth) % (i + 1)
       lineNumFormat = curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR)
-      
+
       popup.addstr(drawLine, xOffset, lineNumLabel, lineNumFormat)
       xOffset += lineNumWidth + 1
-    
+
     # Most consensus and descriptor lines are keyword/value pairs. Both are
     # shown with the same color, but the keyword is bolded.
-    
+
     keyword, value = lineText, ""
     drawFormat = uiTools.getColor(displayColor)
-    
+
     if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]):
       keyword, value = lineText, ""
       drawFormat = uiTools.getColor(HEADER_COLOR)
@@ -189,41 +189,41 @@ def draw(popup, fingerprint, displayText, displayColor, scroll, showLineNumber):
     elif " " in lineText:
       divIndex = lineText.find(" ")
       keyword, value = lineText[:divIndex], lineText[divIndex:]
-    
+
     displayQueue = [(keyword, drawFormat | curses.A_BOLD), (value, drawFormat)]
     cursorLoc = xOffset
-    
+
     while displayQueue:
       msg, format = displayQueue.pop(0)
       if not msg: continue
-      
+
       maxMsgSize = popup.maxX - 1 - cursorLoc
       if len(msg) >= maxMsgSize:
         # needs to split up the line
         msg, remainder = uiTools.cropStr(msg, maxMsgSize, None, endType = None, getRemainder = True)
-        
+
         if xOffset == cursorLoc and msg == "":
           # first word is longer than the line
           msg = uiTools.cropStr(remainder, maxMsgSize)
-          
+
           if " " in remainder:
             remainder = remainder.split(" ", 1)[1]
           else: remainder = ""
-        
+
         popup.addstr(drawLine, cursorLoc, msg, format)
         cursorLoc = xOffset
-        
+
         if remainder:
           displayQueue.insert(0, (remainder.strip(), format))
           drawLine += 1
       else:
         popup.addstr(drawLine, cursorLoc, msg, format)
         cursorLoc += len(msg)
-      
+
       if drawLine > pageHeight: break
-    
+
     drawLine += 1
     if drawLine > pageHeight: break
-  
+
   popup.win.refresh()
 
diff --git a/arm/connections/entries.py b/arm/connections/entries.py
index d5085aa..bf319d8 100644
--- a/arm/connections/entries.py
+++ b/arm/connections/entries.py
@@ -27,53 +27,53 @@ class ConnectionPanelEntry:
   in the panel listing. This caches results until the display indicates that
   they should be flushed.
   """
-  
+
   def __init__(self):
     self.lines = []
     self.flushCache = True
-  
+
   def getLines(self):
     """
     Provides the individual lines in the connection listing.
     """
-    
+
     if self.flushCache:
       self.lines = self._getLines(self.lines)
       self.flushCache = False
-    
+
     return self.lines
-  
+
   def _getLines(self, oldResults):
     # implementation of getLines
-    
+
     for line in oldResults:
       line.resetDisplay()
-    
+
     return oldResults
-  
+
   def getSortValues(self, sortAttrs, listingType):
     """
     Provides the value used in comparisons to sort based on the given
     attribute.
-    
+
     Arguments:
       sortAttrs   - list of SortAttr values for the field being sorted on
       listingType - ListingType enumeration for the attribute we're listing
                     entries by
     """
-    
+
     return [self.getSortValue(attr, listingType) for attr in sortAttrs]
-  
+
   def getSortValue(self, attr, listingType):
     """
     Provides the value of a single attribute used for sorting purposes.
-    
+
     Arguments:
       attr        - list of SortAttr values for the field being sorted on
       listingType - ListingType enumeration for the attribute we're listing
                     entries by
     """
-    
+
     if attr == SortAttr.LISTING:
       if listingType == ListingType.IP_ADDRESS:
         # uses the IP address as the primary value, and port as secondary
@@ -86,44 +86,44 @@ class ConnectionPanelEntry:
         return self.getSortValue(SortAttr.FINGERPRINT, listingType)
       elif listingType == ListingType.NICKNAME:
         return self.getSortValue(SortAttr.NICKNAME, listingType)
-    
+
     return ""
-  
+
   def resetDisplay(self):
     """
     Flushes cached display results.
     """
-    
+
     self.flushCache = True
 
 class ConnectionPanelLine:
   """
   Individual line in the connection panel listing.
   """
-  
+
   def __init__(self):
     # cache for displayed information
     self._listingCache = None
     self._listingCacheArgs = (None, None)
-    
+
     self._detailsCache = None
     self._detailsCacheArgs = None
-    
+
     self._descriptorCache = None
     self._descriptorCacheArgs = None
-  
+
   def getListingPrefix(self):
     """
     Provides a list of characters to be appended before the listing entry.
     """
-    
+
     return ()
-  
+
   def getListingEntry(self, width, currentTime, listingType):
     """
     Provides a [(msg, attr)...] tuple list for contents to be displayed in the
     connection panel listing.
-    
+
     Arguments:
       width       - available space to display in
       currentTime - unix timestamp for what the results should consider to be
@@ -131,41 +131,41 @@ class ConnectionPanelLine:
       listingType - ListingType enumeration for the highest priority content
                     to be displayed
     """
-    
+
     if self._listingCacheArgs != (width, listingType):
       self._listingCache = self._getListingEntry(width, currentTime, listingType)
       self._listingCacheArgs = (width, listingType)
-    
+
     return self._listingCache
-  
+
   def _getListingEntry(self, width, currentTime, listingType):
     # implementation of getListingEntry
     return None
-  
+
   def getDetails(self, width):
     """
     Provides a list of [(msg, attr)...] tuple listings with detailed
     information for this connection.
-    
+
     Arguments:
       width - available space to display in
     """
-    
+
     if self._detailsCacheArgs != width:
       self._detailsCache = self._getDetails(width)
       self._detailsCacheArgs = width
-    
+
     return self._detailsCache
-  
+
   def _getDetails(self, width):
     # implementation of getDetails
     return []
-  
+
   def resetDisplay(self):
     """
     Flushes cached display results.
     """
-    
+
     self._listingCacheArgs = (None, None)
     self._detailsCacheArgs = None
 
diff --git a/arm/controller.py b/arm/controller.py
index a72c634..2fa0027 100644
--- a/arm/controller.py
+++ b/arm/controller.py
@@ -60,52 +60,52 @@ def getController():
   """
   Provides the arm controller instance.
   """
-  
+
   return ARM_CONTROLLER
 
 def initController(stdscr, startTime):
   """
   Spawns the controller, and related panels for it.
-  
+
   Arguments:
     stdscr - curses window
   """
-  
+
   global ARM_CONTROLLER
-  
+
   # initializes the panels
   stickyPanels = [arm.headerPanel.HeaderPanel(stdscr, startTime),
                   LabelPanel(stdscr)]
   pagePanels, firstPagePanels = [], []
-  
+
   # first page: graph and log
   if CONFIG["features.panels.show.graph"]:
     firstPagePanels.append(arm.graphing.graphPanel.GraphPanel(stdscr))
-  
+
   if CONFIG["features.panels.show.log"]:
     expandedEvents = arm.logPanel.expandEvents(CONFIG["startup.events"])
     firstPagePanels.append(arm.logPanel.LogPanel(stdscr, expandedEvents))
-  
+
   if firstPagePanels: pagePanels.append(firstPagePanels)
-  
+
   # second page: connections
   if not CONFIG["startup.blindModeEnabled"] and CONFIG["features.panels.show.connection"]:
     pagePanels.append([arm.connections.connPanel.ConnectionPanel(stdscr)])
-  
+
   # third page: config
   if CONFIG["features.panels.show.config"]:
     pagePanels.append([arm.configPanel.ConfigPanel(stdscr, arm.configPanel.State.TOR)])
-  
+
   # fourth page: torrc
   if CONFIG["features.panels.show.torrc"]:
     pagePanels.append([arm.torrcPanel.TorrcPanel(stdscr, arm.torrcPanel.Config.TORRC)])
-  
+
   # initializes the controller
   ARM_CONTROLLER = Controller(stdscr, stickyPanels, pagePanels)
-  
+
   # additional configuration for the graph panel
   graphPanel = ARM_CONTROLLER.getPanel("graph")
-  
+
   if graphPanel:
     # statistical monitors for graph
     bwStats = arm.graphing.bandwidthStats.BandwidthStats()
@@ -113,13 +113,13 @@ def initController(stdscr, startTime):
     graphPanel.addStats(GraphStat.SYSTEM_RESOURCES, arm.graphing.resourceStats.ResourceStats())
     if not CONFIG["startup.blindModeEnabled"]:
       graphPanel.addStats(GraphStat.CONNECTIONS, arm.graphing.connStats.ConnStats())
-    
+
     # sets graph based on config parameter
     try:
       initialStats = GRAPH_INIT_STATS.get(CONFIG["features.graph.type"])
       graphPanel.setStats(initialStats)
     except ValueError: pass # invalid stats, maybe connections when in blind mode
-    
+
     # prepopulates bandwidth values from state file
     if CONFIG["features.graph.bw.prepopulate"] and torTools.getConn().isAlive():
       isSuccessful = bwStats.prepopulateFromState()
@@ -129,25 +129,25 @@ class LabelPanel(panel.Panel):
   """
   Panel that just displays a single line of text.
   """
-  
+
   def __init__(self, stdscr):
     panel.Panel.__init__(self, stdscr, "msg", 0, height=1)
     self.msgText = ""
     self.msgAttr = curses.A_NORMAL
-  
+
   def setMessage(self, msg, attr = None):
     """
     Sets the message being displayed by the panel.
-    
+
     Arguments:
       msg  - string to be displayed
       attr - attribute for the label, normal text if undefined
     """
-    
+
     if attr == None: attr = curses.A_NORMAL
     self.msgText = msg
     self.msgAttr = attr
-  
+
   def draw(self, width, height):
     self.addstr(0, 0, self.msgText, self.msgAttr)
 
@@ -155,18 +155,18 @@ class Controller:
   """
   Tracks the global state of the interface
   """
-  
+
   def __init__(self, stdscr, stickyPanels, pagePanels):
     """
     Creates a new controller instance. Panel lists are ordered as they appear,
     top to bottom on the page.
-    
+
     Arguments:
       stdscr       - curses window
       stickyPanels - panels shown at the top of each page
       pagePanels   - list of pages, each being a list of the panels on it
     """
-    
+
     self._screen = stdscr
     self._stickyPanels = stickyPanels
     self._pagePanels = pagePanels
@@ -176,208 +176,208 @@ class Controller:
     self._isDone = False
     self._lastDrawn = 0
     self.setMsg() # initializes our control message
-  
+
   def getScreen(self):
     """
     Provides our curses window.
     """
-    
+
     return self._screen
-  
+
   def getPageCount(self):
     """
     Provides the number of pages the interface has. This may be zero if all
     page panels have been disabled.
     """
-    
+
     return len(self._pagePanels)
-  
+
   def getPage(self):
     """
     Provides the number belonging to this page. Page numbers start at zero.
     """
-    
+
     return self._page
-  
+
   def setPage(self, pageNumber):
     """
     Sets the selected page, raising a ValueError if the page number is invalid.
-    
+
     Arguments:
       pageNumber - page number to be selected
     """
-    
+
     if pageNumber < 0 or pageNumber >= self.getPageCount():
       raise ValueError("Invalid page number: %i" % pageNumber)
-    
+
     if pageNumber != self._page:
       self._page = pageNumber
       self._forceRedraw = True
       self.setMsg()
-  
+
   def nextPage(self):
     """
     Increments the page number.
     """
-    
+
     self.setPage((self._page + 1) % len(self._pagePanels))
-  
+
   def prevPage(self):
     """
     Decrements the page number.
     """
-    
+
     self.setPage((self._page - 1) % len(self._pagePanels))
-  
+
   def isPaused(self):
     """
     True if the interface is paused, false otherwise.
     """
-    
+
     return self._isPaused
-  
+
   def setPaused(self, isPause):
     """
     Sets the interface to be paused or unpaused.
     """
-    
+
     if isPause != self._isPaused:
       self._isPaused = isPause
       self._forceRedraw = True
       self.setMsg()
-      
+
       for panelImpl in self.getAllPanels():
         panelImpl.setPaused(isPause)
-  
+
   def getPanel(self, name):
     """
     Provides the panel with the given identifier. This returns None if no such
     panel exists.
-    
+
     Arguments:
       name - name of the panel to be fetched
     """
-    
+
     for panelImpl in self.getAllPanels():
       if panelImpl.getName() == name:
         return panelImpl
-    
+
     return None
-  
+
   def getStickyPanels(self):
     """
     Provides the panels visibile at the top of every page.
     """
-    
+
     return list(self._stickyPanels)
-  
+
   def getDisplayPanels(self, pageNumber = None, includeSticky = True):
     """
     Provides all panels belonging to a page and sticky content above it. This
     is ordered they way they are presented (top to bottom) on the page.
-    
+
     Arguments:
       pageNumber    - page number of the panels to be returned, the current
                       page if None
       includeSticky - includes sticky panels in the results if true
     """
-    
+
     returnPage = self._page if pageNumber == None else pageNumber
-    
+
     if self._pagePanels:
       if includeSticky:
         return self._stickyPanels + self._pagePanels[returnPage]
       else: return list(self._pagePanels[returnPage])
     else: return self._stickyPanels if includeSticky else []
-  
+
   def getDaemonPanels(self):
     """
     Provides thread panels.
     """
-    
+
     threadPanels = []
     for panelImpl in self.getAllPanels():
       if isinstance(panelImpl, threading.Thread):
         threadPanels.append(panelImpl)
-    
+
     return threadPanels
-  
+
   def getAllPanels(self):
     """
     Provides all panels in the interface.
     """
-    
+
     allPanels = list(self._stickyPanels)
-    
+
     for page in self._pagePanels:
       allPanels += list(page)
-    
+
     return allPanels
-  
+
   def redraw(self, force = True):
     """
     Redraws the displayed panel content.
-    
+
     Arguments:
       force - redraws reguardless of if it's needed if true, otherwise ignores
               the request when there arne't changes to be displayed
     """
-    
+
     force |= self._forceRedraw
     self._forceRedraw = False
-    
+
     currentTime = time.time()
     if CONFIG["features.refreshRate"] != 0:
       if self._lastDrawn + CONFIG["features.refreshRate"] <= currentTime:
         force = True
-    
+
     displayPanels = self.getDisplayPanels()
-    
+
     occupiedContent = 0
     for panelImpl in displayPanels:
       panelImpl.setTop(occupiedContent)
       occupiedContent += panelImpl.getHeight()
-    
+
     # apparently curses may cache display contents unless we explicitely
     # request a redraw here...
     # https://trac.torproject.org/projects/tor/ticket/2830#comment:9
     if force: self._screen.clear()
-    
+
     for panelImpl in displayPanels:
       panelImpl.redraw(force)
-    
+
     if force: self._lastDrawn = currentTime
-  
+
   def requestRedraw(self):
     """
     Requests that all content is redrawn when the interface is next rendered.
     """
-    
+
     self._forceRedraw = True
-  
+
   def getLastRedrawTime(self):
     """
     Provides the time when the content was last redrawn, zero if the content
     has never been drawn.
     """
-    
+
     return self._lastDrawn
-  
+
   def setMsg(self, msg = None, attr = None, redraw = False):
     """
     Sets the message displayed in the interfaces control panel. This uses our
     default prompt if no arguments are provided.
-    
+
     Arguments:
       msg    - string to be displayed
       attr   - attribute for the label, normal text if undefined
       redraw - redraws right away if true, otherwise redraws when display
                content is next normally drawn
     """
-    
+
     if msg == None:
       msg = ""
-      
+
       if attr == None:
         if not self._isPaused:
           msg = "page %i / %i - m: menu, p: pause, h: page help, q: quit" % (self._page + 1, len(self._pagePanels))
@@ -385,52 +385,52 @@ class Controller:
         else:
           msg = "Paused"
           attr = curses.A_STANDOUT
-    
+
     controlPanel = self.getPanel("msg")
     controlPanel.setMessage(msg, attr)
-    
+
     if redraw: controlPanel.redraw(True)
     else: self._forceRedraw = True
-  
+
   def getDataDirectory(self):
     """
     Provides the path where arm's resources are being placed. The path ends
     with a slash and is created if it doesn't already exist.
     """
-    
+
     dataDir = os.path.expanduser(CONFIG["startup.dataDirectory"])
     if not dataDir.endswith("/"): dataDir += "/"
     if not os.path.exists(dataDir): os.makedirs(dataDir)
     return dataDir
-  
+
   def isDone(self):
     """
     True if arm should be terminated, false otherwise.
     """
-    
+
     return self._isDone
-  
+
   def quit(self):
     """
     Terminates arm after the input is processed. Optionally if we're connected
     to a arm generated tor instance then this may check if that should be shut
     down too.
     """
-    
+
     self._isDone = True
-    
+
     # check if the torrc has a "ARM_SHUTDOWN" comment flag, if so then shut
     # down the instance
-    
+
     isShutdownFlagPresent = False
     torrcContents = torConfig.getTorrc().getContents()
-    
+
     if torrcContents:
       for line in torrcContents:
         if "# ARM_SHUTDOWN" in line:
           isShutdownFlagPresent = True
           break
-    
+
     if isShutdownFlagPresent:
       try: torTools.getConn().shutdown()
       except IOError, exc: arm.popups.showMsg(str(exc), 3, curses.A_BOLD)
@@ -439,20 +439,20 @@ def shutdownDaemons():
   """
   Stops and joins on worker threads.
   """
-  
+
   # prevents further worker threads from being spawned
   torTools.NO_SPAWN = True
-  
+
   # stops panel daemons
   control = getController()
 
   if control:
     for panelImpl in control.getDaemonPanels(): panelImpl.stop()
     for panelImpl in control.getDaemonPanels(): panelImpl.join()
-  
+
   # joins on stem threads
   torTools.getConn().close()
-  
+
   # joins on utility daemon threads - this might take a moment since the
   # internal threadpools being joined might be sleeping
   hostnames.stop()
@@ -466,11 +466,11 @@ def shutdownDaemons():
 def heartbeatCheck(isUnresponsive):
   """
   Logs if its been ten seconds since the last BW event.
-  
+
   Arguments:
     isUnresponsive - flag for if we've indicated to be responsive or not
   """
-  
+
   conn = torTools.getConn()
   lastHeartbeat = conn.controller.get_latest_heartbeat()
   if conn.isAlive():
@@ -481,7 +481,7 @@ def heartbeatCheck(isUnresponsive):
       # really shouldn't happen (meant Tor froze for a bit)
       isUnresponsive = False
       log.notice("Relay resumed")
-  
+
   return isUnresponsive
 
 def connResetListener(controller, eventType, _):
@@ -489,19 +489,19 @@ def connResetListener(controller, eventType, _):
   Pauses connection resolution when tor's shut down, and resumes with the new
   pid if started again.
   """
-  
+
   if connections.isResolverAlive("tor"):
     resolver = connections.getResolver("tor")
     resolver.setPaused(eventType == State.CLOSED)
-    
+
     if eventType in (State.INIT, State.RESET):
       # Reload the torrc contents. If the torrc panel is present then it will
       # do this instead since it wants to do validation and redraw _after_ the
       # new contents are loaded.
-      
+
       if getController().getPanel("torrc") == None:
         torConfig.getTorrc().load(True)
-      
+
       try:
         resolver.setPid(controller.get_pid())
       except ValueError:
@@ -510,63 +510,63 @@ def connResetListener(controller, eventType, _):
 def startTorMonitor(startTime):
   """
   Initializes the interface and starts the main draw loop.
-  
+
   Arguments:
     startTime - unix time for when arm was started
   """
-  
+
   # attempts to fetch the tor pid, warning if unsuccessful (this is needed for
   # checking its resource usage, among other things)
   conn = torTools.getConn()
   torPid = conn.controller.get_pid(None)
-  
+
   if not torPid and conn.isAlive():
     log.warn("Unable to determine Tor's pid. Some information, like its resource usage will be unavailable.")
-  
+
   # adds events needed for arm functionality to the torTools REQ_EVENTS
   # mapping (they're then included with any setControllerEvents call, and log
   # a more helpful error if unavailable)
-  
+
   torTools.REQ_EVENTS["BW"] = "bandwidth graph won't function"
-  
+
   if not CONFIG["startup.blindModeEnabled"]:
     # The DisableDebuggerAttachment will prevent our connection panel from really
     # functioning. It'll have circuits, but little else. If this is the case then
     # notify the user and tell them what they can do to fix it.
-    
+
     if conn.getOption("DisableDebuggerAttachment", None) == "1":
       log.notice("Tor is preventing system utilities like netstat and lsof from working. This means that arm can't provide you with connection information. You can change this by adding 'DisableDebuggerAttachment 0' to your torrc and restarting tor. For more information see...\nhttps://trac.torproject.org/3313")
       connections.getResolver("tor").setPaused(True)
     else:
       torTools.REQ_EVENTS["CIRC"] = "may cause issues in identifying client connections"
-      
+
       # Configures connection resoultions. This is paused/unpaused according to
       # if Tor's connected or not.
       conn.addStatusListener(connResetListener)
-      
+
       if torPid:
         # use the tor pid to help narrow connection results
         torCmdName = system.get_name_by_pid(torPid)
-        
+
         if torCmdName is None:
           torCmdName = "tor"
-        
+
         connections.getResolver(torCmdName, torPid, "tor")
       else:
         # constructs singleton resolver and, if tor isn't connected, initizes
         # it to be paused
         connections.getResolver("tor").setPaused(not conn.isAlive())
-      
+
       # hack to display a better (arm specific) notice if all resolvers fail
       connections.RESOLVER_FINAL_FAILURE_MSG = "We were unable to use any of your system's resolvers to get tor's connections. This is fine, but means that the connections page will be empty. This is usually permissions related so if you would like to fix this then run arm with the same user as tor (ie, \"sudo -u <tor user> arm\")."
-  
+
   # provides a notice about any event types tor supports but arm doesn't
   missingEventTypes = arm.logPanel.getMissingEventTypes()
-  
+
   if missingEventTypes:
     pluralLabel = "s" if len(missingEventTypes) > 1 else ""
     log.info("arm doesn't recognize the following event type%s: %s (log 'UNKNOWN' events to see them)" % (pluralLabel, ", ".join(missingEventTypes)))
-  
+
   try:
     curses.wrapper(drawTorMonitor, startTime)
   except UnboundLocalError, exc:
@@ -583,63 +583,63 @@ def startTorMonitor(startTime):
     # (which would leave the user's terminal in a screwed up state). There is
     # still a tiny timing issue here (after the exception but before the flag
     # is set) but I've never seen it happen in practice.
-    
+
     panel.HALT_ACTIVITY = True
     shutdownDaemons()
 
 def drawTorMonitor(stdscr, startTime):
   """
   Main draw loop context.
-  
+
   Arguments:
     stdscr    - curses window
     startTime - unix time for when arm was started
   """
-  
+
   initController(stdscr, startTime)
   control = getController()
-  
+
   # provides notice about any unused config keys
   for key in conf.get_config("arm").unused_keys():
     log.notice("Unused configuration entry: %s" % key)
-  
+
   # tells daemon panels to start
   for panelImpl in control.getDaemonPanels(): panelImpl.start()
-  
+
   # allows for background transparency
   try: curses.use_default_colors()
   except curses.error: pass
-  
+
   # makes the cursor invisible
   try: curses.curs_set(0)
   except curses.error: pass
-  
+
   # logs the initialization time
   log.info("arm started (initialization took %0.3f seconds)" % (time.time() - startTime))
-  
+
   # main draw loop
   overrideKey = None     # uses this rather than waiting on user input
   isUnresponsive = False # flag for heartbeat responsiveness check
-  
+
   while not control.isDone():
     displayPanels = control.getDisplayPanels()
     isUnresponsive = heartbeatCheck(isUnresponsive)
-    
+
     # sets panel visability
     for panelImpl in control.getAllPanels():
       panelImpl.setVisible(panelImpl in displayPanels)
-    
+
     # redraws the interface if it's needed
     control.redraw(False)
     stdscr.refresh()
-    
+
     # wait for user keyboard input until timeout, unless an override was set
     if overrideKey:
       key, overrideKey = overrideKey, None
     else:
       curses.halfdelay(CONFIG["features.redrawRate"] * 10)
       key = stdscr.getch()
-    
+
     if key == curses.KEY_RIGHT:
       control.nextPage()
     elif key == curses.KEY_LEFT:
@@ -655,13 +655,13 @@ def drawTorMonitor(stdscr, startTime):
         confirmationKey = arm.popups.showMsg(msg, attr = curses.A_BOLD)
         quitConfirmed = confirmationKey in (ord('q'), ord('Q'))
       else: quitConfirmed = True
-      
+
       if quitConfirmed: control.quit()
     elif key == ord('x') or key == ord('X'):
       # provides prompt to confirm that arm should issue a sighup
       msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
       confirmationKey = arm.popups.showMsg(msg, attr = curses.A_BOLD)
-      
+
       if confirmationKey in (ord('x'), ord('X')):
         try: torTools.getConn().reload()
         except IOError, exc:
@@ -675,6 +675,6 @@ def drawTorMonitor(stdscr, startTime):
       for panelImpl in displayPanels:
         isKeystrokeConsumed = panelImpl.handleKey(key)
         if isKeystrokeConsumed: break
-  
+
   shutdownDaemons()
 
diff --git a/arm/graphing/bandwidthStats.py b/arm/graphing/bandwidthStats.py
index 9d05c72..e074716 100644
--- a/arm/graphing/bandwidthStats.py
+++ b/arm/graphing/bandwidthStats.py
@@ -41,85 +41,85 @@ class BandwidthStats(graphPanel.GraphStats):
   """
   Uses tor BW events to generate bandwidth usage graph.
   """
-  
+
   def __init__(self, isPauseBuffer=False):
     graphPanel.GraphStats.__init__(self)
-    
+
     # stats prepopulated from tor's state file
     self.prepopulatePrimaryTotal = 0
     self.prepopulateSecondaryTotal = 0
     self.prepopulateTicks = 0
-    
+
     # accounting data (set by _updateAccountingInfo method)
     self.accountingLastUpdated = 0
     self.accountingInfo = dict([(arg, "") for arg in ACCOUNTING_ARGS])
-    
+
     # listens for tor reload (sighup) events which can reset the bandwidth
     # rate/burst and if tor's using accounting
     conn = torTools.getConn()
     self._titleStats, self.isAccounting = [], False
     if not isPauseBuffer: self.resetListener(conn.getController(), State.INIT, None) # initializes values
     conn.addStatusListener(self.resetListener)
-    
+
     # Initialized the bandwidth totals to the values reported by Tor. This
     # uses a controller options introduced in ticket 2345:
     # https://trac.torproject.org/projects/tor/ticket/2345
-    # 
+    #
     # further updates are still handled via BW events to avoid unnecessary
     # GETINFO requests.
-    
+
     self.initialPrimaryTotal = 0
     self.initialSecondaryTotal = 0
-    
+
     readTotal = conn.getInfo("traffic/read", None)
     if readTotal and readTotal.isdigit():
       self.initialPrimaryTotal = int(readTotal) / 1024 # Bytes -> KB
-    
+
     writeTotal = conn.getInfo("traffic/written", None)
     if writeTotal and writeTotal.isdigit():
       self.initialSecondaryTotal = int(writeTotal) / 1024 # Bytes -> KB
-  
+
   def clone(self, newCopy=None):
     if not newCopy: newCopy = BandwidthStats(True)
     newCopy.accountingLastUpdated = self.accountingLastUpdated
     newCopy.accountingInfo = self.accountingInfo
-    
+
     # attributes that would have been initialized from calling the resetListener
     newCopy.isAccounting = self.isAccounting
     newCopy._titleStats = self._titleStats
-    
+
     return graphPanel.GraphStats.clone(self, newCopy)
-  
+
   def resetListener(self, controller, eventType, _):
     # updates title parameters and accounting status if they changed
     self._titleStats = []     # force reset of title
     self.new_desc_event(None) # updates title params
-    
+
     if eventType in (State.INIT, State.RESET) and CONFIG["features.graph.bw.accounting.show"]:
       isAccountingEnabled = controller.get_info('accounting/enabled', None) == '1'
-      
+
       if isAccountingEnabled != self.isAccounting:
         self.isAccounting = isAccountingEnabled
-        
+
         # redraws the whole screen since our height changed
         arm.controller.getController().redraw()
-    
+
     # redraws to reflect changes (this especially noticeable when we have
     # accounting and shut down since it then gives notice of the shutdown)
     if self._graphPanel and self.isSelected: self._graphPanel.redraw(True)
-  
+
   def prepopulateFromState(self):
     """
     Attempts to use tor's state file to prepopulate values for the 15 minute
     interval via the BWHistoryReadValues/BWHistoryWriteValues values. This
     returns True if successful and False otherwise.
     """
-    
+
     # checks that this is a relay (if ORPort is unset, then skip)
     conn = torTools.getConn()
     orPort = conn.getOption("ORPort", None)
     if orPort == "0": return
-    
+
     # gets the uptime (using the same parameters as the header panel to take
     # advantage of caching)
     # TODO: stem dropped system caching support so we'll need to think of
@@ -130,11 +130,11 @@ class BandwidthStats(graphPanel.GraphStats):
       queryParam = ["%cpu", "rss", "%mem", "etime"]
       queryCmd = "ps -p %s -o %s" % (queryPid, ",".join(queryParam))
       psCall = system.call(queryCmd, None)
-      
+
       if psCall and len(psCall) == 2:
         stats = psCall[1].strip().split()
         if len(stats) == 4: uptime = stats[3]
-    
+
     # checks if tor has been running for at least a day, the reason being that
     # the state tracks a day's worth of data and this should only prepopulate
     # results associated with this tor instance
@@ -142,38 +142,38 @@ class BandwidthStats(graphPanel.GraphStats):
       msg = PREPOPULATE_FAILURE_MSG % "insufficient uptime"
       log.notice(msg)
       return False
-    
+
     # get the user's data directory (usually '~/.tor')
     dataDir = conn.getOption("DataDirectory", None)
     if not dataDir:
       msg = PREPOPULATE_FAILURE_MSG % "data directory not found"
       log.notice(msg)
       return False
-    
+
     # attempt to open the state file
     try: stateFile = open("%s%s/state" % (torTools.get_chroot(), dataDir), "r")
     except IOError:
       msg = PREPOPULATE_FAILURE_MSG % "unable to read the state file"
       log.notice(msg)
       return False
-    
+
     # get the BWHistory entries (ordered oldest to newest) and number of
     # intervals since last recorded
     bwReadEntries, bwWriteEntries = None, None
     missingReadEntries, missingWriteEntries = None, None
-    
+
     # converts from gmt to local with respect to DST
     tz_offset = time.altzone if time.localtime()[8] else time.timezone
-    
+
     for line in stateFile:
       line = line.strip()
-      
+
       # According to the rep_hist_update_state() function the BWHistory*Ends
       # correspond to the start of the following sampling period. Also, the
       # most recent values of BWHistory*Values appear to be an incremental
       # counter for the current sampling period. Hence, offsets are added to
       # account for both.
-      
+
       if line.startswith("BWHistoryReadValues"):
         bwReadEntries = line[20:].split(",")
         bwReadEntries = [int(entry) / 1024.0 / 900 for entry in bwReadEntries]
@@ -190,131 +190,131 @@ class BandwidthStats(graphPanel.GraphStats):
         lastWriteTime = time.mktime(time.strptime(line[19:], "%Y-%m-%d %H:%M:%S")) - tz_offset
         lastWriteTime -= 900
         missingWriteEntries = int((time.time() - lastWriteTime) / 900)
-    
+
     if not bwReadEntries or not bwWriteEntries or not lastReadTime or not lastWriteTime:
       msg = PREPOPULATE_FAILURE_MSG % "bandwidth stats missing from state file"
       log.notice(msg)
       return False
-    
+
     # fills missing entries with the last value
     bwReadEntries += [bwReadEntries[-1]] * missingReadEntries
     bwWriteEntries += [bwWriteEntries[-1]] * missingWriteEntries
-    
+
     # crops starting entries so they're the same size
     entryCount = min(len(bwReadEntries), len(bwWriteEntries), self.maxCol)
     bwReadEntries = bwReadEntries[len(bwReadEntries) - entryCount:]
     bwWriteEntries = bwWriteEntries[len(bwWriteEntries) - entryCount:]
-    
+
     # gets index for 15-minute interval
     intervalIndex = 0
     for indexEntry in graphPanel.UPDATE_INTERVALS:
       if indexEntry[1] == 900: break
       else: intervalIndex += 1
-    
+
     # fills the graphing parameters with state information
     for i in range(entryCount):
       readVal, writeVal = bwReadEntries[i], bwWriteEntries[i]
-      
+
       self.lastPrimary, self.lastSecondary = readVal, writeVal
-      
+
       self.prepopulatePrimaryTotal += readVal * 900
       self.prepopulateSecondaryTotal += writeVal * 900
       self.prepopulateTicks += 900
-      
+
       self.primaryCounts[intervalIndex].insert(0, readVal)
       self.secondaryCounts[intervalIndex].insert(0, writeVal)
-    
+
     self.maxPrimary[intervalIndex] = max(self.primaryCounts)
     self.maxSecondary[intervalIndex] = max(self.secondaryCounts)
     del self.primaryCounts[intervalIndex][self.maxCol + 1:]
     del self.secondaryCounts[intervalIndex][self.maxCol + 1:]
-    
+
     msg = PREPOPULATE_SUCCESS_MSG
     missingSec = time.time() - min(lastReadTime, lastWriteTime)
     if missingSec: msg += " (%s is missing)" % str_tools.get_time_label(missingSec, 0, True)
     log.notice(msg)
-    
+
     return True
-  
+
   def bandwidth_event(self, event):
     if self.isAccounting and self.isNextTickRedraw():
       if time.time() - self.accountingLastUpdated >= CONFIG["features.graph.bw.accounting.rate"]:
         self._updateAccountingInfo()
-    
+
     # scales units from B to KB for graphing
     self._processEvent(event.read / 1024.0, event.written / 1024.0)
-  
+
   def draw(self, panel, width, height):
     # line of the graph's x-axis labeling
     labelingLine = graphPanel.GraphStats.getContentHeight(self) + panel.graphHeight - 2
-    
+
     # if display is narrow, overwrites x-axis labels with avg / total stats
     if width <= COLLAPSE_WIDTH:
       # clears line
       panel.addstr(labelingLine, 0, " " * width)
       graphCol = min((width - 10) / 2, self.maxCol)
-      
+
       primaryFooter = "%s, %s" % (self._getAvgLabel(True), self._getTotalLabel(True))
       secondaryFooter = "%s, %s" % (self._getAvgLabel(False), self._getTotalLabel(False))
-      
+
       panel.addstr(labelingLine, 1, primaryFooter, uiTools.getColor(self.getColor(True)))
       panel.addstr(labelingLine, graphCol + 6, secondaryFooter, uiTools.getColor(self.getColor(False)))
-    
+
     # provides accounting stats if enabled
     if self.isAccounting:
       if torTools.getConn().isAlive():
         status = self.accountingInfo["status"]
-        
+
         hibernateColor = "green"
         if status == "soft": hibernateColor = "yellow"
         elif status == "hard": hibernateColor = "red"
         elif status == "":
           # failed to be queried
           status, hibernateColor = "unknown", "red"
-        
+
         panel.addstr(labelingLine + 2, 0, "Accounting (", curses.A_BOLD)
         panel.addstr(labelingLine + 2, 12, status, curses.A_BOLD | uiTools.getColor(hibernateColor))
         panel.addstr(labelingLine + 2, 12 + len(status), ")", curses.A_BOLD)
-        
+
         resetTime = self.accountingInfo["resetTime"]
         if not resetTime: resetTime = "unknown"
         panel.addstr(labelingLine + 2, 35, "Time to reset: %s" % resetTime)
-        
+
         used, total = self.accountingInfo["read"], self.accountingInfo["readLimit"]
         if used and total:
           panel.addstr(labelingLine + 3, 2, "%s / %s" % (used, total), uiTools.getColor(self.getColor(True)))
-        
+
         used, total = self.accountingInfo["written"], self.accountingInfo["writtenLimit"]
         if used and total:
           panel.addstr(labelingLine + 3, 37, "%s / %s" % (used, total), uiTools.getColor(self.getColor(False)))
       else:
         panel.addstr(labelingLine + 2, 0, "Accounting:", curses.A_BOLD)
         panel.addstr(labelingLine + 2, 12, "Connection Closed...")
-  
+
   def getTitle(self, width):
     stats = list(self._titleStats)
-    
+
     while True:
       if not stats: return "Bandwidth:"
       else:
         label = "Bandwidth (%s):" % ", ".join(stats)
-        
+
         if len(label) > width: del stats[-1]
         else: return label
-  
+
   def getHeaderLabel(self, width, isPrimary):
     graphType = "Download" if isPrimary else "Upload"
     stats = [""]
-    
+
     # if wide then avg and total are part of the header, otherwise they're on
     # the x-axis
     if width * 2 > COLLAPSE_WIDTH:
       stats = [""] * 3
       stats[1] = "- %s" % self._getAvgLabel(isPrimary)
       stats[2] = ", %s" % self._getTotalLabel(isPrimary)
-    
+
     stats[0] = "%-14s" % ("%s/sec" % str_tools.get_size_label((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"]))
-    
+
     # drops label's components if there's not enough space
     labeling = graphType + " (" + "".join(stats).strip() + "):"
     while len(labeling) >= width:
@@ -324,21 +324,21 @@ class BandwidthStats(graphPanel.GraphStats):
       else:
         labeling = graphType + ":"
         break
-    
+
     return labeling
-  
+
   def getColor(self, isPrimary):
     return DL_COLOR if isPrimary else UL_COLOR
-  
+
   def getContentHeight(self):
     baseHeight = graphPanel.GraphStats.getContentHeight(self)
     return baseHeight + 3 if self.isAccounting else baseHeight
-  
+
   def new_desc_event(self, event):
     # updates self._titleStats with updated values
     conn = torTools.getConn()
     if not conn.isAlive(): return # keep old values
-    
+
     myFingerprint = conn.getInfo("fingerprint", None)
     if not self._titleStats or not myFingerprint or (event and myFingerprint in event.idlist):
       stats = []
@@ -347,19 +347,19 @@ class BandwidthStats(graphPanel.GraphStats):
       bwObserved = conn.getMyBandwidthObserved()
       bwMeasured = conn.getMyBandwidthMeasured()
       labelInBytes = CONFIG["features.graph.bw.transferInBytes"]
-      
+
       if bwRate and bwBurst:
         bwRateLabel = str_tools.get_size_label(bwRate, 1, False, labelInBytes)
         bwBurstLabel = str_tools.get_size_label(bwBurst, 1, False, labelInBytes)
-        
+
         # if both are using rounded values then strip off the ".0" decimal
         if ".0" in bwRateLabel and ".0" in bwBurstLabel:
           bwRateLabel = bwRateLabel.replace(".0", "")
           bwBurstLabel = bwBurstLabel.replace(".0", "")
-        
+
         stats.append("limit: %s/s" % bwRateLabel)
         stats.append("burst: %s/s" % bwBurstLabel)
-      
+
       # Provide the observed bandwidth either if the measured bandwidth isn't
       # available or if the measured bandwidth is the observed (this happens
       # if there isn't yet enough bandwidth measurements).
@@ -367,38 +367,38 @@ class BandwidthStats(graphPanel.GraphStats):
         stats.append("observed: %s/s" % str_tools.get_size_label(bwObserved, 1, False, labelInBytes))
       elif bwMeasured:
         stats.append("measured: %s/s" % str_tools.get_size_label(bwMeasured, 1, False, labelInBytes))
-      
+
       self._titleStats = stats
-  
+
   def _getAvgLabel(self, isPrimary):
     total = self.primaryTotal if isPrimary else self.secondaryTotal
     total += self.prepopulatePrimaryTotal if isPrimary else self.prepopulateSecondaryTotal
     return "avg: %s/sec" % str_tools.get_size_label((total / max(1, self.tick + self.prepopulateTicks)) * 1024, 1, False, CONFIG["features.graph.bw.transferInBytes"])
-  
+
   def _getTotalLabel(self, isPrimary):
     total = self.primaryTotal if isPrimary else self.secondaryTotal
     total += self.initialPrimaryTotal if isPrimary else self.initialSecondaryTotal
     return "total: %s" % str_tools.get_size_label(total * 1024, 1)
-  
+
   def _updateAccountingInfo(self):
     """
     Updates mapping used for accounting info. This includes the following keys:
     status, resetTime, read, written, readLimit, writtenLimit
-    
+
     Any failed lookups result in a mapping to an empty string.
     """
-    
+
     conn = torTools.getConn()
     queried = dict([(arg, "") for arg in ACCOUNTING_ARGS])
     queried["status"] = conn.getInfo("accounting/hibernating", None)
-    
+
     # provides a nicely formatted reset time
     endInterval = conn.getInfo("accounting/interval-end", None)
     if endInterval:
       # converts from gmt to local with respect to DST
       if time.localtime()[8]: tz_offset = time.altzone
       else: tz_offset = time.timezone
-      
+
       sec = time.mktime(time.strptime(endInterval, "%Y-%m-%d %H:%M:%S")) - time.time() - tz_offset
       if CONFIG["features.graph.bw.accounting.isTimeLong"]:
         queried["resetTime"] = ", ".join(str_tools.get_time_labels(sec, True))
@@ -410,21 +410,21 @@ class BandwidthStats(graphPanel.GraphStats):
         minutes = sec / 60
         sec %= 60
         queried["resetTime"] = "%i:%02i:%02i:%02i" % (days, hours, minutes, sec)
-    
+
     # number of bytes used and in total for the accounting period
     used = conn.getInfo("accounting/bytes", None)
     left = conn.getInfo("accounting/bytes-left", None)
-    
+
     if used and left:
       usedComp, leftComp = used.split(" "), left.split(" ")
       read, written = int(usedComp[0]), int(usedComp[1])
       readLeft, writtenLeft = int(leftComp[0]), int(leftComp[1])
-      
+
       queried["read"] = str_tools.get_size_label(read)
       queried["written"] = str_tools.get_size_label(written)
       queried["readLimit"] = str_tools.get_size_label(read + readLeft)
       queried["writtenLimit"] = str_tools.get_size_label(written + writtenLeft)
-    
+
     self.accountingInfo = queried
     self.accountingLastUpdated = time.time()
 
diff --git a/arm/graphing/connStats.py b/arm/graphing/connStats.py
index 69d3489..2b1b188 100644
--- a/arm/graphing/connStats.py
+++ b/arm/graphing/connStats.py
@@ -9,52 +9,52 @@ from stem.control import State
 
 class ConnStats(graphPanel.GraphStats):
   """
-  Tracks number of connections, counting client and directory connections as 
+  Tracks number of connections, counting client and directory connections as
   outbound. Control connections are excluded from counts.
   """
-  
+
   def __init__(self):
     graphPanel.GraphStats.__init__(self)
-    
+
     # listens for tor reload (sighup) events which can reset the ports tor uses
     conn = torTools.getConn()
     self.orPort, self.dirPort, self.controlPort = "0", "0", "0"
     self.resetListener(conn.getController(), State.INIT, None) # 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, controller, eventType, _):
     if eventType in (State.INIT, State.RESET):
       self.orPort = controller.get_conf("ORPort", "0")
       self.dirPort = controller.get_conf("DirPort", "0")
       self.controlPort = controller.get_conf("ControlPort", "0")
-  
+
   def eventTick(self):
     """
     Fetches connection stats from cached information.
     """
-    
+
     inboundCount, outboundCount = 0, 0
-    
+
     for entry in connections.getResolver("tor").getConnections():
       localPort = entry[1]
       if localPort in (self.orPort, self.dirPort): inboundCount += 1
       elif localPort == self.controlPort: pass # control connection
       else: outboundCount += 1
-    
+
     self._processEvent(inboundCount, outboundCount)
-  
+
   def getTitle(self, width):
     return "Connection Count:"
-  
+
   def getHeaderLabel(self, width, isPrimary):
     avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max(1, self.tick)
     if isPrimary: return "Inbound (%s, avg: %s):" % (self.lastPrimary, avg)
     else: return "Outbound (%s, avg: %s):" % (self.lastSecondary, avg)
-  
+
   def getRefreshRate(self):
     return 5
 
diff --git a/arm/graphing/graphPanel.py b/arm/graphing/graphPanel.py
index c95adda..50b755d 100644
--- a/arm/graphing/graphPanel.py
+++ b/arm/graphing/graphPanel.py
@@ -70,48 +70,48 @@ class GraphStats:
   graphed. Up to two graphs (a 'primary' and 'secondary') can be displayed at a
   time and timescale parameters use the labels defined in UPDATE_INTERVALS.
   """
-  
+
   def __init__(self):
     """
     Initializes parameters needed to present a graph.
     """
-    
+
     # panel to be redrawn when updated (set when added to GraphPanel)
     self._graphPanel = None
     self.isSelected = False
     self.isPauseBuffer = False
-    
+
     # tracked stats
     self.tick = 0                                 # number of processed events
     self.lastPrimary, self.lastSecondary = 0, 0   # most recent registered stats
     self.primaryTotal, self.secondaryTotal = 0, 0 # sum of all stats seen
-    
+
     # timescale dependent stats
     self.maxCol = CONFIG["features.graph.maxWidth"]
     self.maxPrimary, self.maxSecondary = {}, {}
     self.primaryCounts, self.secondaryCounts = {}, {}
-    
+
     for i in range(len(UPDATE_INTERVALS)):
       # recent rates for graph
       self.maxPrimary[i] = 0
       self.maxSecondary[i] = 0
-      
+
       # historic stats for graph, first is accumulator
       # iterative insert needed to avoid making shallow copies (nasty, nasty gotcha)
       self.primaryCounts[i] = (self.maxCol + 1) * [0]
       self.secondaryCounts[i] = (self.maxCol + 1) * [0]
-    
+
     # tracks BW events
     torTools.getConn().addEventListener(self.bandwidth_event, stem.control.EventType.BW)
-  
+
   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
@@ -124,110 +124,110 @@ class GraphStats:
     newCopy.secondaryCounts = copy.deepcopy(self.secondaryCounts)
     newCopy.isPauseBuffer = True
     return newCopy
-  
+
   def eventTick(self):
     """
     Called when it's time to process another event. All graphs use tor BW
     events to keep in sync with each other (this happens once a second).
     """
-    
+
     pass
-  
+
   def isNextTickRedraw(self):
     """
     Provides true if the following tick (call to _processEvent) will result in
     being redrawn.
     """
-    
+
     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
     else: return False
-  
+
   def getTitle(self, width):
     """
     Provides top label.
     """
-    
+
     return ""
-  
+
   def getHeaderLabel(self, width, isPrimary):
     """
     Provides labeling presented at the top of the graph.
     """
-    
+
     return ""
-  
+
   def getColor(self, isPrimary):
     """
     Provides the color to be used for the graph and stats.
     """
-    
+
     return DEFAULT_COLOR_PRIMARY if isPrimary else DEFAULT_COLOR_SECONDARY
-  
+
   def getContentHeight(self):
     """
     Provides the height content should take up (not including the graph).
     """
-    
+
     return DEFAULT_CONTENT_HEIGHT
-  
+
   def getRefreshRate(self):
     """
     Provides the number of ticks between when the stats have new values to be
     redrawn.
     """
-    
+
     return 1
-  
+
   def isVisible(self):
     """
     True if the stat has content to present, false if it should be hidden.
     """
-    
+
     return True
-  
+
   def draw(self, panel, width, height):
     """
     Allows for any custom drawing monitor wishes to append.
     """
-    
+
     pass
-  
+
   def bandwidth_event(self, event):
     if not self.isPauseBuffer: self.eventTick()
-  
+
   def _processEvent(self, primary, secondary):
     """
     Includes new stats in graphs and notifies associated GraphPanel of changes.
     """
-    
+
     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.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 and self._graphPanel: self._graphPanel.redraw(True)
 
 class GraphPanel(panel.Panel):
@@ -235,7 +235,7 @@ class GraphPanel(panel.Panel):
   Panel displaying a graph, drawing statistics from custom GraphStats
   implementations.
   """
-  
+
   def __init__(self, stdscr):
     panel.Panel.__init__(self, stdscr, "graph", 0)
     self.updateInterval = CONFIG["features.graph.interval"]
@@ -244,62 +244,62 @@ class GraphPanel(panel.Panel):
     self.currentDisplay = None    # label of the stats currently being displayed
     self.stats = {}               # available stats (mappings of label -> instance)
     self.setPauseAttr("stats")
-  
+
   def getUpdateInterval(self):
     """
     Provides the rate that we update the graph at.
     """
-    
+
     return self.updateInterval
-  
+
   def setUpdateInterval(self, updateInterval):
     """
     Sets the rate that we update the graph at.
-    
+
     Arguments:
       updateInterval - update time enum
     """
-    
+
     self.updateInterval = updateInterval
-  
+
   def getBoundsType(self):
     """
     Provides the type of graph bounds used.
     """
-    
+
     return self.bounds
-  
+
   def setBoundsType(self, boundsType):
     """
     Sets the type of graph boundaries we use.
-    
+
     Arguments:
       boundsType - graph bounds enum
     """
-    
+
     self.bounds = boundsType
-  
+
   def getHeight(self):
     """
     Provides the height requested by the currently displayed GraphStats (zero
     if hidden).
     """
-    
+
     if self.currentDisplay and self.stats[self.currentDisplay].isVisible():
       return self.stats[self.currentDisplay].getContentHeight() + self.graphHeight
     else: return 0
-  
+
   def setGraphHeight(self, newGraphHeight):
     """
     Sets the preferred height used for the graph (restricted to the
     MIN_GRAPH_HEIGHT minimum).
-    
+
     Arguments:
       newGraphHeight - new height for the graph
     """
-    
+
     self.graphHeight = max(MIN_GRAPH_HEIGHT, newGraphHeight)
-  
+
   def resizeGraph(self):
     """
     Prompts for user input to resize the graph panel. Options include...
@@ -307,9 +307,9 @@ class GraphPanel(panel.Panel):
       up arrow - shrink graph
       enter / space - set size
     """
-    
+
     control = arm.controller.getController()
-    
+
     panel.CURSES_LOCK.acquire()
     try:
       while True:
@@ -317,24 +317,24 @@ class GraphPanel(panel.Panel):
         control.setMsg(msg, curses.A_BOLD, True)
         curses.cbreak()
         key = control.getScreen().getch()
-        
+
         if key == curses.KEY_DOWN:
           # don't grow the graph if it's already consuming the whole display
           # (plus an extra line for the graph/log gap)
           maxHeight = self.parent.getmaxyx()[0] - self.top
           currentHeight = self.getHeight()
-          
+
           if currentHeight < maxHeight + 1:
             self.setGraphHeight(self.graphHeight + 1)
         elif key == curses.KEY_UP:
           self.setGraphHeight(self.graphHeight - 1)
         elif uiTools.isSelectionKey(key): break
-        
+
         control.redraw()
     finally:
       control.setMsg()
       panel.CURSES_LOCK.release()
-  
+
   def handleKey(self, key):
     isKeystrokeConsumed = True
     if key == ord('r') or key == ord('R'):
@@ -347,19 +347,19 @@ class GraphPanel(panel.Panel):
       # provides a menu to pick the graphed stats
       availableStats = self.stats.keys()
       availableStats.sort()
-      
+
       # uses sorted, camel cased labels for the options
       options = ["None"]
       for label in availableStats:
         words = label.split()
         options.append(" ".join(word[0].upper() + word[1:] for word in words))
-      
+
       if self.currentDisplay:
         initialSelection = availableStats.index(self.currentDisplay) + 1
       else: initialSelection = 0
-      
+
       selection = arm.popups.showMenu("Graphed Stats:", options, initialSelection)
-      
+
       # applies new setting
       if selection == 0: self.setStats(None)
       elif selection != -1: self.setStats(availableStats[selection - 1])
@@ -369,37 +369,37 @@ class GraphPanel(panel.Panel):
       selection = arm.popups.showMenu("Update Interval:", options, self.updateInterval)
       if selection != -1: self.updateInterval = selection
     else: isKeystrokeConsumed = False
-    
+
     return isKeystrokeConsumed
-  
+
   def getHelp(self):
     if self.currentDisplay: graphedStats = self.currentDisplay
     else: graphedStats = "none"
-    
+
     options = []
     options.append(("r", "resize graph", None))
     options.append(("s", "graphed stats", graphedStats))
     options.append(("b", "graph bounds", self.bounds.lower()))
     options.append(("i", "graph update interval", UPDATE_INTERVALS[self.updateInterval][0]))
     return options
-  
+
   def draw(self, width, height):
     """ Redraws graph panel """
-    
+
     if self.currentDisplay:
       param = self.getAttr("stats")[self.currentDisplay]
       graphCol = min((width - 10) / 2, param.maxCol)
-      
+
       primaryColor = uiTools.getColor(param.getColor(True))
       secondaryColor = uiTools.getColor(param.getColor(False))
-      
+
       if self.isTitleVisible(): self.addstr(0, 0, param.getTitle(width), curses.A_STANDOUT)
-      
+
       # top labels
       left, right = param.getHeaderLabel(width / 2, True), param.getHeaderLabel(width / 2, False)
       if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
       if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
-      
+
       # determines max/min value on the graph
       if self.bounds == Bounds.GLOBAL_MAX:
         primaryMaxBound = int(param.maxPrimary[self.updateInterval])
@@ -412,60 +412,60 @@ class GraphPanel(panel.Panel):
         else:
           primaryMaxBound = int(max(param.primaryCounts[self.updateInterval][1:graphCol + 1]))
           secondaryMaxBound = int(max(param.secondaryCounts[self.updateInterval][1:graphCol + 1]))
-      
+
       primaryMinBound = secondaryMinBound = 0
       if self.bounds == Bounds.TIGHT:
         primaryMinBound = int(min(param.primaryCounts[self.updateInterval][1:graphCol + 1]))
         secondaryMinBound = int(min(param.secondaryCounts[self.updateInterval][1:graphCol + 1]))
-        
+
         # if the max = min (ie, all values are the same) then use zero lower
         # bound so a graph is still displayed
         if primaryMinBound == primaryMaxBound: primaryMinBound = 0
         if secondaryMinBound == secondaryMaxBound: secondaryMinBound = 0
-      
+
       # displays upper and lower bounds
       self.addstr(2, 0, "%4i" % primaryMaxBound, primaryColor)
       self.addstr(self.graphHeight + 1, 0, "%4i" % primaryMinBound, primaryColor)
-      
+
       self.addstr(2, graphCol + 5, "%4i" % secondaryMaxBound, secondaryColor)
       self.addstr(self.graphHeight + 1, graphCol + 5, "%4i" % secondaryMinBound, secondaryColor)
-      
+
       # displays intermediate bounds on every other row
       if CONFIG["features.graph.showIntermediateBounds"]:
         ticks = (self.graphHeight - 3) / 2
         for i in range(ticks):
           row = self.graphHeight - (2 * i) - 3
           if self.graphHeight % 2 == 0 and i >= (ticks / 2): row -= 1
-          
+
           if primaryMinBound != primaryMaxBound:
             primaryVal = (primaryMaxBound - primaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1)
             if not primaryVal in (primaryMinBound, primaryMaxBound): self.addstr(row + 2, 0, "%4i" % primaryVal, primaryColor)
-          
+
           if secondaryMinBound != secondaryMaxBound:
             secondaryVal = (secondaryMaxBound - secondaryMinBound) * (self.graphHeight - row - 1) / (self.graphHeight - 1)
             if not secondaryVal in (secondaryMinBound, secondaryMaxBound): self.addstr(row + 2, graphCol + 5, "%4i" % secondaryVal, secondaryColor)
-      
+
       # creates bar graph (both primary and secondary)
       for col in range(graphCol):
         colCount = int(param.primaryCounts[self.updateInterval][col + 1]) - primaryMinBound
         colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, primaryMaxBound) - primaryMinBound))
         for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
-        
+
         colCount = int(param.secondaryCounts[self.updateInterval][col + 1]) - secondaryMinBound
         colHeight = min(self.graphHeight, self.graphHeight * colCount / (max(1, secondaryMaxBound) - secondaryMinBound))
         for row in range(colHeight): self.addstr(self.graphHeight + 1 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
-      
+
       # bottom labeling of x-axis
       intervalSec = 1 # seconds per labeling
       for i in range(len(UPDATE_INTERVALS)):
         if i == self.updateInterval: intervalSec = UPDATE_INTERVALS[i][1]
-      
+
       intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
       unitsLabel, decimalPrecision = None, 0
       for i in range((graphCol - 4) / intervalSpacing):
         loc = (i + 1) * intervalSpacing
         timeLabel = str_tools.get_time_label(loc * intervalSec, decimalPrecision)
-        
+
         if not unitsLabel: unitsLabel = timeLabel[-1]
         elif unitsLabel != timeLabel[-1]:
           # upped scale so also up precision of future measurements
@@ -474,42 +474,42 @@ class GraphPanel(panel.Panel):
         else:
           # if constrained on space then strips labeling since already provided
           timeLabel = timeLabel[:-1]
-        
+
         self.addstr(self.graphHeight + 2, 4 + loc, timeLabel, primaryColor)
         self.addstr(self.graphHeight + 2, graphCol + 10 + loc, timeLabel, secondaryColor)
-        
+
       param.draw(self, width, height) # allows current stats to modify the display
-  
+
   def addStats(self, label, stats):
     """
     Makes GraphStats instance available in the panel.
     """
-    
+
     stats._graphPanel = self
     self.stats[label] = stats
-  
+
   def getStats(self):
     """
     Provides the currently selected stats label.
     """
-    
+
     return self.currentDisplay
-  
+
   def setStats(self, label):
     """
     Sets the currently displayed stats instance, hiding panel if None.
     """
-    
+
     if label != self.currentDisplay:
       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[self.currentDisplay].isSelected = True
       else: raise ValueError("Unrecognized stats label: %s" % label)
-  
+
   def copyAttr(self, attr):
     if attr == "stats":
       # uses custom clone method to copy GraphStats instances
diff --git a/arm/graphing/resourceStats.py b/arm/graphing/resourceStats.py
index 80d23bc..d4d71c4 100644
--- a/arm/graphing/resourceStats.py
+++ b/arm/graphing/resourceStats.py
@@ -11,22 +11,22 @@ class ResourceStats(graphPanel.GraphStats):
   """
   System resource usage tracker.
   """
-  
+
   def __init__(self):
     graphPanel.GraphStats.__init__(self)
     self.queryPid = torTools.getConn().controller.get_pid(None)
-  
+
   def clone(self, newCopy=None):
     if not newCopy: newCopy = ResourceStats()
     return graphPanel.GraphStats.clone(self, newCopy)
-  
+
   def getTitle(self, width):
     return "System Resources:"
-  
+
   def getHeaderLabel(self, width, isPrimary):
     avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max(1, self.tick)
     lastAmount = self.lastPrimary if isPrimary else self.lastSecondary
-    
+
     if isPrimary:
       return "CPU (%0.1f%%, avg: %0.1f%%):" % (lastAmount, avg)
     else:
@@ -34,20 +34,20 @@ class ResourceStats(graphPanel.GraphStats):
       usageLabel = str_tools.get_size_label(lastAmount * 1048576, 1)
       avgLabel = str_tools.get_size_label(avg * 1048576, 1)
       return "Memory (%s, avg: %s):" % (usageLabel, avgLabel)
-  
+
   def eventTick(self):
     """
     Fetch the cached measurement of resource usage from the ResourceTracker.
     """
-    
+
     primary, secondary = 0, 0
     if self.queryPid:
       resourceTracker = sysTools.getResourceTracker(self.queryPid, True)
-      
+
       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
-    
+
     self._processEvent(primary, secondary)
 
diff --git a/arm/headerPanel.py b/arm/headerPanel.py
index f494025..55f1727 100644
--- a/arm/headerPanel.py
+++ b/arm/headerPanel.py
@@ -42,7 +42,7 @@ FLAG_COLORS = {"Authority": "white",  "BadExit": "red",     "BadDirectory": "red
                "Stable": "blue",      "Running": "yellow",  "Unnamed": "magenta",     "Valid": "green",
                "V2Dir": "cyan",       "V3Dir": "white"}
 
-VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "red", "recommended": "green",  
+VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "obsolete": "red", "recommended": "green",
                          "old": "red",  "unrecommended": "red",  "unknown": "cyan"}
 
 CONFIG = conf.config_dict("arm", {
@@ -59,78 +59,78 @@ class HeaderPanel(panel.Panel, threading.Thread):
           *fdUsed, fdLimit, isFdLimitEstimate
     sys/  hostname, os, version
     stat/ *%torCpu, *%armCpu, *rss, *%mem
-  
+
   * volatile parameter that'll be reset on each update
   """
-  
+
   def __init__(self, stdscr, startTime):
     panel.Panel.__init__(self, stdscr, "header", 0)
     threading.Thread.__init__(self)
     self.setDaemon(True)
-    
+
     self._isTorConnected = torTools.getConn().isAlive()
     self._lastUpdate = -1       # time the content was last revised
     self._halt = False          # terminates thread if true
     self._cond = threading.Condition()  # used for pausing the thread
-    
+
     # Time when the panel was paused or tor was stopped. This is used to
     # freeze the uptime statistic (uptime increments normally when None).
     self._haltTime = None
-    
+
     # The last arm cpu usage sampling taken. This is a tuple of the form:
     # (total arm cpu time, sampling timestamp)
-    # 
+    #
     # The initial cpu total should be zero. However, at startup the cpu time
     # in practice is often greater than the real time causing the initially
     # reported cpu usage to be over 100% (which shouldn't be possible on
     # single core systems).
-    # 
+    #
     # Setting the initial cpu total to the value at this panel's init tends to
     # give smoother results (staying in the same ballpark as the second
     # sampling) so fudging the numbers this way for now.
-    
+
     self._armCpuSampling = (sum(os.times()[:3]), startTime)
-    
+
     # Last sampling received from the ResourceTracker, used to detect when it
     # changes.
     self._lastResourceFetch = -1
-    
+
     # flag to indicate if we've already given file descriptor warnings
     self._isFdSixtyPercentWarned = False
     self._isFdNinetyPercentWarned = False
-    
+
     self.vals = {}
     self.valsLock = threading.RLock()
     self._update(True)
-    
+
     # listens for tor reload (sighup) events
     torTools.getConn().addStatusListener(self.resetListener)
-  
+
   def getHeight(self):
     """
     Provides the height of the content, which is dynamically determined by the
     panel's maximum width.
     """
-    
+
     isWide = self.getParent().getmaxyx()[1] >= MIN_DUAL_COL_WIDTH
     if self.vals["tor/orPort"]: return 4 if isWide else 6
     else: return 3 if isWide else 4
-  
+
   def sendNewnym(self):
     """
     Requests a new identity and provides a visual queue.
     """
-    
+
     torTools.getConn().sendNewnym()
-    
+
     # If we're wide then the newnym label in this panel will give an
     # indication that the signal was sent. Otherwise use a msg.
     isWide = self.getParent().getmaxyx()[1] >= MIN_DUAL_COL_WIDTH
     if not isWide: arm.popups.showMsg("Requesting a new identity", 1)
-  
+
   def handleKey(self, key):
     isKeystrokeConsumed = True
-    
+
     if key in (ord('n'), ord('N')) and torTools.getConn().isNewnymAvailable():
       self.sendNewnym()
     elif key in (ord('r'), ord('R')) and not self._isTorConnected:
@@ -146,7 +146,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       #    controller.authenticate()
       #  except (IOError, stem.SocketError), exc:
       #    controller = None
-      #    
+      #
       #    if not allowPortConnection:
       #      arm.popups.showMsg("Unable to reconnect (%s)" % exc, 3)
       #elif not allowPortConnection:
@@ -158,11 +158,11 @@ class HeaderPanel(panel.Panel, threading.Thread):
       #  # methods. We can't use the starter.py's connection function directly
       #  # due to password prompts, but we could certainly make this mess more
       #  # manageable.
-      #  
+      #
       #  try:
       #    ctlAddr, ctlPort = CONFIG["startup.interface.ipAddress"], CONFIG["startup.interface.port"]
       #    controller = Controller.from_port(ctlAddr, ctlPort)
-      #    
+      #
       #    try:
       #      controller.authenticate()
       #    except stem.connection.MissingPassword:
@@ -175,30 +175,30 @@ class HeaderPanel(panel.Panel, threading.Thread):
       #  log.notice("Reconnected to Tor's control port")
       #  arm.popups.showMsg("Tor reconnected", 1)
     else: isKeystrokeConsumed = False
-    
+
     return isKeystrokeConsumed
-  
+
   def draw(self, width, height):
     self.valsLock.acquire()
     isWide = width + 1 >= MIN_DUAL_COL_WIDTH
-    
+
     # space available for content
     if isWide:
       leftWidth = max(width / 2, 77)
       rightWidth = width - leftWidth
     else: leftWidth = rightWidth = width
-    
+
     # Line 1 / Line 1 Left (system and tor version information)
     sysNameLabel = "arm - %s" % self.vals["sys/hostname"]
     contentSpace = min(leftWidth, 40)
-    
+
     if len(sysNameLabel) + 10 <= contentSpace:
       sysTypeLabel = "%s %s" % (self.vals["sys/os"], self.vals["sys/version"])
       sysTypeLabel = uiTools.cropStr(sysTypeLabel, contentSpace - len(sysNameLabel) - 3, 4)
       self.addstr(0, 0, "%s (%s)" % (sysNameLabel, sysTypeLabel))
     else:
       self.addstr(0, 0, uiTools.cropStr(sysNameLabel, contentSpace))
-    
+
     contentSpace = leftWidth - 43
     if 7 + len(self.vals["tor/version"]) + len(self.vals["tor/versionStatus"]) <= contentSpace:
       if self.vals["tor/version"] != "Unknown":
@@ -210,14 +210,14 @@ class HeaderPanel(panel.Panel, threading.Thread):
         self.addstr(0, 43 + len(labelPrefix) + len(self.vals["tor/versionStatus"]), ")")
     elif 11 <= contentSpace:
       self.addstr(0, 43, uiTools.cropStr("Tor %s" % self.vals["tor/version"], contentSpace, 4))
-    
+
     # Line 2 / Line 2 Left (tor ip/port information)
     x, includeControlPort = 0, True
     if self.vals["tor/orPort"]:
       myAddress = "Unknown"
       if self.vals["tor/orListenAddr"]: myAddress = self.vals["tor/orListenAddr"]
       elif self.vals["tor/address"]: myAddress = self.vals["tor/address"]
-      
+
       # acting as a relay (we can assume certain parameters are set
       dirPortLabel = ", Dir Port: %s" % self.vals["tor/dirPort"] if self.vals["tor/dirPort"] != "0" else ""
       for label in (self.vals["tor/nickname"], " - " + myAddress, ":" + self.vals["tor/orPort"], dirPortLabel):
@@ -232,16 +232,16 @@ class HeaderPanel(panel.Panel, threading.Thread):
         x += 17
       else:
         statusTime = torTools.getConn().controller.get_latest_heartbeat()
-        
+
         if statusTime:
           statusTimeLabel = time.strftime("%H:%M %m/%d/%Y, ", time.localtime(statusTime))
         else: statusTimeLabel = "" # never connected to tor
-        
+
         self.addstr(1, x, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red"))
         self.addstr(1, x + 16, " (%spress r to reconnect)" % statusTimeLabel)
         x += 39 + len(statusTimeLabel)
         includeControlPort = False
-    
+
     if includeControlPort:
       if self.vals["tor/controlPort"] == "0":
         # connected via a control socket
@@ -250,7 +250,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
         if self.vals["tor/isAuthPassword"]: authType = "password"
         elif self.vals["tor/isAuthCookie"]: authType = "cookie"
         else: authType = "open"
-        
+
         if x + 19 + len(self.vals["tor/controlPort"]) + len(authType) <= leftWidth:
           authColor = "red" if authType == "open" else "green"
           self.addstr(1, x, ", Control Port (")
@@ -258,12 +258,12 @@ class HeaderPanel(panel.Panel, threading.Thread):
           self.addstr(1, x + 16 + len(authType), "): %s" % self.vals["tor/controlPort"])
         elif x + 16 + len(self.vals["tor/controlPort"]) <= leftWidth:
           self.addstr(1, 0, ", Control Port: %s" % self.vals["tor/controlPort"])
-    
+
     # Line 3 / Line 1 Right (system usage info)
     y, x = (0, leftWidth) if isWide else (2, 0)
     if self.vals["stat/rss"] != "0": memoryLabel = str_tools.get_size_label(int(self.vals["stat/rss"]))
     else: memoryLabel = "0"
-    
+
     uptimeLabel = ""
     if self.vals["tor/startTime"]:
       if self.isPaused() or not self._isTorConnected:
@@ -271,31 +271,31 @@ class HeaderPanel(panel.Panel, threading.Thread):
         uptimeLabel = str_tools.get_short_time_label(self.getPauseTime() - self.vals["tor/startTime"])
       else:
         uptimeLabel = str_tools.get_short_time_label(time.time() - self.vals["tor/startTime"])
-    
+
     sysFields = ((0, "cpu: %s%% tor, %s%% arm" % (self.vals["stat/%torCpu"], self.vals["stat/%armCpu"])),
                  (27, "mem: %s (%s%%)" % (memoryLabel, self.vals["stat/%mem"])),
                  (47, "pid: %s" % (self.vals["tor/pid"] if self._isTorConnected else "")),
                  (59, "uptime: %s" % uptimeLabel))
-    
+
     for (start, label) in sysFields:
       if start + len(label) <= rightWidth: self.addstr(y, x + start, label)
       else: break
-    
+
     if self.vals["tor/orPort"]:
       # Line 4 / Line 2 Right (fingerprint, and possibly file descriptor usage)
       y, x = (1, leftWidth) if isWide else (3, 0)
-      
+
       fingerprintLabel = uiTools.cropStr("fingerprint: %s" % self.vals["tor/fingerprint"], width)
       self.addstr(y, x, fingerprintLabel)
-      
+
       # if there's room and we're able to retrieve both the file descriptor
       # usage and limit then it might be presented
       if width - x - 59 >= 20 and self.vals["tor/fdUsed"] and self.vals["tor/fdLimit"]:
         # display file descriptor usage if we're either configured to do so or
         # running out
-        
+
         fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals["tor/fdLimit"]
-        
+
         if fdPercent >= 60 or CONFIG["features.showFdUsage"]:
           fdPercentLabel, fdPercentFormat = "%i%%" % fdPercent, curses.A_NORMAL
           if fdPercent >= 95:
@@ -304,28 +304,28 @@ class HeaderPanel(panel.Panel, threading.Thread):
             fdPercentFormat = uiTools.getColor("red")
           elif fdPercent >= 60:
             fdPercentFormat = uiTools.getColor("yellow")
-          
+
           estimateChar = "?" if self.vals["tor/isFdLimitEstimate"] else ""
           baseLabel = "file desc: %i / %i%s (" % (self.vals["tor/fdUsed"], self.vals["tor/fdLimit"], estimateChar)
-          
+
           self.addstr(y, x + 59, baseLabel)
           self.addstr(y, x + 59 + len(baseLabel), fdPercentLabel, fdPercentFormat)
           self.addstr(y, x + 59 + len(baseLabel) + len(fdPercentLabel), ")")
-      
+
       # Line 5 / Line 3 Left (flags)
       if self._isTorConnected:
         y, x = (2 if isWide else 4, 0)
         self.addstr(y, x, "flags: ")
         x += 7
-        
+
         if len(self.vals["tor/flags"]) > 0:
           for i in range(len(self.vals["tor/flags"])):
             flag = self.vals["tor/flags"][i]
             flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
-            
+
             self.addstr(y, x, flag, curses.A_BOLD | uiTools.getColor(flagColor))
             x += len(flag)
-            
+
             if i < len(self.vals["tor/flags"]) - 1:
               self.addstr(y, x, ", ")
               x += 2
@@ -337,33 +337,33 @@ class HeaderPanel(panel.Panel, threading.Thread):
         statusTimeLabel = time.strftime("%H:%M %m/%d/%Y", time.localtime(statusTime))
         self.addstr(y, 0, "Tor Disconnected", curses.A_BOLD | uiTools.getColor("red"))
         self.addstr(y, 16, " (%s) - press r to reconnect" % statusTimeLabel)
-      
+
       # Undisplayed / Line 3 Right (exit policy)
       if isWide:
         exitPolicy = self.vals["tor/exitPolicy"]
-        
+
         # adds note when default exit policy is appended
         if exitPolicy == "": exitPolicy = "<default>"
         elif not exitPolicy.endswith((" *:*", " *")): exitPolicy += ", <default>"
-        
+
         self.addstr(2, leftWidth, "exit policy: ")
         x = leftWidth + 13
-        
+
         # color codes accepts to be green, rejects to be red, and default marker to be cyan
         isSimple = len(exitPolicy) > rightWidth - 13
         policies = exitPolicy.split(", ")
         for i in range(len(policies)):
           policy = policies[i].strip()
           policyLabel = policy.replace("accept", "").replace("reject", "").strip() if isSimple else policy
-          
+
           policyColor = "white"
           if policy.startswith("accept"): policyColor = "green"
           elif policy.startswith("reject"): policyColor = "red"
           elif policy.startswith("<default>"): policyColor = "cyan"
-          
+
           self.addstr(2, x, policyLabel, curses.A_BOLD | uiTools.getColor(policyColor))
           x += len(policyLabel)
-          
+
           if i < len(policies) - 1:
             self.addstr(2, x, ", ")
             x += 2
@@ -372,34 +372,34 @@ class HeaderPanel(panel.Panel, threading.Thread):
       if isWide:
         conn = torTools.getConn()
         newnymWait = conn.getNewnymWait()
-        
+
         msg = "press 'n' for a new identity"
         if newnymWait > 0:
           pluralLabel = "s" if newnymWait > 1 else ""
           msg = "building circuits, available again in %i second%s" % (newnymWait, pluralLabel)
-        
+
         self.addstr(1, leftWidth, msg)
-    
+
     self.valsLock.release()
-  
+
   def getPauseTime(self):
     """
     Provides the time Tor stopped if it isn't running. Otherwise this is the
     time we were last paused.
     """
-    
+
     if self._haltTime: return self._haltTime
     else: return panel.Panel.getPauseTime(self)
-  
+
   def run(self):
     """
     Keeps stats updated, checking for new information at a set rate.
     """
-    
+
     lastDraw = time.time() - 1
     while not self._halt:
       currentTime = time.time()
-      
+
       if self.isPaused() or currentTime - lastDraw < 1 or not self._isTorConnected:
         self._cond.acquire()
         if not self._halt: self._cond.wait(0.2)
@@ -409,41 +409,41 @@ class HeaderPanel(panel.Panel, threading.Thread):
         # a new resource usage sampling (the most dynamic stat) or its been
         # twenty seconds since last fetched (so we still refresh occasionally
         # when resource fetches fail).
-        # 
+        #
         # Otherwise, just redraw the panel to change the uptime field.
-        
+
         isChanged = False
         if self.vals["tor/pid"]:
           resourceTracker = sysTools.getResourceTracker(self.vals["tor/pid"])
           isChanged = self._lastResourceFetch != resourceTracker.getRunCount()
-        
+
         if isChanged or currentTime - self._lastUpdate >= 20:
           self._update()
-        
+
         self.redraw(True)
         lastDraw += 1
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self._cond.acquire()
     self._halt = True
     self._cond.notifyAll()
     self._cond.release()
-  
+
   def resetListener(self, controller, eventType, _):
     """
     Updates static parameters on tor reload (sighup) events.
     """
-    
+
     if eventType in (State.INIT, State.RESET):
       initialHeight = self.getHeight()
       self._isTorConnected = True
       self._haltTime = None
       self._update(True)
-      
+
       if self.getHeight() != initialHeight:
         # We're toggling between being a relay and client, causing the height
         # of this panel to change. Redraw all content so we don't get
@@ -457,19 +457,19 @@ class HeaderPanel(panel.Panel, threading.Thread):
       self._haltTime = time.time()
       self._update()
       self.redraw(True)
-  
+
   def _update(self, setStatic=False):
     """
     Updates stats in the vals mapping. By default this just revises volatile
     attributes.
-    
+
     Arguments:
       setStatic - resets all parameters, including relatively static values
     """
-    
+
     self.valsLock.acquire()
     conn = torTools.getConn()
-    
+
     if setStatic:
       # version is truncated to first part, for instance:
       # 0.2.2.13-alpha (git-feb8c1b5f67f2c6f) -> 0.2.2.13-alpha
@@ -482,10 +482,10 @@ class HeaderPanel(panel.Panel, threading.Thread):
       self.vals["tor/socketPath"] = conn.getOption("ControlSocket", "")
       self.vals["tor/isAuthPassword"] = conn.getOption("HashedControlPassword", None) != None
       self.vals["tor/isAuthCookie"] = conn.getOption("CookieAuthentication", None) == "1"
-      
+
       # orport is reported as zero if unset
       if self.vals["tor/orPort"] == "0": self.vals["tor/orPort"] = ""
-      
+
       # overwrite address if ORListenAddress is set (and possibly orPort too)
       self.vals["tor/orListenAddr"] = ""
       listenAddr = conn.getOption("ORListenAddress", None)
@@ -496,30 +496,30 @@ class HeaderPanel(panel.Panel, threading.Thread):
           self.vals["tor/orPort"] = listenAddr[listenAddr.find(":") + 1:]
         else:
           self.vals["tor/orListenAddr"] = listenAddr
-      
+
       # fetch exit policy (might span over multiple lines)
       policyEntries = []
       for exitPolicy in conn.getOption("ExitPolicy", [], True):
         policyEntries += [policy.strip() for policy in exitPolicy.split(",")]
       self.vals["tor/exitPolicy"] = ", ".join(policyEntries)
-      
+
       # file descriptor limit for the process, if this can't be determined
       # then the limit is None
       fdLimit, fdIsEstimate = conn.getMyFileDescriptorLimit()
       self.vals["tor/fdLimit"] = fdLimit
       self.vals["tor/isFdLimitEstimate"] = fdIsEstimate
-      
+
       # system information
       unameVals = os.uname()
       self.vals["sys/hostname"] = unameVals[1]
       self.vals["sys/os"] = unameVals[0]
       self.vals["sys/version"] = unameVals[2]
-      
+
       self.vals["tor/pid"] = conn.controller.get_pid("")
-      
+
       startTime = conn.getStartTime()
       self.vals["tor/startTime"] = startTime if startTime else ""
-      
+
       # reverts volatile parameters to defaults
       self.vals["tor/fingerprint"] = "Unknown"
       self.vals["tor/flags"] = []
@@ -528,15 +528,15 @@ class HeaderPanel(panel.Panel, threading.Thread):
       self.vals["stat/%armCpu"] = "0"
       self.vals["stat/rss"] = "0"
       self.vals["stat/%mem"] = "0"
-    
+
     # sets volatile parameters
     # TODO: This can change, being reported by STATUS_SERVER -> EXTERNAL_ADDRESS
     # events. Introduce caching via torTools?
     self.vals["tor/address"] = conn.getInfo("address", "")
-    
+
     self.vals["tor/fingerprint"] = conn.getInfo("fingerprint", self.vals["tor/fingerprint"])
     self.vals["tor/flags"] = conn.getMyFlags(self.vals["tor/flags"])
-    
+
     # Updates file descriptor usage and logs if the usage is high. If we don't
     # have a known limit or it's obviously faulty (being lower than our
     # current usage) then omit file descriptor functionality.
@@ -544,12 +544,12 @@ class HeaderPanel(panel.Panel, threading.Thread):
       fdUsed = conn.getMyFileDescriptorUsage()
       if fdUsed and fdUsed <= self.vals["tor/fdLimit"]: self.vals["tor/fdUsed"] = fdUsed
       else: self.vals["tor/fdUsed"] = 0
-    
+
     if self.vals["tor/fdUsed"] and self.vals["tor/fdLimit"]:
       fdPercent = 100 * self.vals["tor/fdUsed"] / self.vals["tor/fdLimit"]
       estimatedLabel = " estimated" if self.vals["tor/isFdLimitEstimate"] else ""
       msg = "Tor's%s file descriptor usage is at %i%%." % (estimatedLabel, fdPercent)
-      
+
       if fdPercent >= 90 and not self._isFdNinetyPercentWarned:
         self._isFdSixtyPercentWarned, self._isFdNinetyPercentWarned = True, True
         msg += " If you run out Tor will be unable to continue functioning."
@@ -557,11 +557,11 @@ class HeaderPanel(panel.Panel, threading.Thread):
       elif fdPercent >= 60 and not self._isFdSixtyPercentWarned:
         self._isFdSixtyPercentWarned = True
         log.notice(msg)
-    
+
     # ps or proc derived resource usage stats
     if self.vals["tor/pid"]:
       resourceTracker = sysTools.getResourceTracker(self.vals["tor/pid"])
-      
+
       if resourceTracker.lastQueryFailed():
         self.vals["stat/%torCpu"] = "0"
         self.vals["stat/rss"] = "0"
@@ -572,10 +572,10 @@ class HeaderPanel(panel.Panel, threading.Thread):
         self.vals["stat/%torCpu"] = "%0.1f" % (100 * cpuUsage)
         self.vals["stat/rss"] = str(memUsage)
         self.vals["stat/%mem"] = "%0.1f" % (100 * memUsagePercent)
-    
+
     # determines the cpu time for the arm process (including user and system
     # time of both the primary and child processes)
-    
+
     totalArmCpuTime, currentTime = sum(os.times()[:3]), time.time()
     armCpuDelta = totalArmCpuTime - self._armCpuSampling[0]
     armTimeDelta = currentTime - self._armCpuSampling[1]
@@ -583,7 +583,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     sysCallCpuTime = sysTools.getSysCpuUsage()
     self.vals["stat/%armCpu"] = "%0.1f" % (100 * (pythonCpuTime + sysCallCpuTime))
     self._armCpuSampling = (totalArmCpuTime, currentTime)
-    
+
     self._lastUpdate = currentTime
     self.valsLock.release()
 
diff --git a/arm/logPanel.py b/arm/logPanel.py
index e9b3d06..101a7e8 100644
--- a/arm/logPanel.py
+++ b/arm/logPanel.py
@@ -97,11 +97,11 @@ def daysSince(timestamp=None):
   """
   Provides the number of days since the epoch converted to local time (rounded
   down).
-  
+
   Arguments:
     timestamp - unix timestamp to convert, current time if undefined
   """
-  
+
   if timestamp == None: timestamp = time.time()
   return int((timestamp - TIMEZONE_OFFSET) / 86400)
 
@@ -115,18 +115,18 @@ def expandEvents(eventAbbr):
   DINWE - runlevel and higher
   12345 - arm/stem runlevel and higher (ARM_DEBUG - ARM_ERR)
   Raises ValueError with invalid input if any part isn't recognized.
-  
+
   Examples:
   "inUt" -> ["INFO", "NOTICE", "UNKNOWN", "STREAM_BW"]
   "N4" -> ["NOTICE", "WARN", "ERR", "ARM_WARN", "ARM_ERR"]
   "cfX" -> []
-  
+
   Arguments:
     eventAbbr - flags to be parsed to event types
   """
-  
+
   expandedEvents, invalidFlags = set(), ""
-  
+
   for flag in eventAbbr:
     if flag == "A":
       armRunlevels = ["ARM_" + runlevel for runlevel in log.Runlevel]
@@ -142,7 +142,7 @@ def expandEvents(eventAbbr):
       elif flag in "N3": runlevelIndex = 3
       elif flag in "W4": runlevelIndex = 4
       elif flag in "E5": runlevelIndex = 5
-      
+
       if flag in "DINWE":
         runlevelSet = [runlevel for runlevel in list(log.Runlevel)[runlevelIndex:]]
         expandedEvents = expandedEvents.union(set(runlevelSet))
@@ -155,7 +155,7 @@ def expandEvents(eventAbbr):
       expandedEvents.add(TOR_EVENT_TYPES[flag])
     else:
       invalidFlags += flag
-  
+
   if invalidFlags: raise ValueError(invalidFlags)
   else: return expandedEvents
 
@@ -165,9 +165,9 @@ def getMissingEventTypes():
   doesn't. This provides an empty list if no event types are missing, and None
   if the GETINFO query fails.
   """
-  
+
   torEventTypes = torTools.getConn().getInfo("events/names", None)
-  
+
   if torEventTypes:
     torEventTypes = torEventTypes.split(" ")
     armEventTypes = TOR_EVENT_TYPES.values()
@@ -178,10 +178,10 @@ def loadLogMessages():
   """
   Fetches a mapping of common log messages to their runlevels from the config.
   """
-  
+
   global COMMON_LOG_MESSAGES
   armConf = conf.get_config("arm")
-  
+
   COMMON_LOG_MESSAGES = {}
   for confKey in armConf.keys():
     if confKey.startswith("msg."):
@@ -195,31 +195,31 @@ def getLogFileEntries(runlevels, readLimit = None, addLimit = None):
   a list of log entries (ordered newest to oldest). Limiting the number of read
   entries is suggested to avoid parsing everything from logs in the GB and TB
   range.
-  
+
   Arguments:
     runlevels - event types (DEBUG - ERR) to be returned
     readLimit - max lines of the log file that'll be read (unlimited if None)
     addLimit  - maximum entries to provide back (unlimited if None)
   """
-  
+
   startTime = time.time()
   if not runlevels: return []
-  
+
   # checks tor's configuration for the log file's location (if any exists)
   loggingTypes, loggingLocation = None, None
   for loggingEntry in torTools.getConn().getOption("Log", [], True):
     # looks for an entry like: notice file /var/log/tor/notices.log
     entryComp = loggingEntry.split()
-    
+
     if entryComp[1] == "file":
       loggingTypes, loggingLocation = entryComp[0], entryComp[2]
       break
-  
+
   if not loggingLocation: return []
-  
+
   # includes the prefix for tor paths
   loggingLocation = torTools.get_chroot() + loggingLocation
-  
+
   # if the runlevels argument is a superset of the log file then we can
   # limit the read contents to the addLimit
   runlevels = list(log.Runlevel)
@@ -233,16 +233,16 @@ def getLogFileEntries(runlevels, readLimit = None, addLimit = None):
     else:
       sIndex = runlevels.index(loggingTypes)
       logFileRunlevels = runlevels[sIndex:]
-    
+
     # checks if runlevels we're reporting are a superset of the file's contents
     isFileSubset = True
     for runlevelType in logFileRunlevels:
       if runlevelType not in runlevels:
         isFileSubset = False
         break
-    
+
     if isFileSubset: readLimit = addLimit
-  
+
   # tries opening the log file, cropping results to avoid choking on huge logs
   lines = []
   try:
@@ -255,33 +255,33 @@ def getLogFileEntries(runlevels, readLimit = None, addLimit = None):
       logFile.close()
   except IOError:
     log.warn("Unable to read tor's log file: %s" % loggingLocation)
-  
+
   if not lines: return []
-  
+
   loggedEvents = []
   currentUnixTime, currentLocalTime = time.time(), time.localtime()
   for i in range(len(lines) - 1, -1, -1):
     line = lines[i]
-    
+
     # entries look like:
     # Jul 15 18:29:48.806 [notice] Parsing GEOIP file.
     lineComp = line.split()
-    
+
     # Checks that we have all the components we expect. This could happen if
     # we're either not parsing a tor log or in weird edge cases (like being
     # out of disk space)
-    
+
     if len(lineComp) < 4: continue
-    
+
     eventType = lineComp[3][1:-1].upper()
-    
+
     if eventType in runlevels:
       # converts timestamp to unix time
       timestamp = " ".join(lineComp[:3])
-      
+
       # strips the decimal seconds
       if "." in timestamp: timestamp = timestamp[:timestamp.find(".")]
-      
+
       # Ignoring wday and yday since they aren't used.
       #
       # Pretend the year is 2012, because 2012 is a leap year, and parsing a
@@ -290,24 +290,24 @@ def getLogFileEntries(runlevels, readLimit = None, addLimit = None):
       # might be parsing old logs which didn't get rotated.
       #
       # https://trac.torproject.org/projects/tor/ticket/5265
-      
+
       timestamp = "2012 " + timestamp
       eventTimeComp = list(time.strptime(timestamp, "%Y %b %d %H:%M:%S"))
       eventTimeComp[8] = currentLocalTime.tm_isdst
       eventTime = time.mktime(eventTimeComp) # converts local to unix time
-      
+
       # The above is gonna be wrong if the logs are for the previous year. If
       # the event's in the future then correct for this.
       if eventTime > currentUnixTime + 60:
         eventTimeComp[0] -= 1
         eventTime = time.mktime(eventTimeComp)
-      
+
       eventMsg = " ".join(lineComp[4:])
       loggedEvents.append(LogEntry(eventTime, eventType, eventMsg, RUNLEVEL_EVENT_COLOR[eventType]))
-    
+
     if "opening log file" in line:
       break # this entry marks the start of this tor instance
-  
+
   if addLimit: loggedEvents = loggedEvents[:addLimit]
   log.info("Read %i entries from tor's log file: %s (read limit: %i, runtime: %0.3f)" % (len(loggedEvents), loggingLocation, readLimit, time.time() - startTime))
   return loggedEvents
@@ -318,36 +318,36 @@ def getDaybreaks(events, ignoreTimeForCache = False):
   whenever the date changed between log entries (or since the most recent
   event). The timestamp matches the beginning of the day for the following
   entry.
-  
+
   Arguments:
     events             - chronologically ordered listing of events
     ignoreTimeForCache - skips taking the day into consideration for providing
                          cached results if true
   """
-  
+
   global CACHED_DAYBREAKS_ARGUMENTS, CACHED_DAYBREAKS_RESULT
   if not events: return []
-  
+
   newListing = []
   currentDay = daysSince()
   lastDay = currentDay
-  
+
   if CACHED_DAYBREAKS_ARGUMENTS[0] == events and \
     (ignoreTimeForCache or CACHED_DAYBREAKS_ARGUMENTS[1] == currentDay):
     return list(CACHED_DAYBREAKS_RESULT)
-  
+
   for entry in events:
     eventDay = daysSince(entry.timestamp)
     if eventDay != lastDay:
       markerTimestamp = (eventDay * 86400) + TIMEZONE_OFFSET
       newListing.append(LogEntry(markerTimestamp, DAYBREAK_EVENT, "", "white"))
-    
+
     newListing.append(entry)
     lastDay = eventDay
-  
+
   CACHED_DAYBREAKS_ARGUMENTS = (list(events), currentDay)
   CACHED_DAYBREAKS_RESULT = list(newListing)
-  
+
   return newListing
 
 def getDuplicates(events):
@@ -356,39 +356,39 @@ def getDuplicates(events):
   log entry and count of duplicates following it. Entries in different days are
   not considered to be duplicates. This times out, returning None if it takes
   longer than DEDUPLICATION_TIMEOUT.
-  
+
   Arguments:
     events - chronologically ordered listing of events
   """
-  
+
   global CACHED_DUPLICATES_ARGUMENTS, CACHED_DUPLICATES_RESULT
   if CACHED_DUPLICATES_ARGUMENTS == events:
     return list(CACHED_DUPLICATES_RESULT)
-  
+
   # loads common log entries from the config if they haven't been
   if COMMON_LOG_MESSAGES == None: loadLogMessages()
-  
+
   startTime = time.time()
   eventsRemaining = list(events)
   returnEvents = []
-  
+
   while eventsRemaining:
     entry = eventsRemaining.pop(0)
     duplicateIndices = isDuplicate(entry, eventsRemaining, True)
-    
+
     # checks if the call timeout has been reached
     if (time.time() - startTime) > DEDUPLICATION_TIMEOUT / 1000.0:
       return None
-    
+
     # drops duplicate entries
     duplicateIndices.reverse()
     for i in duplicateIndices: del eventsRemaining[i]
-    
+
     returnEvents.append((entry, len(duplicateIndices)))
-  
+
   CACHED_DUPLICATES_ARGUMENTS = list(events)
   CACHED_DUPLICATES_RESULT = list(returnEvents)
-  
+
   return returnEvents
 
 def isDuplicate(event, eventSet, getDuplicates = False):
@@ -396,22 +396,22 @@ def isDuplicate(event, eventSet, getDuplicates = False):
   True if the event is a duplicate for something in the eventSet, false
   otherwise. If the getDuplicates flag is set this provides the indices of
   the duplicates instead.
-  
+
   Arguments:
     event         - event to search for duplicates of
     eventSet      - set to look for the event in
     getDuplicates - instead of providing back a boolean this gives a list of
                     the duplicate indices in the eventSet
   """
-  
+
   duplicateIndices = []
   for i in range(len(eventSet)):
     forwardEntry = eventSet[i]
-    
+
     # if showing dates then do duplicate detection for each day, rather
     # than globally
     if forwardEntry.type == DAYBREAK_EVENT: break
-    
+
     if event.type == forwardEntry.type:
       isDuplicate = False
       if event.msg == forwardEntry.msg: isDuplicate = True
@@ -423,13 +423,13 @@ def isDuplicate(event, eventSet, getDuplicates = False):
             isDuplicate = commonMsg[1:] in event.msg and commonMsg[1:] in forwardEntry.msg
           else:
             isDuplicate = event.msg.startswith(commonMsg) and forwardEntry.msg.startswith(commonMsg)
-          
+
           if isDuplicate: break
-      
+
       if isDuplicate:
         if getDuplicates: duplicateIndices.append(i)
         else: return True
-  
+
   if getDuplicates: return duplicateIndices
   else: return False
 
@@ -441,32 +441,32 @@ class LogEntry():
     msg       - message that was logged
     color     - color of the log entry
   """
-  
+
   def __init__(self, timestamp, eventType, msg, color):
     self.timestamp = timestamp
     self.type = eventType
     self.msg = msg
     self.color = color
     self._displayMessage = None
-  
+
   def getDisplayMessage(self, includeDate = False):
     """
     Provides the entry's message for the log.
-    
+
     Arguments:
       includeDate - appends the event's date to the start of the message
     """
-    
+
     if includeDate:
       # not the common case so skip caching
       entryTime = time.localtime(self.timestamp)
       timeLabel =  "%i/%i/%i %02i:%02i:%02i" % (entryTime[1], entryTime[2], entryTime[0], entryTime[3], entryTime[4], entryTime[5])
       return "%s [%s] %s" % (timeLabel, self.type, self.msg)
-    
+
     if not self._displayMessage:
       entryTime = time.localtime(self.timestamp)
       self._displayMessage = "%02i:%02i:%02i [%s] %s" % (entryTime[3], entryTime[4], entryTime[5], self.type, self.msg)
-    
+
     return self._displayMessage
 
 class LogPanel(panel.Panel, threading.Thread, logging.Handler):
@@ -474,85 +474,85 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
   Listens for and displays tor, arm, and stem events. This can prepopulate
   from tor's log file if it exists.
   """
-  
+
   def __init__(self, stdscr, loggedEvents):
     panel.Panel.__init__(self, stdscr, "log", 0)
     logging.Handler.__init__(self, level = log.logging_level(log.DEBUG))
-    
+
     self.setFormatter(logging.Formatter(
       fmt = '%(asctime)s [%(levelname)s] %(message)s',
       datefmt = '%m/%d/%Y %H:%M:%S'),
     )
-    
+
     threading.Thread.__init__(self)
     self.setDaemon(True)
-    
+
     # Make sure that the msg.* messages are loaded. Lazy loading it later is
     # fine, but this way we're sure it happens before warning about unused
     # config options.
     loadLogMessages()
-    
+
     # regex filters the user has defined
     self.filterOptions = []
-    
+
     for filter in CONFIG["features.log.regex"]:
       # checks if we can't have more filters
       if len(self.filterOptions) >= MAX_REGEX_FILTERS: break
-      
+
       try:
         re.compile(filter)
         self.filterOptions.append(filter)
       except re.error, exc:
         log.notice("Invalid regular expression pattern (%s): %s" % (exc, filter))
-    
+
     self.loggedEvents = [] # needs to be set before we receive any events
-    
+
     # restricts the input to the set of events we can listen to, and
     # configures the controller to liten to them
     self.loggedEvents = self.setEventListening(loggedEvents)
-    
+
     self.setPauseAttr("msgLog")         # tracks the message log when we're paused
     self.msgLog = []                    # log entries, sorted by the timestamp
     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._lastUpdate = -1               # time the content was last revised
     self._halt = False                  # terminates thread if true
     self._cond = threading.Condition()  # used for pausing/resuming the thread
-    
+
     # restricts concurrent write access to attributes used to draw the display
     # and pausing:
     # msgLog, loggedEvents, regexFilter, scroll
     self.valsLock = threading.RLock()
-    
+
     # cached parameters (invalidated if arguments for them change)
     # last set of events we've drawn with
     self._lastLoggedEvents = []
-    
+
     # _getTitle (args: loggedEvents, regexFilter pattern, width)
     self._titleCache = None
     self._titleArgs = (None, None, None)
-    
+
     self.reprepopulateEvents()
-    
+
     # leaving lastContentHeight as being too low causes initialization problems
     self.lastContentHeight = len(self.msgLog)
-    
+
     # adds listeners for tor and stem events
     conn = torTools.getConn()
     conn.addStatusListener(self._resetListener)
-    
+
     # opens log file if we'll be saving entries
     if CONFIG["features.logFile"]:
       logPath = CONFIG["features.logFile"]
-      
+
       try:
         # make dir if the path doesn't already exist
         baseDir = os.path.dirname(logPath)
         if not os.path.exists(baseDir): os.makedirs(baseDir)
-        
+
         self.logFile = open(logPath, "a")
         log.notice("arm %s opening log file (%s)" % (__version__, logPath))
       except IOError, exc:
@@ -561,29 +561,29 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       except OSError, exc:
         log.error("Unable to write to log file: %s" % exc)
         self.logFile = None
-    
+
     stem_logger = log.get_logger()
     stem_logger.addHandler(self)
-  
+
   def emit(self, record):
     if record.levelname == "ERROR":
       record.levelname = "ERR"
     elif record.levelname == "WARNING":
       record.levelname = "WARN"
-    
+
     eventColor = RUNLEVEL_EVENT_COLOR[record.levelname]
     self.registerEvent(LogEntry(int(record.created), "ARM_%s" % record.levelname, record.msg, eventColor))
-  
+
   def reprepopulateEvents(self):
     """
     Clears the event log and repopulates it from the arm and tor backlogs.
     """
-    
+
     self.valsLock.acquire()
-    
+
     # clears the event log
     self.msgLog = []
-    
+
     # fetches past tor events from log file, if available
     if CONFIG["features.log.prepopulate"]:
       setRunlevels = list(set.intersection(set(self.loggedEvents), set(list(log.Runlevel))))
@@ -591,32 +591,32 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       addLimit = CONFIG["cache.logPanel.size"]
       for entry in getLogFileEntries(setRunlevels, readLimit, addLimit):
         self.msgLog.append(entry)
-    
+
     # crops events that are either too old, or more numerous than the caching size
     self._trimEvents(self.msgLog)
-    
+
     self.valsLock.release()
-  
+
   def setDuplicateVisability(self, isVisible):
     """
     Sets if duplicate log entries are collaped or expanded.
-    
+
     Arguments:
       isVisible - if true all log entries are shown, otherwise they're
                   deduplicated
     """
-    
+
     armConf = conf.get_config("arm")
     armConf.set("features.log.showDuplicateEntries", str(isVisible))
-  
+
   def registerTorEvent(self, event):
     """
     Translates a stem.response.event.Event instance into a LogEvent, and calls
     registerEvent().
     """
-    
+
     msg, color = ' '.join(str(event).split(' ')[1:]), "white"
-    
+
     if isinstance(event, events.CircuitEvent):
       color = "yellow"
     elif isinstance(event, events.BandwidthEvent):
@@ -633,22 +633,22 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       color = "yellow"
     elif not event.type in TOR_EVENT_TYPES.values():
       color = "red" # unknown event type
-    
+
     self.registerEvent(LogEntry(event.arrived_at, event.type, msg, color))
-  
+
   def registerEvent(self, event):
     """
     Notes event and redraws log. If paused it's held in a temporary buffer.
-    
+
     Arguments:
       event - LogEntry for the event that occurred
     """
-    
+
     if not event.type in self.loggedEvents: return
-    
+
     # strips control characters to avoid screwing up the terminal
     event.msg = uiTools.getPrintable(event.msg)
-    
+
     # note event in the log file if we're saving them
     if self.logFile:
       try:
@@ -657,74 +657,74 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       except IOError, exc:
         log.error("Unable to write to log file: %s" % exc.strerror)
         self.logFile = None
-    
+
     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 setLoggedEvents(self, eventTypes):
     """
     Sets the event types recognized by the panel.
-    
+
     Arguments:
       eventTypes - event types to be logged
     """
-    
+
     if eventTypes == self.loggedEvents: return
     self.valsLock.acquire()
-    
+
     # configures the controller to listen for these tor events, and provides
     # back a subset without anything we're failing to listen to
     setTypes = self.setEventListening(eventTypes)
     self.loggedEvents = setTypes
     self.redraw(True)
     self.valsLock.release()
-  
+
   def getFilter(self):
     """
     Provides our currently selected regex filter.
     """
-    
+
     return self.filterOptions[0] if self.regexFilter else None
-  
+
   def setFilter(self, logFilter):
     """
     Filters log entries according to the given regular expression.
-    
+
     Arguments:
       logFilter - regular expression used to determine which messages are
                   shown, None if no filter should be applied
     """
-    
+
     if logFilter == self.regexFilter: return
-    
+
     self.valsLock.acquire()
     self.regexFilter = logFilter
     self.redraw(True)
     self.valsLock.release()
-  
+
   def makeFilterSelection(self, selectedOption):
     """
     Makes the given filter selection, applying it to the log and reorganizing
     our filter selection.
-    
+
     Arguments:
       selectedOption - regex filter we've already added, None if no filter
                        should be applied
     """
-    
+
     if selectedOption:
       try:
         self.setFilter(re.compile(selectedOption))
-        
+
         # move selection to top
         self.filterOptions.remove(selectedOption)
         self.filterOptions.insert(0, selectedOption)
@@ -733,14 +733,14 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
         log.warn("Invalid regular expression ('%s': %s) - removing from listing" % (selectedOption, exc))
         self.filterOptions.remove(selectedOption)
     else: self.setFilter(None)
-  
+
   def showFilterPrompt(self):
     """
     Prompts the user to add a new regex filter.
     """
-    
+
     regexInput = popups.inputPrompt("Regular expression: ")
-    
+
     if regexInput:
       try:
         self.setFilter(re.compile(regexInput))
@@ -748,27 +748,27 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
         self.filterOptions.insert(0, regexInput)
       except re.error, exc:
         popups.showMsg("Unable to compile expression: %s" % exc, 2)
-  
+
   def showEventSelectionPrompt(self):
     """
     Prompts the user to select the events being listened for.
     """
-    
+
     # allow user to enter new types of events to log - unchanged if left blank
     popup, width, height = popups.init(11, 80)
-    
+
     if popup:
       try:
         # displays the available flags
         popup.win.box()
         popup.addstr(0, 0, "Event Types:", curses.A_STANDOUT)
         eventLines = EVENT_LISTING.split("\n")
-        
+
         for i in range(len(eventLines)):
           popup.addstr(i + 1, 1, eventLines[i][6:])
-        
+
         popup.win.refresh()
-        
+
         userInput = popups.inputPrompt("Events to log: ")
         if userInput:
           userInput = userInput.replace(' ', '') # strips spaces
@@ -776,69 +776,69 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
           except ValueError, exc:
             popups.showMsg("Invalid flags: %s" % str(exc), 2)
       finally: popups.finalize()
-  
+
   def showSnapshotPrompt(self):
     """
     Lets user enter a path to take a snapshot, canceling if left blank.
     """
-    
+
     pathInput = popups.inputPrompt("Path to save log snapshot: ")
-    
+
     if pathInput:
       try:
         self.saveSnapshot(pathInput)
         popups.showMsg("Saved: %s" % pathInput, 2)
       except IOError, exc:
         popups.showMsg("Unable to save snapshot: %s" % exc.strerror, 2)
-  
+
   def clear(self):
     """
     Clears the contents of the event log.
     """
-    
+
     self.valsLock.acquire()
     self.msgLog = []
     self.redraw(True)
     self.valsLock.release()
-  
+
   def saveSnapshot(self, path):
     """
     Saves the log events currently being displayed to the given path. This
     takes filers into account. This overwrites the file if it already exists,
     and raises an IOError if there's a problem.
-    
+
     Arguments:
       path - path where to save the log snapshot
     """
-    
+
     path = os.path.abspath(os.path.expanduser(path))
-    
+
     # make dir if the path doesn't already exist
     baseDir = os.path.dirname(path)
-    
+
     try:
       if not os.path.exists(baseDir): os.makedirs(baseDir)
     except OSError, exc:
       raise IOError("unable to make directory '%s'" % baseDir)
-    
+
     snapshotFile = open(path, "w")
     self.valsLock.acquire()
     try:
       for entry in self.msgLog:
         isVisible = not self.regexFilter or self.regexFilter.search(entry.getDisplayMessage())
         if isVisible: snapshotFile.write(entry.getDisplayMessage(True) + "\n")
-      
+
       self.valsLock.release()
     except Exception, exc:
       self.valsLock.release()
       raise exc
-  
+
   def handleKey(self, key):
     isKeystrokeConsumed = True
     if uiTools.isScrollKey(key):
       pageHeight = self.getPreferredSize()[0] - 1
       newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self.lastContentHeight)
-      
+
       if self.scroll != newScroll:
         self.valsLock.acquire()
         self.scroll = newScroll
@@ -858,13 +858,13 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       # for syntax see: http://docs.python.org/library/re.html#regular-expression-syntax
       options = ["None"] + self.filterOptions + ["New..."]
       oldSelection = 0 if not self.regexFilter else 1
-      
+
       # does all activity under a curses lock to prevent redraws when adding
       # new filters
       panel.CURSES_LOCK.acquire()
       try:
         selection = popups.showMenu("Log Filter:", options, oldSelection)
-        
+
         # applies new setting
         if selection == 0:
           self.setFilter(None)
@@ -875,16 +875,16 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
           self.makeFilterSelection(self.filterOptions[selection - 1])
       finally:
         panel.CURSES_LOCK.release()
-      
+
       if len(self.filterOptions) > MAX_REGEX_FILTERS: del self.filterOptions[MAX_REGEX_FILTERS:]
     elif key == ord('e') or key == ord('E'):
       self.showEventSelectionPrompt()
     elif key == ord('a') or key == ord('A'):
       self.showSnapshotPrompt()
     else: isKeystrokeConsumed = False
-    
+
     return isKeystrokeConsumed
-  
+
   def getHelp(self):
     options = []
     options.append(("up arrow", "scroll log up a line", None))
@@ -895,57 +895,57 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     options.append(("u", "duplicate log entries", "visible" if CONFIG["features.log.showDuplicateEntries"] else "hidden"))
     options.append(("c", "clear event log", None))
     return options
-  
+
   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(currentLog), time.time()
-    
+
     # draws the top label
     if self.isTitleVisible():
       self.addstr(0, 0, self._getTitle(width), curses.A_STANDOUT)
-    
+
     # restricts scroll location to valid bounds
     self.scroll = max(0, min(self.scroll, self.lastContentHeight - height + 1))
-    
+
     # draws left-hand scroll bar if content's longer than the height
     msgIndent, dividerIndent = 1, 0 # offsets for scroll bar
     isScrollBarVisible = self.lastContentHeight > height - 1
     if isScrollBarVisible:
       msgIndent, dividerIndent = 3, 2
       self.addScrollBar(self.scroll, self.scroll + height - 1, self.lastContentHeight, 1)
-    
+
     # draws log entries
     lineCount = 1 - self.scroll
     seenFirstDateDivider = False
     dividerAttr, duplicateAttr = curses.A_BOLD | uiTools.getColor("yellow"), curses.A_BOLD | uiTools.getColor("green")
-    
+
     isDatesShown = self.regexFilter == None and CONFIG["features.log.showDateDividers"]
     eventLog = getDaybreaks(currentLog, self.isPaused()) if isDatesShown else list(currentLog)
     if not CONFIG["features.log.showDuplicateEntries"]:
       deduplicatedLog = getDuplicates(eventLog)
-      
+
       if deduplicatedLog == None:
         log.warn("Deduplication took too long. Its current implementation has difficulty handling large logs so disabling it to keep the interface responsive.")
         self.setDuplicateVisability(True)
         deduplicatedLog = [(entry, 0) for entry in eventLog]
     else: deduplicatedLog = [(entry, 0) for entry in eventLog]
-    
+
     # determines if we have the minimum width to show date dividers
     showDaybreaks = width - dividerIndent >= 3
-    
+
     while deduplicatedLog:
       entry, duplicateCount = deduplicatedLog.pop(0)
-      
+
       if self.regexFilter and not self.regexFilter.search(entry.getDisplayMessage()):
         continue  # filter doesn't match log message - skip
-      
+
       # checks if we should be showing a divider with the date
       if entry.type == DAYBREAK_EVENT:
         # bottom of the divider
@@ -954,44 +954,44 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
             self.addch(lineCount, dividerIndent, curses.ACS_LLCORNER,  dividerAttr)
             self.hline(lineCount, dividerIndent + 1, width - dividerIndent - 2, dividerAttr)
             self.addch(lineCount, width - 1, curses.ACS_LRCORNER, dividerAttr)
-          
+
           lineCount += 1
-        
+
         # top of the divider
         if lineCount >= 1 and lineCount < height and showDaybreaks:
           timeLabel = time.strftime(" %B %d, %Y ", time.localtime(entry.timestamp))
           self.addch(lineCount, dividerIndent, curses.ACS_ULCORNER, dividerAttr)
           self.addch(lineCount, dividerIndent + 1, curses.ACS_HLINE, dividerAttr)
           self.addstr(lineCount, dividerIndent + 2, timeLabel, curses.A_BOLD | dividerAttr)
-          
+
           lineLength = width - dividerIndent - len(timeLabel) - 3
           self.hline(lineCount, dividerIndent + len(timeLabel) + 2, lineLength, dividerAttr)
           self.addch(lineCount, dividerIndent + len(timeLabel) + 2 + lineLength, curses.ACS_URCORNER, dividerAttr)
-        
+
         seenFirstDateDivider = True
         lineCount += 1
       else:
         # entry contents to be displayed, tuples of the form:
         # (msg, formatting, includeLinebreak)
         displayQueue = []
-        
+
         msgComp = entry.getDisplayMessage().split("\n")
         for i in range(len(msgComp)):
           font = curses.A_BOLD if "ERR" in entry.type else curses.A_NORMAL # emphasizes ERR messages
           displayQueue.append((msgComp[i].strip(), font | uiTools.getColor(entry.color), i != len(msgComp) - 1))
-        
+
         if duplicateCount:
           pluralLabel = "s" if duplicateCount > 1 else ""
           duplicateMsg = DUPLICATE_MSG % (duplicateCount, pluralLabel)
           displayQueue.append((duplicateMsg, duplicateAttr, False))
-        
+
         cursorLoc, lineOffset = msgIndent, 0
         maxEntriesPerLine = CONFIG["features.log.maxLinesPerEntry"]
         while displayQueue:
           msg, format, includeBreak = displayQueue.pop(0)
           drawLine = lineCount + lineOffset
           if lineOffset == maxEntriesPerLine: break
-          
+
           maxMsgSize = width - cursorLoc - 1
           if len(msg) > maxMsgSize:
             # message is too long - break it up
@@ -1000,40 +1000,40 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
             else:
               msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True)
               displayQueue.insert(0, (remainder.strip(), format, includeBreak))
-            
+
             includeBreak = True
-          
+
           if drawLine < height and drawLine >= 1:
             if seenFirstDateDivider and width - dividerIndent >= 3 and showDaybreaks:
               self.addch(drawLine, dividerIndent, curses.ACS_VLINE, dividerAttr)
               self.addch(drawLine, width - 1, curses.ACS_VLINE, dividerAttr)
-            
+
             self.addstr(drawLine, cursorLoc, msg, format)
-          
+
           cursorLoc += len(msg)
-          
+
           if includeBreak or not displayQueue:
             lineOffset += 1
             cursorLoc = msgIndent + ENTRY_INDENT
-        
+
         lineCount += lineOffset
-      
+
       # if this is the last line and there's room, then draw the bottom of the divider
       if not deduplicatedLog and seenFirstDateDivider:
         if lineCount < height and showDaybreaks:
           self.addch(lineCount, dividerIndent, curses.ACS_LLCORNER, dividerAttr)
           self.hline(lineCount, dividerIndent + 1, width - dividerIndent - 2, dividerAttr)
           self.addch(lineCount, width - 1, curses.ACS_LRCORNER, dividerAttr)
-        
+
         lineCount += 1
-    
+
     # redraw the display if...
     # - lastContentHeight was off by too much
     # - we're off the bottom of the page
     newContentHeight = lineCount + self.scroll - 1
     contentHeightDelta = abs(self.lastContentHeight - newContentHeight)
     forceRedraw, forceRedrawReason = True, ""
-    
+
     if contentHeightDelta >= CONTENT_HEIGHT_REDRAW_THRESHOLD:
       forceRedrawReason = "estimate was off by %i" % contentHeightDelta
     elif newContentHeight > height and self.scroll + height - 1 > newContentHeight:
@@ -1043,37 +1043,37 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     elif isScrollBarVisible and newContentHeight <= height - 1:
       forceRedrawReason = "scroll bar shouldn't be visible"
     else: forceRedraw = False
-    
+
     self.lastContentHeight = newContentHeight
     if forceRedraw:
       log.debug("redrawing the log panel with the corrected content height (%s)" % forceRedrawReason)
       self.redraw(True)
-    
+
     self.valsLock.release()
-  
+
   def redraw(self, forceRedraw=False, block=False):
     # determines if the content needs to be redrawn or not
     panel.Panel.redraw(self, forceRedraw, block)
-  
+
   def run(self):
     """
     Redraws the display, coalescing updates if events are rapidly logged (for
     instance running at the DEBUG runlevel) while also being immediately
     responsive if additions are less frequent.
     """
-    
+
     lastDay = daysSince() # used to determine if the date has changed
     while not self._halt:
       currentDay = daysSince()
       timeSinceReset = time.time() - self._lastUpdate
       maxLogUpdateRate = CONFIG["features.log.maxRefreshRate"] / 1000.0
-      
+
       sleepTime = 0
       if (self.msgLog == self._lastLoggedEvents and lastDay == currentDay) or self.isPaused():
         sleepTime = 5
       elif timeSinceReset < maxLogUpdateRate:
         sleepTime = max(0.05, maxLogUpdateRate - timeSinceReset)
-      
+
       if sleepTime:
         self._cond.acquire()
         if not self._halt: self._cond.wait(sleepTime)
@@ -1081,84 +1081,84 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       else:
         lastDay = currentDay
         self.redraw(True)
-        
+
         # makes sure that we register this as an update, otherwise lacking the
         # curses lock can cause a busy wait here
         self._lastUpdate = time.time()
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self._cond.acquire()
     self._halt = True
     self._cond.notifyAll()
     self._cond.release()
-  
+
   def setEventListening(self, events):
     """
     Configures the events Tor listens for, filtering non-tor events from what we
     request from the controller. This returns a sorted list of the events we
     successfully set.
-    
+
     Arguments:
       events - event types to attempt to set
     """
-    
+
     events = set(events) # drops duplicates
-    
+
     # accounts for runlevel naming difference
     if "ERROR" in events:
       events.add("ERR")
       events.remove("ERROR")
-    
+
     if "WARNING" in events:
       events.add("WARN")
       events.remove("WARNING")
-    
+
     torEvents = events.intersection(set(TOR_EVENT_TYPES.values()))
     armEvents = events.intersection(set(["ARM_%s" % runlevel for runlevel in log.Runlevel.keys()]))
-    
+
     # adds events unrecognized by arm if we're listening to the 'UNKNOWN' type
     if "UNKNOWN" in events:
       torEvents.update(set(getMissingEventTypes()))
-    
+
     torConn = torTools.getConn()
     torConn.removeEventListener(self.registerTorEvent)
-    
+
     for eventType in list(torEvents):
       try:
         torConn.addEventListener(self.registerTorEvent, eventType)
       except stem.ProtocolError:
         torEvents.remove(eventType)
-    
+
     # provides back the input set minus events we failed to set
     return sorted(torEvents.union(armEvents))
-  
+
   def _resetListener(self, controller, eventType, _):
     # if we're attaching to a new tor instance then clears the log and
     # prepopulates it with the content belonging to this instance
-    
+
     if eventType == State.INIT:
       self.reprepopulateEvents()
       self.redraw(True)
     elif eventType == State.CLOSED:
       log.notice("Tor control port closed")
-  
+
   def _getTitle(self, width):
     """
     Provides the label used for the panel, looking like:
       Events (ARM NOTICE - ERR, BW - filter: prepopulate):
-    
+
     This truncates the attributes (with an ellipse) if too long, and condenses
     runlevel ranges if there's three or more in a row (for instance ARM_INFO,
     ARM_NOTICE, and ARM_WARN becomes "ARM_INFO - WARN").
-    
+
     Arguments:
       width - width constraint the label needs to fix in
     """
-    
+
     # usually the attributes used to make the label are decently static, so
     # provide cached results if they're unchanged
     self.valsLock.acquire()
@@ -1169,7 +1169,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
     if isUnchanged:
       self.valsLock.release()
       return self._titleCache
-    
+
     eventsList = list(self.loggedEvents)
     if not eventsList:
       if not currentPattern:
@@ -1185,7 +1185,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       #   types (ex. "NOTICE - ERR, ARM_NOTICE - ERR" becomes "TOR/ARM NOTICE - ERR")
       tmpRunlevels = [] # runlevels pulled from the list (just the runlevel part)
       runlevelRanges = [] # tuple of type, startLevel, endLevel for ranges to be consensed
-      
+
       # reverses runlevels and types so they're appended in the right order
       reversedRunlevels = list(log.Runlevel)
       reversedRunlevels.reverse()
@@ -1206,68 +1206,68 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
               # adds runlevels individaully
               for tmpRunlevel in tmpRunlevels:
                 eventsList.insert(0, prefix + tmpRunlevel)
-            
+
             tmpRunlevels = []
-      
+
       # adds runlevel ranges, condensing if there's identical ranges
       for i in range(len(runlevelRanges)):
         if runlevelRanges[i]:
           prefix, startLevel, endLevel = runlevelRanges[i]
-          
+
           # check for matching ranges
           matches = []
           for j in range(i + 1, len(runlevelRanges)):
             if runlevelRanges[j] and runlevelRanges[j][1] == startLevel and runlevelRanges[j][2] == endLevel:
               matches.append(runlevelRanges[j])
               runlevelRanges[j] = None
-          
+
           if matches:
             # strips underscores and replaces empty entries with "TOR"
             prefixes = [entry[0] for entry in matches] + [prefix]
             for k in range(len(prefixes)):
               if prefixes[k] == "": prefixes[k] = "TOR"
               else: prefixes[k] = prefixes[k].replace("_", "")
-            
+
             eventsList.insert(0, "%s %s - %s" % ("/".join(prefixes), startLevel, endLevel))
           else:
             eventsList.insert(0, "%s%s - %s" % (prefix, startLevel, endLevel))
-      
+
       # truncates to use an ellipsis if too long, for instance:
       attrLabel = ", ".join(eventsList)
       if currentPattern: attrLabel += " - filter: %s" % currentPattern
       attrLabel = uiTools.cropStr(attrLabel, width - 10, 1)
       if attrLabel: attrLabel = " (%s)" % attrLabel
       panelLabel = "Events%s:" % attrLabel
-    
+
     # cache results and return
     self._titleCache = panelLabel
     self._titleArgs = (list(self.loggedEvents), currentPattern, width)
     self.valsLock.release()
     return panelLabel
-  
+
   def _trimEvents(self, eventListing):
     """
     Crops events that have either:
     - grown beyond the cache limit
     - outlived the configured log duration
-    
+
     Argument:
       eventListing - listing of log entries
     """
-    
+
     cacheSize = CONFIG["cache.logPanel.size"]
     if len(eventListing) > cacheSize: del eventListing[cacheSize:]
-    
+
     logTTL = CONFIG["features.log.entryDuration"]
     if logTTL > 0:
       currentDay = daysSince()
-      
+
       breakpoint = None # index at which to crop from
       for i in range(len(eventListing) - 1, -1, -1):
         daysSinceEvent = currentDay - daysSince(eventListing[i].timestamp)
         if daysSinceEvent > logTTL: breakpoint = i # older than the ttl
         else: break
-      
+
       # removes entries older than the ttl
       if breakpoint != None: del eventListing[breakpoint:]
 
diff --git a/arm/menu/actions.py b/arm/menu/actions.py
index ce58608..052b249 100644
--- a/arm/menu/actions.py
+++ b/arm/menu/actions.py
@@ -21,13 +21,13 @@ def makeMenu():
   """
   Constructs the base menu and all of its contents.
   """
-  
+
   baseMenu = arm.menu.item.Submenu("")
   baseMenu.add(makeActionsMenu())
   baseMenu.add(makeViewMenu())
-  
+
   control = arm.controller.getController()
-  
+
   for pagePanel in control.getDisplayPanels(includeSticky = False):
     if pagePanel.getName() == "graph":
       baseMenu.add(makeGraphMenu(pagePanel))
@@ -39,9 +39,9 @@ def makeMenu():
       baseMenu.add(makeConfigurationMenu(pagePanel))
     elif pagePanel.getName() == "torrc":
       baseMenu.add(makeTorrcMenu(pagePanel))
-  
+
   baseMenu.add(makeHelpMenu())
-  
+
   return baseMenu
 
 def makeActionsMenu():
@@ -53,23 +53,23 @@ def makeActionsMenu():
     Reset Tor
     Exit
   """
-  
+
   control = arm.controller.getController()
   conn = torTools.getConn()
   headerPanel = control.getPanel("header")
   actionsMenu = arm.menu.item.Submenu("Actions")
   actionsMenu.add(arm.menu.item.MenuItem("Close Menu", None))
   actionsMenu.add(arm.menu.item.MenuItem("New Identity", headerPanel.sendNewnym))
-  
+
   if conn.isAlive():
     actionsMenu.add(arm.menu.item.MenuItem("Stop Tor", conn.shutdown))
-  
+
   actionsMenu.add(arm.menu.item.MenuItem("Reset Tor", conn.reload))
-  
+
   if control.isPaused(): label, arg = "Unpause", False
   else: label, arg = "Pause", True
   actionsMenu.add(arm.menu.item.MenuItem(label, functools.partial(control.setPaused, arg)))
-  
+
   actionsMenu.add(arm.menu.item.MenuItem("Exit", control.quit))
   return actionsMenu
 
@@ -81,30 +81,30 @@ def makeViewMenu():
     [ ] etc...
         Color (Submenu)
   """
-  
+
   viewMenu = arm.menu.item.Submenu("View")
   control = arm.controller.getController()
-  
+
   if control.getPageCount() > 0:
     pageGroup = arm.menu.item.SelectionGroup(control.setPage, control.getPage())
-    
+
     for i in range(control.getPageCount()):
       pagePanels = control.getDisplayPanels(pageNumber = i, includeSticky = False)
       label = " / ".join([str_tools._to_camel_case(panel.getName()) for panel in pagePanels])
-      
+
       viewMenu.add(arm.menu.item.SelectionMenuItem(label, pageGroup, i))
-  
+
   if uiTools.isColorSupported():
     colorMenu = arm.menu.item.Submenu("Color")
     colorGroup = arm.menu.item.SelectionGroup(uiTools.setColorOverride, uiTools.getColorOverride())
-    
+
     colorMenu.add(arm.menu.item.SelectionMenuItem("All", colorGroup, None))
-    
+
     for color in uiTools.COLOR_LIST:
       colorMenu.add(arm.menu.item.SelectionMenuItem(str_tools._to_camel_case(color), colorGroup, color))
-    
+
     viewMenu.add(colorMenu)
-  
+
   return viewMenu
 
 def makeHelpMenu():
@@ -113,7 +113,7 @@ def makeHelpMenu():
     Hotkeys
     About
   """
-  
+
   helpMenu = arm.menu.item.Submenu("Help")
   helpMenu.add(arm.menu.item.MenuItem("Hotkeys", arm.popups.showHelpPopup))
   helpMenu.add(arm.menu.item.MenuItem("About", arm.popups.showAboutPopup))
@@ -128,46 +128,46 @@ def makeGraphMenu(graphPanel):
         Resize...
         Interval (Submenu)
         Bounds (Submenu)
-  
+
   Arguments:
     graphPanel - instance of the graph panel
   """
-  
+
   graphMenu = arm.menu.item.Submenu("Graph")
-  
+
   # stats options
   statGroup = arm.menu.item.SelectionGroup(graphPanel.setStats, graphPanel.getStats())
   availableStats = graphPanel.stats.keys()
   availableStats.sort()
-  
+
   for statKey in ["None"] + availableStats:
     label = str_tools._to_camel_case(statKey, divider = " ")
     statKey = None if statKey == "None" else statKey
     graphMenu.add(arm.menu.item.SelectionMenuItem(label, statGroup, statKey))
-  
+
   # resizing option
   graphMenu.add(arm.menu.item.MenuItem("Resize...", graphPanel.resizeGraph))
-  
+
   # interval submenu
   intervalMenu = arm.menu.item.Submenu("Interval")
   intervalGroup = arm.menu.item.SelectionGroup(graphPanel.setUpdateInterval, graphPanel.getUpdateInterval())
-  
+
   for i in range(len(arm.graphing.graphPanel.UPDATE_INTERVALS)):
     label = arm.graphing.graphPanel.UPDATE_INTERVALS[i][0]
     label = str_tools._to_camel_case(label, divider = " ")
     intervalMenu.add(arm.menu.item.SelectionMenuItem(label, intervalGroup, i))
-  
+
   graphMenu.add(intervalMenu)
-  
+
   # bounds submenu
   boundsMenu = arm.menu.item.Submenu("Bounds")
   boundsGroup = arm.menu.item.SelectionGroup(graphPanel.setBoundsType, graphPanel.getBoundsType())
-  
+
   for boundsType in arm.graphing.graphPanel.Bounds:
     boundsMenu.add(arm.menu.item.SelectionMenuItem(boundsType, boundsGroup, boundsType))
-  
+
   graphMenu.add(boundsMenu)
-  
+
   return graphMenu
 
 def makeLogMenu(logPanel):
@@ -178,34 +178,34 @@ def makeLogMenu(logPanel):
     Clear
     Show / Hide Duplicates
     Filter (Submenu)
-  
+
   Arguments:
     logPanel - instance of the log panel
   """
-  
+
   logMenu = arm.menu.item.Submenu("Log")
-  
+
   logMenu.add(arm.menu.item.MenuItem("Events...", logPanel.showEventSelectionPrompt))
   logMenu.add(arm.menu.item.MenuItem("Snapshot...", logPanel.showSnapshotPrompt))
   logMenu.add(arm.menu.item.MenuItem("Clear", logPanel.clear))
-  
+
   if CONFIG["features.log.showDuplicateEntries"]:
     label, arg = "Hide", False
   else: label, arg = "Show", True
   logMenu.add(arm.menu.item.MenuItem("%s Duplicates" % label, functools.partial(logPanel.setDuplicateVisability, arg)))
-  
+
   # filter submenu
   filterMenu = arm.menu.item.Submenu("Filter")
   filterGroup = arm.menu.item.SelectionGroup(logPanel.makeFilterSelection, logPanel.getFilter())
-  
+
   filterMenu.add(arm.menu.item.SelectionMenuItem("None", filterGroup, None))
-  
+
   for option in logPanel.filterOptions:
     filterMenu.add(arm.menu.item.SelectionMenuItem(option, filterGroup, option))
-  
+
   filterMenu.add(arm.menu.item.MenuItem("New...", logPanel.showFilterPrompt))
   logMenu.add(filterMenu)
-  
+
   return logMenu
 
 def makeConnectionsMenu(connPanel):
@@ -216,37 +216,37 @@ def makeConnectionsMenu(connPanel):
     [ ] Nickname
         Sorting...
         Resolver (Submenu)
-  
+
   Arguments:
     connPanel - instance of the connections panel
   """
-  
+
   connectionsMenu = arm.menu.item.Submenu("Connections")
-  
+
   # listing options
   listingGroup = arm.menu.item.SelectionGroup(connPanel.setListingType, connPanel.getListingType())
-  
+
   listingOptions = list(arm.connections.entries.ListingType)
   listingOptions.remove(arm.connections.entries.ListingType.HOSTNAME)
-  
+
   for option in listingOptions:
     connectionsMenu.add(arm.menu.item.SelectionMenuItem(option, listingGroup, option))
-  
+
   # sorting option
   connectionsMenu.add(arm.menu.item.MenuItem("Sorting...", connPanel.showSortDialog))
-  
+
   # resolver submenu
   connResolver = connections.getResolver("tor")
   resolverMenu = arm.menu.item.Submenu("Resolver")
   resolverGroup = arm.menu.item.SelectionGroup(connResolver.setOverwriteResolver, connResolver.getOverwriteResolver())
-  
+
   resolverMenu.add(arm.menu.item.SelectionMenuItem("auto", resolverGroup, None))
-  
+
   for option in connections.Resolver:
     resolverMenu.add(arm.menu.item.SelectionMenuItem(option, resolverGroup, option))
-  
+
   connectionsMenu.add(resolverMenu)
-  
+
   return connectionsMenu
 
 def makeConfigurationMenu(configPanel):
@@ -255,19 +255,19 @@ def makeConfigurationMenu(configPanel):
     Save Config...
     Sorting...
     Filter / Unfilter Options
-  
+
   Arguments:
     configPanel - instance of the configuration panel
   """
-  
+
   configMenu = arm.menu.item.Submenu("Configuration")
   configMenu.add(arm.menu.item.MenuItem("Save Config...", configPanel.showWriteDialog))
   configMenu.add(arm.menu.item.MenuItem("Sorting...", configPanel.showSortDialog))
-  
+
   if configPanel.showAll: label, arg = "Filter", True
   else: label, arg = "Unfilter", False
   configMenu.add(arm.menu.item.MenuItem("%s Options" % label, functools.partial(configPanel.setFiltering, arg)))
-  
+
   return configMenu
 
 def makeTorrcMenu(torrcPanel):
@@ -276,21 +276,21 @@ def makeTorrcMenu(torrcPanel):
     Reload
     Show / Hide Comments
     Show / Hide Line Numbers
-  
+
   Arguments:
     torrcPanel - instance of the torrc panel
   """
-  
+
   torrcMenu = arm.menu.item.Submenu("Torrc")
   torrcMenu.add(arm.menu.item.MenuItem("Reload", torrcPanel.reloadTorrc))
-  
+
   if torrcPanel.stripComments: label, arg = "Show", True
   else: label, arg = "Hide", False
   torrcMenu.add(arm.menu.item.MenuItem("%s Comments" % label, functools.partial(torrcPanel.setCommentsVisible, arg)))
-  
+
   if torrcPanel.showLineNum: label, arg = "Hide", False
   else: label, arg = "Show", True
   torrcMenu.add(arm.menu.item.MenuItem("%s Line Numbers" % label, functools.partial(torrcPanel.setLineNumberVisible, arg)))
-  
+
   return torrcMenu
 
diff --git a/arm/menu/item.py b/arm/menu/item.py
index 4e66b2b..ffd46a5 100644
--- a/arm/menu/item.py
+++ b/arm/menu/item.py
@@ -8,99 +8,99 @@ class MenuItem():
   """
   Option in a drop-down menu.
   """
-  
+
   def __init__(self, label, callback):
     self._label = label
     self._callback = callback
     self._parent = None
-  
+
   def getLabel(self):
     """
     Provides a tuple of three strings representing the prefix, label, and
     suffix for this item.
     """
-    
+
     return ("", self._label, "")
-  
+
   def getParent(self):
     """
     Provides the Submenu we're contained within.
     """
-    
+
     return self._parent
-  
+
   def getHierarchy(self):
     """
     Provides a list with all of our parents, up to the root.
     """
-    
+
     myHierarchy = [self]
     while myHierarchy[-1].getParent():
       myHierarchy.append(myHierarchy[-1].getParent())
-    
+
     myHierarchy.reverse()
     return myHierarchy
-  
+
   def getRoot(self):
     """
     Provides the base submenu we belong to.
     """
-    
+
     if self._parent: return self._parent.getRoot()
     else: return self
-  
+
   def select(self):
     """
     Performs the callback for the menu item, returning true if we should close
     the menu and false otherwise.
     """
-    
+
     if self._callback:
       control = arm.controller.getController()
       control.setMsg()
       control.redraw()
       self._callback()
     return True
-  
+
   def next(self):
     """
     Provides the next option for the submenu we're in, raising a ValueError
     if we don't have a parent.
     """
-    
+
     return self._getSibling(1)
-  
+
   def prev(self):
     """
     Provides the previous option for the submenu we're in, raising a ValueError
     if we don't have a parent.
     """
-    
+
     return self._getSibling(-1)
-  
+
   def _getSibling(self, offset):
     """
     Provides our sibling with a given index offset from us, raising a
     ValueError if we don't have a parent.
-    
+
     Arguments:
       offset - index offset for the sibling to be returned
     """
-    
+
     if self._parent:
       mySiblings = self._parent.getChildren()
-      
+
       try:
         myIndex = mySiblings.index(self)
         return mySiblings[(myIndex + offset) % len(mySiblings)]
       except ValueError:
         # We expect a bidirectional references between submenus and their
         # children. If we don't have this then our menu's screwed up.
-        
+
         msg = "The '%s' submenu doesn't contain '%s' (children: '%s')" % (self, self._parent, "', '".join(mySiblings))
         raise ValueError(msg)
     else: raise ValueError("Menu option '%s' doesn't have a parent" % self)
-  
+
   def __str__(self):
     return self._label
 
@@ -108,48 +108,48 @@ class Submenu(MenuItem):
   """
   Menu item that lists other menu options.
   """
-  
+
   def __init__(self, label):
     MenuItem.__init__(self, label, None)
     self._children = []
-  
+
   def getLabel(self):
     """
     Provides our label with a ">" suffix to indicate that we have suboptions.
     """
-    
+
     myLabel = MenuItem.getLabel(self)[1]
     return ("", myLabel, " >")
-  
+
   def add(self, menuItem):
     """
     Adds the given menu item to our listing. This raises a ValueError if the
     item already has a parent.
-    
+
     Arguments:
       menuItem - menu option to be added
     """
-    
+
     if menuItem.getParent():
       raise ValueError("Menu option '%s' already has a parent" % menuItem)
     else:
       menuItem._parent = self
       self._children.append(menuItem)
-  
+
   def getChildren(self):
     """
     Provides the menu and submenus we contain.
     """
-    
+
     return list(self._children)
-  
+
   def isEmpty(self):
     """
     True if we have no children, false otherwise.
     """
-    
+
     return not bool(self._children)
-  
+
   def select(self):
     return False
 
@@ -157,7 +157,7 @@ class SelectionGroup():
   """
   Radio button groups that SelectionMenuItems can belong to.
   """
-  
+
   def __init__(self, action, selectedArg):
     self.action = action
     self.selectedArg = selectedArg
@@ -167,35 +167,35 @@ class SelectionMenuItem(MenuItem):
   Menu item with an associated group which determines the selection. This is
   for the common single argument getter/setter pattern.
   """
-  
+
   def __init__(self, label, group, arg):
     MenuItem.__init__(self, label, None)
     self._group = group
     self._arg = arg
-  
+
   def isSelected(self):
     """
     True if we're the selected item, false otherwise.
     """
-    
+
     return self._arg == self._group.selectedArg
-  
+
   def getLabel(self):
     """
     Provides our label with a "[X]" prefix if selected and "[ ]" if not.
     """
-    
+
     myLabel = MenuItem.getLabel(self)[1]
     myPrefix = "[X] " if self.isSelected() else "[ ] "
     return (myPrefix, myLabel, "")
-  
+
   def select(self):
     """
     Performs the group's setter action with our argument.
     """
-    
+
     if not self.isSelected():
       self._group.action(self._arg)
-    
+
     return True
 
diff --git a/arm/menu/menu.py b/arm/menu/menu.py
index d8cb514..3edb0a7 100644
--- a/arm/menu/menu.py
+++ b/arm/menu/menu.py
@@ -15,30 +15,30 @@ class MenuCursor:
   """
   Tracks selection and key handling in the menu.
   """
-  
+
   def __init__(self, initialSelection):
     self._selection = initialSelection
     self._isDone = False
-  
+
   def isDone(self):
     """
     Provides true if a selection has indicated that we should close the menu.
     False otherwise.
     """
-    
+
     return self._isDone
-  
+
   def getSelection(self):
     """
     Provides the currently selected menu item.
     """
-    
+
     return self._selection
-  
+
   def handleKey(self, key):
     isSelectionSubmenu = isinstance(self._selection, arm.menu.item.Submenu)
     selectionHierarchy = self._selection.getHierarchy()
-    
+
     if uiTools.isSelectionKey(key):
       if isSelectionSubmenu:
         if not self._selection.isEmpty():
@@ -73,46 +73,46 @@ def showMenu():
   popup, _, _ = arm.popups.init(1, belowStatic = False)
   if not popup: return
   control = arm.controller.getController()
-  
+
   try:
     # generates the menu and uses the initial selection of the first item in
     # the file menu
     menu = arm.menu.actions.makeMenu()
     cursor = MenuCursor(menu.getChildren()[0].getChildren()[0])
-    
+
     while not cursor.isDone():
       # sets the background color
       popup.win.clear()
       popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red"))
       selectionHierarchy = cursor.getSelection().getHierarchy()
-      
+
       # provide a message saying how to close the menu
       control.setMsg("Press m or esc to close the menu.", curses.A_BOLD, True)
-      
+
       # renders the menu bar, noting where the open submenu is positioned
       drawLeft, selectionLeft = 0, 0
-      
+
       for topLevelItem in menu.getChildren():
         drawFormat = curses.A_BOLD
         if topLevelItem == selectionHierarchy[1]:
           drawFormat |= curses.A_UNDERLINE
           selectionLeft = drawLeft
-        
+
         drawLabel = " %s " % topLevelItem.getLabel()[1]
         popup.addstr(0, drawLeft, drawLabel, drawFormat)
         popup.addch(0, drawLeft + len(drawLabel), curses.ACS_VLINE)
-        
+
         drawLeft += len(drawLabel) + 1
-      
+
       # recursively shows opened submenus
       _drawSubmenu(cursor, 1, 1, selectionLeft)
-      
+
       popup.win.refresh()
-      
+
       curses.cbreak()
       key = control.getScreen().getch()
       cursor.handleKey(key)
-      
+
       # redraws the rest of the interface if we're rendering on it again
       if not cursor.isDone(): control.redraw()
   finally:
@@ -121,44 +121,44 @@ def showMenu():
 
 def _drawSubmenu(cursor, level, top, left):
   selectionHierarchy = cursor.getSelection().getHierarchy()
-  
+
   # checks if there's nothing to display
   if len(selectionHierarchy) < level + 2: return
-  
+
   # fetches the submenu and selection we're displaying
   submenu = selectionHierarchy[level]
   selection = selectionHierarchy[level + 1]
-  
+
   # gets the size of the prefix, middle, and suffix columns
   allLabelSets = [entry.getLabel() for entry in submenu.getChildren()]
   prefixColSize = max([len(entry[0]) for entry in allLabelSets])
   middleColSize = max([len(entry[1]) for entry in allLabelSets])
   suffixColSize = max([len(entry[2]) for entry in allLabelSets])
-  
+
   # formatted string so we can display aligned menu entries
   labelFormat = " %%-%is%%-%is%%-%is " % (prefixColSize, middleColSize, suffixColSize)
   menuWidth = len(labelFormat % ("", "", ""))
-  
+
   popup, _, _ = arm.popups.init(len(submenu.getChildren()), menuWidth, top, left, belowStatic = False)
   if not popup: return
-  
+
   try:
     # sets the background color
     popup.win.bkgd(' ', curses.A_STANDOUT | uiTools.getColor("red"))
-    
+
     drawTop, selectionTop = 0, 0
     for menuItem in submenu.getChildren():
       if menuItem == selection:
         drawFormat = curses.A_BOLD | uiTools.getColor("white")
         selectionTop = drawTop
       else: drawFormat = curses.A_NORMAL
-      
+
       popup.addstr(drawTop, 0, labelFormat % menuItem.getLabel(), drawFormat)
       drawTop += 1
-    
+
     popup.win.refresh()
-    
+
     # shows the next submenu
     _drawSubmenu(cursor, level + 1, top + selectionTop, left + menuWidth)
   finally: arm.popups.finalize()
-  
+
diff --git a/arm/popups.py b/arm/popups.py
index 2b40558..c4aed89 100644
--- a/arm/popups.py
+++ b/arm/popups.py
@@ -16,7 +16,7 @@ def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
   and this returns a tuple of the...
   (popup, draw width, draw height)
   Otherwise this leaves curses unlocked and returns None.
-  
+
   Arguments:
     height      - maximum height of the popup
     width       - maximum width of the popup
@@ -24,15 +24,15 @@ def init(height = -1, width = -1, top = 0, left = 0, belowStatic = True):
     left        - left position from the screen
     belowStatic - positions popup below static content if true
   """
-  
+
   control = arm.controller.getController()
   if belowStatic:
     stickyHeight = sum([stickyPanel.getHeight() for stickyPanel in control.getStickyPanels()])
   else: stickyHeight = 0
-  
+
   popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
   popup.setVisible(True)
-  
+
   # Redraws the popup to prepare a subwindow instance. If none is spawned then
   # the panel can't be drawn (for instance, due to not being visible).
   popup.redraw(True)
@@ -46,7 +46,7 @@ def finalize():
   Cleans up after displaying a popup, releasing the cureses lock and redrawing
   the rest of the display.
   """
-  
+
   arm.controller.getController().requestRedraw()
   panel.CURSES_LOCK.release()
 
@@ -54,12 +54,12 @@ def inputPrompt(msg, initialValue = ""):
   """
   Prompts the user to enter a string on the control line (which usually
   displays the page number and basic controls).
-  
+
   Arguments:
     msg          - message to prompt the user for input with
     initialValue - initial value of the field
   """
-  
+
   panel.CURSES_LOCK.acquire()
   control = arm.controller.getController()
   msgPanel = control.getPanel("msg")
@@ -74,23 +74,23 @@ def showMsg(msg, maxWait = -1, attr = curses.A_STANDOUT):
   """
   Displays a single line message on the control line for a set time. Pressing
   any key will end the message. This returns the key pressed.
-  
+
   Arguments:
     msg     - message to be displayed to the user
     maxWait - time to show the message, indefinite if -1
     attr    - attributes with which to draw the message
   """
-  
+
   panel.CURSES_LOCK.acquire()
   control = arm.controller.getController()
   control.setMsg(msg, attr, True)
-  
+
   if maxWait == -1: curses.cbreak()
   else: curses.halfdelay(maxWait * 10)
   keyPress = control.getScreen().getch()
   control.setMsg()
   panel.CURSES_LOCK.release()
-  
+
   return keyPress
 
 def showHelpPopup():
@@ -99,30 +99,30 @@ def showHelpPopup():
   returns the user input used to close the popup. If the popup didn't close
   properly, this is an arrow, enter, or scroll key then this returns None.
   """
-  
+
   popup, _, height = init(9, 80)
   if not popup: return
-  
+
   exitKey = None
   try:
     control = arm.controller.getController()
     pagePanels = control.getDisplayPanels()
-    
+
     # the first page is the only one with multiple panels, and it looks better
     # with the log entries first, so reversing the order
     pagePanels.reverse()
-    
+
     helpOptions = []
     for entry in pagePanels:
       helpOptions += entry.getHelp()
-    
+
     # test doing afterward in case of overwriting
     popup.win.box()
     popup.addstr(0, 0, "Page %i Commands:" % (control.getPage() + 1), curses.A_STANDOUT)
-    
+
     for i in range(len(helpOptions)):
       if i / 2 >= height - 2: break
-      
+
       # draws entries in the form '<key>: <description>[ (<selection>)]', for
       # instance...
       # u: duplicate log entries (hidden)
@@ -130,26 +130,26 @@ def showHelpPopup():
       if key: description = ": " + description
       row = (i / 2) + 1
       col = 2 if i % 2 == 0 else 41
-      
+
       popup.addstr(row, col, key, curses.A_BOLD)
       col += len(key)
       popup.addstr(row, col, description)
       col += len(description)
-      
+
       if selection:
         popup.addstr(row, col, " (")
         popup.addstr(row, col + 2, selection, curses.A_BOLD)
         popup.addstr(row, col + 2 + len(selection), ")")
-    
+
     # tells user to press a key if the lower left is unoccupied
     if len(helpOptions) < 13 and height == 9:
       popup.addstr(7, 2, "Press any key...")
-    
+
     popup.win.refresh()
     curses.cbreak()
     exitKey = control.getScreen().getch()
   finally: finalize()
-  
+
   if not uiTools.isSelectionKey(exitKey) and \
     not uiTools.isScrollKey(exitKey) and \
     not exitKey in (curses.KEY_LEFT, curses.KEY_RIGHT):
@@ -160,13 +160,13 @@ def showAboutPopup():
   """
   Presents a popup with author and version information.
   """
-  
+
   popup, _, height = init(9, 80)
   if not popup: return
-  
+
   try:
     control = arm.controller.getController()
-    
+
     popup.win.box()
     popup.addstr(0, 0, "About:", curses.A_STANDOUT)
     popup.addstr(1, 2, "arm, version %s (released %s)" % (__version__, __release_date__), curses.A_BOLD)
@@ -175,7 +175,7 @@ def showAboutPopup():
     popup.addstr(5, 2, "Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)")
     popup.addstr(7, 2, "Press any key...")
     popup.win.refresh()
-    
+
     curses.cbreak()
     control.getScreen().getch()
   finally: finalize()
@@ -183,42 +183,42 @@ def showAboutPopup():
 def showSortDialog(title, options, oldSelection, optionColors):
   """
   Displays a sorting dialog of the form:
-  
+
     Current Order: <previous selection>
     New Order: <selections made>
-    
+
     <option 1>    <option 2>    <option 3>   Cancel
-  
+
   Options are colored when among the "Current Order" or "New Order", but not
   when an option below them. If cancel is selected or the user presses escape
   then this returns None. Otherwise, the new ordering is provided.
-  
+
   Arguments:
     title   - title displayed for the popup window
     options      - ordered listing of option labels
     oldSelection - current ordering
     optionColors - mappings of options to their color
   """
-  
+
   popup, _, _ = init(9, 80)
   if not popup: return
   newSelections = []  # new ordering
-  
+
   try:
     cursorLoc = 0     # index of highlighted option
     curses.cbreak()   # wait indefinitely for key presses (no timeout)
-    
+
     selectionOptions = list(options)
     selectionOptions.append("Cancel")
-    
+
     while len(newSelections) < len(oldSelection):
       popup.win.erase()
       popup.win.box()
       popup.addstr(0, 0, title, curses.A_STANDOUT)
-      
+
       _drawSortSelection(popup, 1, 2, "Current Order: ", oldSelection, optionColors)
       _drawSortSelection(popup, 2, 2, "New Order: ", newSelections, optionColors)
-      
+
       # presents remaining options, each row having up to four options with
       # spacing of nineteen cells
       row, col = 4, 0
@@ -227,9 +227,9 @@ def showSortDialog(title, options, oldSelection, optionColors):
         popup.addstr(row, col * 19 + 2, selectionOptions[i], optionFormat)
         col += 1
         if col == 4: row, col = row + 1, 0
-      
+
       popup.win.refresh()
-      
+
       key = arm.controller.getController().getScreen().getch()
       if key == curses.KEY_LEFT:
         cursorLoc = max(0, cursorLoc - 1)
@@ -241,7 +241,7 @@ def showSortDialog(title, options, oldSelection, optionColors):
         cursorLoc = min(len(selectionOptions) - 1, cursorLoc + 4)
       elif uiTools.isSelectionKey(key):
         selection = selectionOptions[cursorLoc]
-        
+
         if selection == "Cancel": break
         else:
           newSelections.append(selection)
@@ -249,7 +249,7 @@ def showSortDialog(title, options, oldSelection, optionColors):
           cursorLoc = min(cursorLoc, len(selectionOptions) - 1)
       elif key == 27: break # esc - cancel
   finally: finalize()
-  
+
   if len(newSelections) == len(oldSelection):
     return newSelections
   else: return None
@@ -258,9 +258,9 @@ def _drawSortSelection(popup, y, x, prefix, options, optionColors):
   """
   Draws a series of comma separated sort selections. The whole line is bold
   and sort options also have their specified color. Example:
-  
+
     Current Order: Man Page Entry, Option Name, Is Default
-  
+
   Arguments:
     popup        - panel in which to draw sort selection
     y            - vertical location
@@ -269,16 +269,16 @@ def _drawSortSelection(popup, y, x, prefix, options, optionColors):
     options      - sort options to be shown
     optionColors - mappings of options to their color
   """
-  
+
   popup.addstr(y, x, prefix, curses.A_BOLD)
   x += len(prefix)
-  
+
   for i in range(len(options)):
     sortType = options[i]
     sortColor = uiTools.getColor(optionColors.get(sortType, "white"))
     popup.addstr(y, x, sortType, sortColor | curses.A_BOLD)
     x += len(sortType)
-    
+
     # comma divider between options, if this isn't the last
     if i < len(options) - 1:
       popup.addstr(y, x, ", ", curses.A_BOLD)
@@ -289,42 +289,42 @@ def showMenu(title, options, oldSelection):
   Provides menu with options laid out in a single column. User can cancel
   selection with the escape key, in which case this proives -1. Otherwise this
   returns the index of the selection.
-  
+
   Arguments:
     title        - title displayed for the popup window
     options      - ordered listing of options to display
     oldSelection - index of the initially selected option (uses the first
                    selection without a carrot if -1)
   """
-  
+
   maxWidth = max(map(len, options)) + 9
   popup, _, _ = init(len(options) + 2, maxWidth)
   if not popup: return
   key, selection = 0, oldSelection if oldSelection != -1 else 0
-  
+
   try:
     # hides the title of the first panel on the page
     control = arm.controller.getController()
     topPanel = control.getDisplayPanels(includeSticky = False)[0]
     topPanel.setTitleVisible(False)
     topPanel.redraw(True)
-    
+
     curses.cbreak()   # wait indefinitely for key presses (no timeout)
-    
+
     while not uiTools.isSelectionKey(key):
       popup.win.erase()
       popup.win.box()
       popup.addstr(0, 0, title, curses.A_STANDOUT)
-      
+
       for i in range(len(options)):
         label = options[i]
         format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
         tab = "> " if i == oldSelection else "  "
         popup.addstr(i + 1, 2, tab)
         popup.addstr(i + 1, 4, " %s " % label, format)
-      
+
       popup.win.refresh()
-      
+
       key = control.getScreen().getch()
       if key == curses.KEY_UP: selection = max(0, selection - 1)
       elif key == curses.KEY_DOWN: selection = min(len(options) - 1, selection + 1)
@@ -332,6 +332,6 @@ def showMenu(title, options, oldSelection):
   finally:
     topPanel.setTitleVisible(True)
     finalize()
-  
+
   return selection
 
diff --git a/arm/prereq.py b/arm/prereq.py
index c01dfbd..1813662 100644
--- a/arm/prereq.py
+++ b/arm/prereq.py
@@ -22,7 +22,7 @@ def isStemAvailable():
   """
   True if stem is already available on the platform, false otherwise.
   """
-  
+
   try:
     import stem
     return True
@@ -34,20 +34,20 @@ def promptStemInstall():
   Asks the user to install stem. This returns True if it was installed and
   False otherwise (if it was either declined or failed to be fetched).
   """
-  
+
   userInput = raw_input("Arm requires stem to run, but it's unavailable. Would you like to install it? (y/n): ")
-  
+
   # if user says no then terminate
   if not userInput.lower() in ("y", "yes"): return False
-  
+
   # attempt to install stem, printing the issue if unsuccessful
   try:
     #fetchLibrary(STEM_ARCHIVE, STEM_SIG)
     installStem()
-    
+
     if not isStemAvailable():
       raise IOError("Unable to install stem, sorry")
-    
+
     print "Stem successfully installed"
     return True
   except IOError, exc:
@@ -58,36 +58,36 @@ def fetchLibrary(url, sig):
   """
   Downloads the given archive, verifies its signature, then installs the
   library. This raises an IOError if any of these steps fail.
-  
+
   Arguments:
     url - url from which to fetch the gzipped tarball
     sig - sha256 signature for the archive
   """
-  
+
   tmpDir = tempfile.mkdtemp()
   destination = tmpDir + "/" + url.split("/")[-1]
   urllib.urlretrieve(url, destination)
-  
+
   # checks the signature, reading the archive in 256-byte chunks
   m = hashlib.sha256()
   fd = open(destination, "rb")
-  
+
   while True:
     data = fd.read(256)
     if not data: break
     m.update(data)
-  
+
   fd.close()
   actualSig = m.hexdigest()
-  
+
   if sig != actualSig:
     raise IOError("Signature of the library is incorrect (got '%s' rather than '%s')" % (actualSig, sig))
-  
+
   # extracts the tarball
   tarFd = tarfile.open(destination, 'r:gz')
   tarFd.extractall("src/")
   tarFd.close()
-  
+
   # clean up the temporary contents (fails quietly if unsuccessful)
   shutil.rmtree(destination, ignore_errors=True)
 
@@ -96,24 +96,24 @@ def installStem():
   Checks out the current git head release for stem and bundles it with arm.
   This raises an IOError if unsuccessful.
   """
-  
+
   if isStemAvailable(): return
-  
+
   # temporary destination for stem's git clone, guarenteed to be unoccupied
   # (to avoid conflicting with files that are already there)
   tmpFilename = tempfile.mktemp("/stem")
-  
+
   # fetches stem
   exitStatus = os.system("git clone --quiet %s %s > /dev/null" % (STEM_REPO, tmpFilename))
   if exitStatus: raise IOError("Unable to get stem from %s. Is git installed?" % STEM_REPO)
-  
+
   # the destination for stem will be our directory
   ourDir = os.path.dirname(os.path.realpath(__file__))
-  
+
   # exports stem to our location
   exitStatus = os.system("(cd %s && git archive --format=tar master stem) | (cd %s && tar xf - 2> /dev/null)" % (tmpFilename, ourDir))
   if exitStatus: raise IOError("Unable to install stem to %s" % ourDir)
-  
+
   # Clean up the temporary contents. This isn't vital so quietly fails in case
   # of errors.
   shutil.rmtree(tmpFilename, ignore_errors=True)
@@ -121,18 +121,18 @@ def installStem():
 if __name__ == '__main__':
   majorVersion = sys.version_info[0]
   minorVersion = sys.version_info[1]
-  
+
   if majorVersion > 2:
     print("arm isn't compatible beyond the python 2.x series\n")
     sys.exit(1)
   elif majorVersion < 2 or minorVersion < 5:
     print("arm requires python version 2.5 or greater\n")
     sys.exit(1)
-  
+
   if not isStemAvailable():
     isInstalled = promptStemInstall()
     if not isInstalled: sys.exit(1)
-  
+
   try:
     import curses
   except ImportError:
diff --git a/arm/torrcPanel.py b/arm/torrcPanel.py
index 7cf34d1..d6d8123 100644
--- a/arm/torrcPanel.py
+++ b/arm/torrcPanel.py
@@ -31,31 +31,31 @@ class TorrcPanel(panel.Panel):
   Renders the current torrc or armrc with syntax highlighting in a scrollable
   area.
   """
-  
+
   def __init__(self, stdscr, configType):
     panel.Panel.__init__(self, stdscr, "torrc", 0)
-    
+
     self.valsLock = threading.RLock()
     self.configType = configType
     self.scroll = 0
     self.showLineNum = True     # shows left aligned line numbers
     self.stripComments = False  # drops comments and extra whitespace
-    
+
     # height of the content when last rendered (the cached value is invalid if
     # _lastContentHeightArgs is None or differs from the current dimensions)
     self._lastContentHeight = 1
     self._lastContentHeightArgs = None
-    
+
     # listens for tor reload (sighup) events
     conn = torTools.getConn()
     conn.addStatusListener(self.resetListener)
     if conn.isAlive(): self.resetListener(None, State.INIT, None)
-  
+
   def resetListener(self, controller, eventType, _):
     """
     Reloads and displays the torrc on tor reload (sighup) events.
     """
-    
+
     if eventType == State.INIT:
       # loads the torrc and provides warnings in case of validation errors
       try:
@@ -69,36 +69,36 @@ class TorrcPanel(panel.Panel):
         torConfig.getTorrc().load(True)
         self.redraw(True)
       except: pass
-  
+
   def setCommentsVisible(self, isVisible):
     """
     Sets if comments and blank lines are shown or stripped.
-    
+
     Arguments:
       isVisible - displayed comments and blank lines if true, strips otherwise
     """
-    
+
     self.stripComments = not isVisible
     self._lastContentHeightArgs = None
     self.redraw(True)
-  
+
   def setLineNumberVisible(self, isVisible):
     """
     Sets if line numbers are shown or hidden.
-    
+
     Arguments:
       isVisible - displays line numbers if true, hides otherwise
     """
-    
+
     self.showLineNum = isVisible
     self._lastContentHeightArgs = None
     self.redraw(True)
-  
+
   def reloadTorrc(self):
     """
     Reloads the torrc, displaying an indicator of success or failure.
     """
-    
+
     try:
       torConfig.getTorrc().load()
       self._lastContentHeightArgs = None
@@ -106,18 +106,18 @@ class TorrcPanel(panel.Panel):
       resultMsg = "torrc reloaded"
     except IOError:
       resultMsg = "failed to reload torrc"
-    
+
     self._lastContentHeightArgs = None
     self.redraw(True)
     popups.showMsg(resultMsg, 1)
-  
+
   def handleKey(self, key):
     self.valsLock.acquire()
     isKeystrokeConsumed = True
     if uiTools.isScrollKey(key):
       pageHeight = self.getPreferredSize()[0] - 1
       newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
-      
+
       if self.scroll != newScroll:
         self.scroll = newScroll
         self.redraw(True)
@@ -128,16 +128,16 @@ class TorrcPanel(panel.Panel):
     elif key == ord('r') or key == ord('R'):
       self.reloadTorrc()
     else: isKeystrokeConsumed = False
-    
+
     self.valsLock.release()
     return isKeystrokeConsumed
-  
+
   def setVisible(self, isVisible):
     if not isVisible:
       self._lastContentHeightArgs = None # redraws when next displayed
-    
+
     panel.Panel.setVisible(self, isVisible)
-  
+
   def getHelp(self):
     options = []
     options.append(("up arrow", "scroll up a line", None))
@@ -149,80 +149,80 @@ class TorrcPanel(panel.Panel):
     options.append(("r", "reload torrc", None))
     options.append(("x", "reset tor (issue sighup)", None))
     return options
-  
+
   def draw(self, width, height):
     self.valsLock.acquire()
-    
+
     # If true, we assume that the cached value in self._lastContentHeight is
     # still accurate, and stop drawing when there's nothing more to display.
     # Otherwise the self._lastContentHeight is suspect, and we'll process all
     # the content to check if it's right (and redraw again with the corrected
     # height if not).
     trustLastContentHeight = self._lastContentHeightArgs == (width, height)
-    
+
     # restricts scroll location to valid bounds
     self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))
-    
+
     renderedContents, corrections, confLocation = None, {}, None
     if self.configType == Config.TORRC:
       loadedTorrc = torConfig.getTorrc()
       loadedTorrc.getLock().acquire()
       confLocation = loadedTorrc.getConfigLocation()
-      
+
       if not loadedTorrc.isLoaded():
         renderedContents = ["### Unable to load the torrc ###"]
       else:
         renderedContents = loadedTorrc.getDisplayContents(self.stripComments)
-        
+
         # constructs a mapping of line numbers to the issue on it
         corrections = dict((lineNum, (issue, msg)) for lineNum, issue, msg in loadedTorrc.getCorrections())
-      
+
       loadedTorrc.getLock().release()
     else:
       loadedArmrc = conf.get_config("arm")
       confLocation = loadedArmrc._path
       renderedContents = list(loadedArmrc._raw_contents)
-    
+
     # offset to make room for the line numbers
     lineNumOffset = 0
     if self.showLineNum:
       if len(renderedContents) == 0: lineNumOffset = 2
       else: lineNumOffset = int(math.log10(len(renderedContents))) + 2
-    
+
     # draws left-hand scroll bar if content's longer than the height
     scrollOffset = 0
     if CONFIG["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
       scrollOffset = 3
       self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1)
-    
+
     displayLine = -self.scroll + 1 # line we're drawing on
-    
+
     # draws the top label
     if self.isTitleVisible():
       sourceLabel = "Tor" if self.configType == Config.TORRC else "Arm"
       locationLabel = " (%s)" % confLocation if confLocation else ""
       self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)
-    
+
     isMultiline = False # true if we're in the middle of a multiline torrc entry
     for lineNumber in range(0, len(renderedContents)):
       lineText = renderedContents[lineNumber]
       lineText = lineText.rstrip() # remove ending whitespace
-      
+
       # blank lines are hidden when stripping comments
       if self.stripComments and not lineText: continue
-      
+
       # splits the line into its component (msg, format) tuples
       lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")],
                   "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                   "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
                   "comment": ["", uiTools.getColor("white")]}
-      
+
       # parses the comment
       commentIndex = lineText.find("#")
       if commentIndex != -1:
         lineComp["comment"][0] = lineText[commentIndex:]
         lineText = lineText[:commentIndex]
-      
+
       # splits the option and argument, preserving any whitespace around them
       strippedLine = lineText.strip()
       optionIndex = strippedLine.find(" ")
@@ -238,15 +238,15 @@ class TorrcPanel(panel.Panel):
         optionEnd = lineText.find(optionText) + len(optionText)
         lineComp["option"][0] = lineText[:optionEnd]
         lineComp["argument"][0] = lineText[optionEnd:]
-      
+
       # flags following lines as belonging to this multiline entry if it ends
       # with a slash
       if strippedLine: isMultiline = strippedLine.endswith("\\")
-      
+
       # gets the correction
       if lineNumber in corrections:
         lineIssue, lineIssueMsg = corrections[lineNumber]
-        
+
         if lineIssue in (torConfig.ValidationError.DUPLICATE, torConfig.ValidationError.IS_DEFAULT):
           lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
           lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
@@ -258,20 +258,20 @@ class TorrcPanel(panel.Panel):
           # provide extra data (for instance, the type for tor state fields).
           lineComp["correction"][0] = " (%s)" % lineIssueMsg
           lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")
-      
+
       # draws the line number
       if self.showLineNum and displayLine < height and displayLine >= 1:
         lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
         self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))
-      
+
       # draws the rest of the components with line wrap
       cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
       maxLinesPerEntry = CONFIG["features.config.file.maxLinesPerEntry"]
       displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")]
-      
+
       while displayQueue:
         msg, format = displayQueue.pop(0)
-        
+
         maxMsgSize, includeBreak = width - cursorLoc, False
         if len(msg) >= maxMsgSize:
           # message is too long - break it up
@@ -281,31 +281,31 @@ class TorrcPanel(panel.Panel):
             includeBreak = True
             msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.Ending.HYPHEN, True)
             displayQueue.insert(0, (remainder.strip(), format))
-        
+
         drawLine = displayLine + lineOffset
         if msg and drawLine < height and drawLine >= 1:
           self.addstr(drawLine, cursorLoc, msg, format)
-        
+
         # If we're done, and have added content to this line, then start
         # further content on the next line.
         cursorLoc += len(msg)
         includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset
-        
+
         if includeBreak:
           lineOffset += 1
           cursorLoc = lineNumOffset + scrollOffset
-      
+
       displayLine += max(lineOffset, 1)
-      
+
       if trustLastContentHeight and displayLine >= height: break
-    
+
     if not trustLastContentHeight:
       self._lastContentHeightArgs = (width, height)
       newContentHeight = displayLine + self.scroll - 1
-      
+
       if self._lastContentHeight != newContentHeight:
         self._lastContentHeight = newContentHeight
         self.redraw(True)
-    
+
     self.valsLock.release()
 
diff --git a/arm/util/__init__.py b/arm/util/__init__.py
index 3e21520..33897a1 100644
--- a/arm/util/__init__.py
+++ b/arm/util/__init__.py
@@ -1,6 +1,6 @@
 """
-General purpose utilities for a variety of tasks including logging the 
-application's status, making cross platform system calls, parsing tor data, 
+General purpose utilities for a variety of tasks including logging the
+application's status, making cross platform system calls, parsing tor data,
 and safely working with curses (hiding some of the gory details).
 """
 
diff --git a/arm/util/connections.py b/arm/util/connections.py
index aa2aa2e..423a555 100644
--- a/arm/util/connections.py
+++ b/arm/util/connections.py
@@ -57,7 +57,7 @@ RUN_SS = "ss -nptu"
 # -w = no warnings
 # output:
 # tor  3873  atagar  45u  IPv4  40994  0t0  TCP 10.243.55.20:45724->194.154.227.109:9001 (ESTABLISHED)
-# 
+#
 # oddly, using the -p flag via:
 # lsof      lsof -nPi -p <pid> | grep "^<process>.*(ESTABLISHED)"
 # is much slower (11-28% in tests I ran)
@@ -79,7 +79,7 @@ RESOLVER_FINAL_FAILURE_MSG = "All connection resolvers failed"
 def conf_handler(key, value):
   if key.startswith("port.label."):
     portEntry = key[11:]
-    
+
     divIndex = portEntry.find("-")
     if divIndex == -1:
       # single port
@@ -94,7 +94,7 @@ def conf_handler(key, value):
         minPort = int(portEntry[:divIndex])
         maxPort = int(portEntry[divIndex + 1:])
         if minPort > maxPort: raise ValueError()
-        
+
         for port in range(minPort, maxPort + 1):
           PORT_USAGE[str(port)] = value
       except ValueError:
@@ -111,15 +111,15 @@ def isValidIpAddress(ipStr):
   """
   Returns true if input is a valid IPv4 address, false otherwise.
   """
-  
+
   # checks if theres four period separated values
   if not ipStr.count(".") == 3: return False
-  
+
   # checks that each value in the octet are decimal values between 0-255
   for ipComp in ipStr.split("."):
     if not ipComp.isdigit() or int(ipComp) < 0 or int(ipComp) > 255:
       return False
-  
+
   return True
 
 def isIpAddressPrivate(ipAddr):
@@ -128,49 +128,49 @@ def isIpAddressPrivate(ipAddr):
   loopback, false otherwise. These include:
   Private ranges: 10.*, 172.16.* - 172.31.*, 192.168.*
   Loopback: 127.*
-  
+
   Arguments:
     ipAddr - IP address to be checked
   """
-  
+
   # checks for any of the simple wildcard ranges
   if ipAddr.startswith("10.") or ipAddr.startswith("192.168.") or ipAddr.startswith("127."):
     return True
-  
+
   # checks for the 172.16.* - 172.31.* range
   if ipAddr.startswith("172.") and ipAddr.count(".") == 3:
     secondOctet = ipAddr[4:ipAddr.find(".", 4)]
-    
+
     if secondOctet.isdigit() and int(secondOctet) >= 16 and int(secondOctet) <= 31:
       return True
-  
+
   return False
 
 def ipToInt(ipAddr):
   """
   Provides an integer representation of the ip address, suitable for sorting.
-  
+
   Arguments:
     ipAddr - ip address to be converted
   """
-  
+
   total = 0
-  
+
   for comp in ipAddr.split("."):
     total *= 255
     total += int(comp)
-  
+
   return total
 
 def getPortUsage(port):
   """
   Provides the common use of a given port. If no useage is known then this
   provides None.
-  
+
   Arguments:
     port - port number to look up
   """
-  
+
   return PORT_USAGE.get(port)
 
 def getResolverCommand(resolutionCmd, processName, processPid = ""):
@@ -178,23 +178,23 @@ def getResolverCommand(resolutionCmd, processName, processPid = ""):
   Provides the command and line filter that would be processed for the given
   resolver type. This raises a ValueError if either the resolutionCmd isn't
   recognized or a pid was requited but not provided.
-  
+
   Arguments:
     resolutionCmd - command to use in resolving the address
     processName   - name of the process for which connections are fetched
     processPid    - process ID (this helps improve accuracy)
   """
-  
+
   if not processPid:
     # the pid is required for procstat resolution
     if resolutionCmd == Resolver.BSD_PROCSTAT:
       raise ValueError("procstat resolution requires a pid")
-    
+
     # if the pid was undefined then match any in that field
     processPid = "[0-9]*"
-  
+
   no_op_filter = lambda line: True
-  
+
   if resolutionCmd == Resolver.PROC: return ("", no_op_filter)
   elif resolutionCmd == Resolver.NETSTAT:
     return (
@@ -238,18 +238,18 @@ def getConnections(resolutionCmd, processName, processPid = ""):
     - insufficient permissions
     - resolution command is unavailable
     - usage of the command is non-standard (particularly an issue for BSD)
-  
+
   Arguments:
     resolutionCmd - command to use in resolving the address
     processName   - name of the process for which connections are fetched
     processPid    - process ID (this helps improve accuracy)
   """
-  
+
   if resolutionCmd == Resolver.PROC:
     # Attempts resolution via checking the proc contents.
     if not processPid:
       raise ValueError("proc resolution requires a pid")
-    
+
     try:
       return proc.get_connections(processPid)
     except Exception, exc:
@@ -260,9 +260,9 @@ def getConnections(resolutionCmd, processName, processPid = ""):
     cmd, cmd_filter = getResolverCommand(resolutionCmd, processName, processPid)
     results = system.call(cmd)
     results = filter(cmd_filter, results)
-    
+
     if not results: raise IOError("No results found using: %s" % cmd)
-    
+
     # parses results for the resolution command
     conn = []
     for line in results:
@@ -272,7 +272,7 @@ def getConnections(resolutionCmd, processName, processPid = ""):
         # the last one.
         comp = line.replace("(ESTABLISHED)", "").strip().split()
       else: comp = line.split()
-      
+
       if resolutionCmd == Resolver.NETSTAT:
         localIp, localPort = comp[3].split(":")
         foreignIp, foreignPort = comp[4].split(":")
@@ -292,40 +292,40 @@ def getConnections(resolutionCmd, processName, processPid = ""):
       elif resolutionCmd == Resolver.BSD_PROCSTAT:
         localIp, localPort = comp[9].split(":")
         foreignIp, foreignPort = comp[10].split(":")
-      
+
       conn.append((localIp, localPort, foreignIp, foreignPort))
-    
+
     return conn
 
 def isResolverAlive(processName, processPid = ""):
   """
   This provides true if a singleton resolver instance exists for the given
   process/pid combination, false otherwise.
-  
+
   Arguments:
     processName - name of the process being checked
     processPid  - pid of the process being checked, if undefined this matches
                   against any resolver with the process name
   """
-  
+
   for resolver in RESOLVERS:
     if not resolver._halt and resolver.processName == processName and (not processPid or resolver.processPid == processPid):
       return True
-  
+
   return False
 
 def getResolver(processName, processPid = "", alias=None):
   """
   Singleton constructor for resolver instances. If a resolver already exists
   for the process then it's returned. Otherwise one is created and started.
-  
+
   Arguments:
     processName - name of the process being resolved
     processPid  - pid of the process being resolved, if undefined this matches
                   against any resolver with the process name
     alias       - alternative handle under which the resolver can be requested
   """
-  
+
   # check if one's already been created
   requestHandle = alias if alias else processName
   haltedIndex = -1 # old instance of this resolver with the _halt flag set
@@ -334,11 +334,11 @@ def getResolver(processName, processPid = "", alias=None):
     if resolver.handle == requestHandle and (not processPid or resolver.processPid == processPid):
       if resolver._halt and RECREATE_HALTED_RESOLVERS: haltedIndex = i
       else: return resolver
-  
+
   # make a new resolver
   r = ConnectionResolver(processName, processPid, handle = requestHandle)
   r.start()
-  
+
   # overwrites halted instance of this resolver if it exists, otherwise append
   if haltedIndex == -1: RESOLVERS.append(r)
   else: RESOLVERS[haltedIndex] = r
@@ -348,24 +348,24 @@ def getSystemResolvers(osType = None):
   """
   Provides the types of connection resolvers available on this operating
   system.
-  
+
   Arguments:
     osType - operating system type, fetched from the os module if undefined
   """
-  
+
   if osType == None: osType = os.uname()[0]
-  
+
   if osType == "FreeBSD":
     resolvers = [Resolver.BSD_SOCKSTAT, Resolver.BSD_PROCSTAT, Resolver.LSOF]
   elif osType in ("OpenBSD", "Darwin"):
     resolvers = [Resolver.LSOF]
   else:
     resolvers = [Resolver.NETSTAT, Resolver.SOCKSTAT, Resolver.LSOF, Resolver.SS]
-  
+
   # proc resolution, by far, outperforms the others so defaults to this is able
   if proc.is_available():
     resolvers = [Resolver.PROC] + resolvers
-  
+
   return resolvers
 
 class ConnectionResolver(threading.Thread):
@@ -376,24 +376,24 @@ class ConnectionResolver(threading.Thread):
   - falls back to use different resolution methods in case of repeated failures
   - avoids overly frequent querying of connection data, which can be demanding
     in terms of system resources
-  
+
   Unless an overriding method of resolution is requested this defaults to
   choosing a resolver the following way:
-  
+
   - Checks the current PATH to determine which resolvers are available. This
     uses the first of the following that's available:
       netstat, ss, lsof (picks netstat if none are found)
-  
+
   - Attempts to resolve using the selection. Single failures are logged at the
     INFO level, and a series of failures at NOTICE. In the later case this
     blacklists the resolver, moving on to the next. If all resolvers fail this
     way then resolution's abandoned and logs a WARN message.
-  
+
   The time between resolving connections, unless overwritten, is set to be
   either five seconds or ten times the runtime of the resolver (whichever is
   larger). This is to prevent systems either strapped for resources or with a
   vast number of connections from being burdened too heavily by this daemon.
-  
+
   Parameters:
     processName       - name of the process being resolved
     processPid        - pid of the process being resolved
@@ -406,15 +406,15 @@ class ConnectionResolver(threading.Thread):
     * defaultResolver - resolver used by default (None if all resolution
                         methods have been exhausted)
     resolverOptions   - resolvers to be cycled through (differ by os)
-    
+
     * read-only
   """
-  
+
   def __init__(self, processName, processPid = "", resolveRate = None, handle = None):
     """
     Initializes a new resolver daemon. When no longer needed it's suggested
     that this is stopped.
-    
+
     Arguments:
       processName - name of the process being resolved
       processPid  - pid of the process being resolved
@@ -423,10 +423,10 @@ class ConnectionResolver(threading.Thread):
       handle      - name used to query this resolver, this is the processName
                     if undefined
     """
-    
+
     threading.Thread.__init__(self)
     self.setDaemon(True)
-    
+
     self.processName = processName
     self.processPid = processPid
     self.resolveRate = resolveRate
@@ -435,23 +435,23 @@ class ConnectionResolver(threading.Thread):
     self.lastLookup = -1
     self.overwriteResolver = None
     self.defaultResolver = Resolver.PROC
-    
+
     osType = os.uname()[0]
     self.resolverOptions = getSystemResolvers(osType)
-    
+
     log.info("Operating System: %s, Connection Resolvers: %s" % (osType, ", ".join(self.resolverOptions)))
-    
+
     # sets the default resolver to be the first found in the system's PATH
     # (left as netstat if none are found)
     for resolver in self.resolverOptions:
       # Resolver strings correspond to their command with the exception of bsd
       # resolvers.
       resolverCmd = resolver.replace(" (bsd)", "")
-      
+
       if resolver == Resolver.PROC or system.is_available(resolverCmd):
         self.defaultResolver = resolver
         break
-    
+
     self._connections = []        # connection cache (latest results)
     self._resolutionCounter = 0   # number of successful connection resolutions
     self._isPaused = False
@@ -459,70 +459,70 @@ class ConnectionResolver(threading.Thread):
     self._cond = threading.Condition()  # used for pausing the thread
     self._subsiquentFailures = 0  # number of failed resolutions with the default in a row
     self._resolverBlacklist = []  # resolvers that have failed to resolve
-    
+
     # Number of sequential times the threshold rate's been too low. This is to
     # avoid having stray spikes up the rate.
     self._rateThresholdBroken = 0
-  
+
   def getOverwriteResolver(self):
     """
     Provides the resolver connection resolution is forced to use. This returns
     None if it's dynamically determined.
     """
-    
+
     return self.overwriteResolver
-     
+
   def setOverwriteResolver(self, overwriteResolver):
     """
     Sets the resolver used for connection resolution, if None then this is
     automatically determined based on what is available.
-    
+
     Arguments:
       overwriteResolver - connection resolver to be used
     """
-    
+
     self.overwriteResolver = overwriteResolver
-  
+
   def run(self):
     while not self._halt:
       minWait = self.resolveRate if self.resolveRate else self.defaultRate
       timeSinceReset = time.time() - self.lastLookup
-      
+
       if self._isPaused or timeSinceReset < minWait:
         sleepTime = max(0.2, minWait - timeSinceReset)
-        
+
         self._cond.acquire()
         if not self._halt: self._cond.wait(sleepTime)
         self._cond.release()
-        
+
         continue # done waiting, try again
-      
+
       isDefault = self.overwriteResolver == None
       resolver = self.defaultResolver if isDefault else self.overwriteResolver
-      
+
       # checks if there's nothing to resolve with
       if not resolver:
         self.lastLookup = time.time() # avoids a busy wait in this case
         continue
-      
+
       try:
         resolveStart = time.time()
         connResults = getConnections(resolver, self.processName, self.processPid)
         lookupTime = time.time() - resolveStart
-        
+
         self._connections = connResults
         self._resolutionCounter += 1
-        
+
         newMinDefaultRate = 100 * lookupTime
         if self.defaultRate < newMinDefaultRate:
           if self._rateThresholdBroken >= 3:
             # adding extra to keep the rate from frequently changing
             self.defaultRate = newMinDefaultRate + 0.5
-            
+
             log.trace("connection lookup time increasing to %0.1f seconds per call" % self.defaultRate)
           else: self._rateThresholdBroken += 1
         else: self._rateThresholdBroken = 0
-        
+
         if isDefault: self._subsiquentFailures = 0
       except (ValueError, IOError), exc:
         # this logs in a couple of cases:
@@ -531,86 +531,86 @@ class ConnectionResolver(threading.Thread):
         # - note fail-overs for default resolution methods
         if str(exc).startswith("No results found using:"):
           log.info(exc)
-        
+
         if isDefault:
           self._subsiquentFailures += 1
-          
+
           if self._subsiquentFailures >= RESOLVER_FAILURE_TOLERANCE:
             # failed several times in a row - abandon resolver and move on to another
             self._resolverBlacklist.append(resolver)
             self._subsiquentFailures = 0
-            
+
             # pick another (non-blacklisted) resolver
             newResolver = None
             for r in self.resolverOptions:
               if not r in self._resolverBlacklist:
                 newResolver = r
                 break
-            
+
             if newResolver:
               # provide notice that failures have occurred and resolver is changing
               log.notice(RESOLVER_SERIAL_FAILURE_MSG % (resolver, newResolver))
             else:
               # exhausted all resolvers, give warning
               log.notice(RESOLVER_FINAL_FAILURE_MSG)
-            
+
             self.defaultResolver = newResolver
       finally:
         self.lastLookup = time.time()
-  
+
   def getConnections(self):
     """
     Provides the last queried connection results, an empty list if resolver
     has been halted.
     """
-    
+
     if self._halt: return []
     else: return list(self._connections)
-  
+
   def getResolutionCount(self):
     """
     Provides the number of successful resolutions so far. This can be used to
     determine if the connection results are new for the caller or not.
     """
-    
+
     return self._resolutionCounter
-  
+
   def getPid(self):
     """
     Provides the pid used to narrow down connection resolution. This is an
     empty string if undefined.
     """
-    
+
     return self.processPid
-  
+
   def setPid(self, processPid):
     """
     Sets the pid used to narrow down connection resultions.
-    
+
     Arguments:
       processPid - pid for the process we're fetching connections for
     """
-    
+
     self.processPid = processPid
-  
+
   def setPaused(self, isPause):
     """
     Allows or prevents further connection resolutions (this still makes use of
     cached results).
-    
+
     Arguments:
       isPause - puts a freeze on further resolutions if true, allows them to
                 continue otherwise
     """
-    
+
     if isPause == self._isPaused: return
     self._isPaused = isPause
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self._cond.acquire()
     self._halt = True
     self._cond.notifyAll()
@@ -622,89 +622,89 @@ class AppResolver:
   stops attempting to query if it fails three times without successfully
   getting lsof results.
   """
-  
+
   def __init__(self, scriptName = "python"):
     """
     Constructs a resolver instance.
-    
+
     Arguments:
       scriptName - name by which to all our own entries
     """
-    
+
     self.scriptName = scriptName
     self.queryResults = {}
     self.resultsLock = threading.RLock()
     self._cond = threading.Condition()  # used for pausing when waiting for results
     self.isResolving = False  # flag set if we're in the process of making a query
     self.failureCount = 0     # -1 if we've made a successful query
-  
+
   def getResults(self, maxWait=0):
     """
     Provides the last queried results. If we're in the process of making a
     query then we can optionally block for a time to see if it finishes.
-    
+
     Arguments:
       maxWait - maximum second duration to block on getting results before
                 returning
     """
-    
+
     self._cond.acquire()
     if self.isResolving and maxWait > 0:
       self._cond.wait(maxWait)
     self._cond.release()
-    
+
     self.resultsLock.acquire()
     results = dict(self.queryResults)
     self.resultsLock.release()
-    
+
     return results
-  
+
   def resolve(self, ports):
     """
     Queues the given listing of ports to be resolved. This clears the last set
     of results when completed.
-    
+
     Arguments:
       ports - list of ports to be resolved to applications
     """
-    
+
     if self.failureCount < 3:
       self.isResolving = True
       t = threading.Thread(target = self._queryApplications, kwargs = {"ports": ports})
       t.setDaemon(True)
       t.start()
-  
+
   def _queryApplications(self, ports=[]):
     """
     Performs an lsof lookup on the given ports to get the command/pid tuples.
-    
+
     Arguments:
       ports - list of ports to be resolved to applications
     """
-    
+
     # atagar at fenrir:~/Desktop/arm$ lsof -i tcp:51849 -i tcp:37277
     # COMMAND  PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
     # tor     2001 atagar   14u  IPv4  14048      0t0  TCP localhost:9051->localhost:37277 (ESTABLISHED)
     # tor     2001 atagar   15u  IPv4  22024      0t0  TCP localhost:9051->localhost:51849 (ESTABLISHED)
     # python  2462 atagar    3u  IPv4  14047      0t0  TCP localhost:37277->localhost:9051 (ESTABLISHED)
     # python  3444 atagar    3u  IPv4  22023      0t0  TCP localhost:51849->localhost:9051 (ESTABLISHED)
-    
+
     if not ports:
       self.resultsLock.acquire()
       self.queryResults = {}
       self.isResolving = False
       self.resultsLock.release()
-      
+
       # wakes threads waiting on results
       self._cond.acquire()
       self._cond.notifyAll()
       self._cond.release()
-      
+
       return
-    
+
     results = {}
     lsofArgs = []
-    
+
     # Uses results from the last query if we have any, otherwise appends the
     # port to the lsof command. This has the potential for persisting dirty
     # results but if we're querying by the dynamic port on the local tcp
@@ -714,11 +714,11 @@ class AppResolver:
       if port in self.queryResults:
         results[port] = self.queryResults[port]
       else: lsofArgs.append("-i tcp:%s" % port)
-    
+
     if lsofArgs:
       lsofResults = system.call("lsof -nP " + " ".join(lsofArgs))
     else: lsofResults = None
-    
+
     if not lsofResults and self.failureCount != -1:
       # lsof query failed and we aren't yet sure if it's possible to
       # successfully get results on this platform
@@ -728,26 +728,26 @@ class AppResolver:
     elif lsofResults:
       # (iPort, oPort) tuple for our own process, if it was fetched
       ourConnection = None
-      
+
       for line in lsofResults:
         lineComp = line.split()
-        
+
         if len(lineComp) == 10 and lineComp[9] == "(ESTABLISHED)":
           cmd, pid, _, _, _, _, _, _, portMap, _ = lineComp
-          
+
           if "->" in portMap:
             iPort, oPort = portMap.split("->")
             iPort = iPort.split(":")[1]
             oPort = oPort.split(":")[1]
-            
+
             # entry belongs to our own process
             if pid == str(os.getpid()):
               cmd = self.scriptName
               ourConnection = (iPort, oPort)
-            
+
             if iPort.isdigit() and oPort.isdigit():
               newEntry = (iPort, oPort, cmd, pid)
-              
+
               # adds the entry under the key of whatever we queried it with
               # (this might be both the inbound _and_ outbound ports)
               for portMatch in (iPort, oPort):
@@ -755,27 +755,27 @@ class AppResolver:
                   if portMatch in results:
                     results[portMatch].append(newEntry)
                   else: results[portMatch] = [newEntry]
-      
+
       # making the lsof call generated an extraneous sh entry for our own connection
       if ourConnection:
         for ourPort in ourConnection:
           if ourPort in results:
             shIndex = None
-            
+
             for i in range(len(results[ourPort])):
               if results[ourPort][i][2] == "sh":
                 shIndex = i
                 break
-            
+
             if shIndex != None:
               del results[ourPort][shIndex]
-    
+
     self.resultsLock.acquire()
     self.failureCount = -1
     self.queryResults = results
     self.isResolving = False
     self.resultsLock.release()
-    
+
     # wakes threads waiting on results
     self._cond.acquire()
     self._cond.notifyAll()
diff --git a/arm/util/hostnames.py b/arm/util/hostnames.py
index a58eb94..a829f9f 100644
--- a/arm/util/hostnames.py
+++ b/arm/util/hostnames.py
@@ -63,7 +63,7 @@ def start():
   not necessary since resolving any address will start the service if it isn't
   already running.
   """
-  
+
   global RESOLVER
   RESOLVER_LOCK.acquire()
   if not isRunning(): RESOLVER = _Resolver()
@@ -74,7 +74,7 @@ def stop():
   Halts further resolutions and stops the service. This joins on the resolver's
   thread pool and clears its lookup cache.
   """
-  
+
   global RESOLVER
   RESOLVER_LOCK.acquire()
   if isRunning():
@@ -83,7 +83,7 @@ def stop():
     # all calls currently in progress can still proceed on the RESOLVER's local
     # references.
     resolverRef, RESOLVER = RESOLVER, None
-    
+
     # joins on its worker thread pool
     resolverRef.stop()
     for t in resolverRef.threadPool: t.join()
@@ -94,12 +94,12 @@ def setPaused(isPause):
   Allows or prevents further hostname resolutions (resolutions still make use of
   cached entries if available). This starts the service if it isn't already
   running.
-  
+
   Arguments:
     isPause - puts a freeze on further resolutions if true, allows them to
               continue otherwise
   """
-  
+
   # makes sure a running resolver is set with the pausing setting
   RESOLVER_LOCK.acquire()
   start()
@@ -110,14 +110,14 @@ def isRunning():
   """
   Returns True if the service is currently running, False otherwise.
   """
-  
+
   return bool(RESOLVER)
 
 def isPaused():
   """
   Returns True if the resolver is paused, False otherwise.
   """
-  
+
   resolverRef = RESOLVER
   if resolverRef: return resolverRef.isPaused
   else: return False
@@ -127,7 +127,7 @@ def isResolving():
   Returns True if addresses are currently waiting to be resolved, False
   otherwise.
   """
-  
+
   resolverRef = RESOLVER
   if resolverRef: return not resolverRef.unresolvedQueue.empty()
   else: return False
@@ -139,14 +139,14 @@ def resolve(ipAddr, timeout = 0, suppressIOExc = True):
   lookup if not. This provides None if the lookup fails (with a suppressed
   exception) or timeout is reached without resolution. This starts the service
   if it isn't already running.
-  
+
   If paused this simply returns the cached reply (no request is queued and
   returns immediately regardless of the timeout argument).
-  
+
   Requests may raise the following exceptions:
   - ValueError - address was unresolvable (includes the DNS error response)
   - IOError - lookup failed due to os or network issues (suppressed by default)
-  
+
   Arguments:
     ipAddr        - ip address to be resolved
     timeout       - maximum duration to wait for a resolution (blocks to
@@ -154,7 +154,7 @@ def resolve(ipAddr, timeout = 0, suppressIOExc = True):
     suppressIOExc - suppresses lookup errors and re-runs failed calls if true,
                     raises otherwise
   """
-  
+
   # starts the service if it isn't already running (making sure we have an
   # instance in a thread safe fashion before continuing)
   resolverRef = RESOLVER
@@ -163,11 +163,11 @@ def resolve(ipAddr, timeout = 0, suppressIOExc = True):
     start()
     resolverRef = RESOLVER
     RESOLVER_LOCK.release()
-  
+
   if resolverRef.isPaused:
     # get cache entry, raising if an exception and returning if a hostname
     cacheRef = resolverRef.resolvedCache
-    
+
     if ipAddr in cacheRef:
       entry = cacheRef[ipAddr][0]
       if suppressIOExc and type(entry) == IOError: return None
@@ -179,7 +179,7 @@ def resolve(ipAddr, timeout = 0, suppressIOExc = True):
     # suppression since these error may be transient)
     cacheRef = resolverRef.resolvedCache
     flush = ipAddr in cacheRef and type(cacheRef[ipAddr]) == IOError
-    
+
     try: return resolverRef.getHostname(ipAddr, timeout, flush)
     except IOError: return None
   else: return resolverRef.getHostname(ipAddr, timeout)
@@ -189,7 +189,7 @@ def getPendingCount():
   Provides an approximate count of the number of addresses still pending
   resolution.
   """
-  
+
   resolverRef = RESOLVER
   if resolverRef: return resolverRef.unresolvedQueue.qsize()
   else: return 0
@@ -198,7 +198,7 @@ def getRequestCount():
   """
   Provides the number of resolutions requested since starting the service.
   """
-  
+
   resolverRef = RESOLVER
   if resolverRef: return resolverRef.totalResolves
   else: return 0
@@ -208,11 +208,11 @@ def _resolveViaSocket(ipAddr):
   Performs hostname lookup via the socket module's gethostbyaddr function. This
   raises an IOError if the lookup fails (network issue) and a ValueError in
   case of DNS errors (address unresolvable).
-  
+
   Arguments:
     ipAddr - ip address to be resolved
   """
-  
+
   try:
     # provides tuple like: ('localhost', [], ['127.0.0.1'])
     return socket.gethostbyaddr(ipAddr)[0]
@@ -226,13 +226,13 @@ def _resolveViaHost(ipAddr):
   Performs a host lookup for the given IP, returning the resolved hostname.
   This raises an IOError if the lookup fails (os or network issue), and a
   ValueError in the case of DNS errors (address is unresolvable).
-  
+
   Arguments:
     ipAddr - ip address to be resolved
   """
-  
+
   hostname = system.call("host %s" % ipAddr)[0].split()[-1:][0]
-  
+
   if hostname == "reached":
     # got message: ";; connection timed out; no servers could be reached"
     raise IOError("lookup timed out")
@@ -248,12 +248,12 @@ class _Resolver():
   Performs reverse DNS resolutions. Lookups are a network bound operation so
   this spawns a pool of worker threads to do several at a time in parallel.
   """
-  
+
   def __init__(self):
     # IP Address => (hostname/error, age), resolution failures result in a
     # ValueError with the lookup's status
     self.resolvedCache = {}
-    
+
     self.resolvedLock = threading.RLock() # governs concurrent access when modifying resolvedCache
     self.unresolvedQueue = Queue.Queue()  # unprocessed lookup requests
     self.recentQueries = []               # recent resolution requests to prevent duplicate requests
@@ -262,42 +262,42 @@ class _Resolver():
     self.isPaused = False                 # prevents further resolutions if true
     self.halt = False                     # if true, tells workers to stop
     self.cond = threading.Condition()     # used for pausing threads
-    
+
     # Determines if resolutions are made using os 'host' calls or python's
     # 'socket.gethostbyaddr'. The following checks if the system has the
     # gethostbyname_r function, which determines if python resolutions can be
     # done in parallel or not. If so, this is preferable.
     isSocketResolutionParallel = distutils.sysconfig.get_config_var("HAVE_GETHOSTBYNAME_R")
     self.useSocketResolution = CONFIG["queries.hostnames.useSocketModule"] and isSocketResolutionParallel
-    
+
     for _ in range(CONFIG["queries.hostnames.poolSize"]):
       t = threading.Thread(target = self._workerLoop)
       t.setDaemon(True)
       t.start()
       self.threadPool.append(t)
-  
+
   def getHostname(self, ipAddr, timeout, flushCache = False):
     """
     Provides the hostname, queuing the request and returning None if the
     timeout is reached before resolution. If a problem's encountered then this
     either raises an IOError (for os and network issues) or ValueError (for DNS
     resolution errors).
-    
+
     Arguments:
       ipAddr     - ip address to be resolved
       timeout    - maximum duration to wait for a resolution (blocks to
                    completion if None)
       flushCache - if true the cache is skipped and address re-resolved
     """
-    
+
     # if outstanding requests are done then clear recentQueries to allow
     # entries removed from the cache to be re-run
     if self.unresolvedQueue.empty(): self.recentQueries = []
-    
+
     # copies reference cache (this is important in case the cache is trimmed
     # during this call)
     cacheRef = self.resolvedCache
-    
+
     if not flushCache and ipAddr in cacheRef:
       # cached response is available - raise if an error, return if a hostname
       response = cacheRef[ipAddr][0]
@@ -308,11 +308,11 @@ class _Resolver():
       self.totalResolves += 1
       self.recentQueries.append(ipAddr)
       self.unresolvedQueue.put(ipAddr)
-    
+
     # periodically check cache if requester is willing to wait
     if timeout == None or timeout > 0:
       startTime = time.time()
-      
+
       while timeout == None or time.time() - startTime < timeout:
         if ipAddr in cacheRef:
           # address was resolved - raise if an error, return if a hostname
@@ -320,19 +320,19 @@ class _Resolver():
           if isinstance(response, Exception): raise response
           else: return response
         else: time.sleep(0.1)
-    
+
     return None # timeout reached without resolution
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self.cond.acquire()
     self.halt = True
     self.cond.notifyAll()
     self.cond.release()
-  
+
   def _workerLoop(self):
     """
     Simple producer-consumer loop followed by worker threads. This takes
@@ -340,7 +340,7 @@ class _Resolver():
     adds its results or the error to the resolved cache. Resolver reference
     provides shared resources used by the thread pool.
     """
-    
+
     while not self.halt:
       # if resolver is paused then put a hold on further resolutions
       if self.isPaused:
@@ -348,7 +348,7 @@ class _Resolver():
         if not self.halt: self.cond.wait(1)
         self.cond.release()
         continue
-      
+
       # snags next available ip, timeout is because queue can't be woken up
       # when 'halt' is set
       try: ipAddr = self.unresolvedQueue.get_nowait()
@@ -359,36 +359,36 @@ class _Resolver():
         self.cond.release()
         continue
       if self.halt: break
-      
+
       try:
         if self.useSocketResolution: result = _resolveViaSocket(ipAddr)
         else: result = _resolveViaHost(ipAddr)
       except IOError, exc: result = exc # lookup failed
       except ValueError, exc: result = exc # dns error
-      
+
       self.resolvedLock.acquire()
       self.resolvedCache[ipAddr] = (result, RESOLVER_COUNTER.next())
-      
+
       # trim cache if excessively large (clearing out oldest entries)
       if len(self.resolvedCache) > CONFIG["cache.hostnames.size"]:
         # Providing for concurrent, non-blocking calls require that entries are
         # never removed from the cache, so this creates a new, trimmed version
         # instead.
-        
+
         # determines minimum age of entries to be kept
         currentCount = RESOLVER_COUNTER.next()
         newCacheSize = CONFIG["cache.hostnames.size"] - CONFIG["cache.hostnames.trimSize"]
         threshold = currentCount - newCacheSize
         newCache = {}
-        
+
         msg = "trimming hostname cache from %i entries to %i" % (len(self.resolvedCache), newCacheSize)
         log.info(msg)
-        
+
         # checks age of each entry, adding to toDelete if too old
         for ipAddr, entry in self.resolvedCache.iteritems():
           if entry[1] >= threshold: newCache[ipAddr] = entry
-        
+
         self.resolvedCache = newCache
-      
+
       self.resolvedLock.release()
-  
+
diff --git a/arm/util/panel.py b/arm/util/panel.py
index 17b99b7..fb33c7a 100644
--- a/arm/util/panel.py
+++ b/arm/util/panel.py
@@ -13,7 +13,7 @@ from arm.util import textInput, uiTools
 
 from stem.util import log
 
-# global ui lock governing all panel instances (curses isn't thread save and 
+# global ui lock governing all panel instances (curses isn't thread save and
 # concurrency bugs produce especially sinister glitches)
 CURSES_LOCK = RLock()
 
@@ -36,16 +36,16 @@ class Panel():
     - gracefully handle terminal resizing
     - clip text that falls outside the panel
     - convenience methods for word wrap, in-line formatting, etc
-  
+
   This uses a design akin to Swing where panel instances provide their display
   implementation by overwriting the draw() method, and are redrawn with
   redraw().
   """
-  
+
   def __init__(self, parent, name, top, left=0, height=-1, width=-1):
     """
     Creates a durable wrapper for a curses subwindow in the given parent.
-    
+
     Arguments:
       parent - parent curses window
       name   - identifier for the panel
@@ -54,112 +54,112 @@ class Panel():
       height - maximum height of panel (uses all available space if -1)
       width  - maximum width of panel (uses all available space if -1)
     """
-    
+
     # The not-so-pythonic getters for these parameters are because some
     # implementations aren't entirely deterministic (for instance panels
     # might chose their height based on its parent's current width).
-    
+
     self.panelName = name
     self.parent = parent
     self.visible = False
     self.titleVisible = 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.left = left
     self.height = height
     self.width = width
-    
+
     # The panel's subwindow instance. This is made available to implementors
     # via their draw method and shouldn't be accessed directly.
-    # 
+    #
     # This is None if either the subwindow failed to be created or needs to be
     # remade before it's used. The later could be for a couple reasons:
     # - The subwindow was never initialized.
     # - Any of the parameters used for subwindow initialization have changed.
     self.win = None
-    
+
     self.maxY, self.maxX = -1, -1 # subwindow dimensions when last redrawn
-  
+
   def getName(self):
     """
     Provides panel's identifier.
     """
-    
+
     return self.panelName
-  
+
   def isTitleVisible(self):
     """
     True if the title is configured to be visible, False otherwise.
     """
-    
+
     return self.titleVisible
-  
+
   def setTitleVisible(self, isVisible):
     """
     Configures the panel's title to be visible or not when it's next redrawn.
     This is not guarenteed to be respected (not all panels have a title).
     """
-    
+
     self.titleVisible = isVisible
-  
+
   def getParent(self):
     """
     Provides the parent used to create subwindows.
     """
-    
+
     return self.parent
-  
+
   def setParent(self, parent):
     """
     Changes the parent used to create subwindows.
-    
+
     Arguments:
       parent - parent curses window
     """
-    
+
     if self.parent != parent:
       self.parent = parent
       self.win = None
-  
+
   def isVisible(self):
     """
     Provides if the panel's configured to be visible or not.
     """
-    
+
     return self.visible
-  
+
   def setVisible(self, isVisible):
     """
     Toggles if the panel is visible or not.
-    
+
     Arguments:
       isVisible - panel is redrawn when requested if true, skipped otherwise
     """
-    
+
     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
@@ -167,160 +167,160 @@ class Panel():
     > 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.paused: 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.
     """
-    
+
     return self.top
-  
+
   def setTop(self, top):
     """
     Changes the position where subwindows are placed within its parent.
-    
+
     Arguments:
       top - positioning of top within parent
     """
-    
+
     if self.top != top:
       self.top = top
       self.win = None
-  
+
   def getLeft(self):
     """
     Provides the left position where this subwindow is placed within its
     parent.
     """
-    
+
     return self.left
-  
+
   def setLeft(self, left):
     """
     Changes the left position where this subwindow is placed within its parent.
-    
+
     Arguments:
       left - positioning of top within parent
     """
-    
+
     if self.left != left:
       self.left = left
       self.win = None
-  
+
   def getHeight(self):
     """
     Provides the height used for subwindows (-1 if it isn't limited).
     """
-    
+
     return self.height
-  
+
   def setHeight(self, height):
     """
     Changes the height used for subwindows. This uses all available space if -1.
-    
+
     Arguments:
       height - maximum height of panel (uses all available space if -1)
     """
-    
+
     if self.height != height:
       self.height = height
       self.win = None
-  
+
   def getWidth(self):
     """
     Provides the width used for subwindows (-1 if it isn't limited).
     """
-    
+
     return self.width
-  
+
   def setWidth(self, width):
     """
     Changes the width used for subwindows. This uses all available space if -1.
-    
+
     Arguments:
       width - maximum width of panel (uses all available space if -1)
     """
-    
+
     if self.width != width:
       self.width = width
       self.win = None
-  
+
   def getPreferredSize(self):
     """
     Provides the dimensions the subwindow would use when next redrawn, given
     that none of the properties of the panel or parent change before then. This
     returns a tuple of (height, width).
     """
-    
+
     newHeight, newWidth = self.parent.getmaxyx()
     setHeight, setWidth = self.getHeight(), self.getWidth()
     newHeight = max(0, newHeight - self.top)
@@ -328,75 +328,75 @@ class Panel():
     if setHeight != -1: newHeight = min(newHeight, setHeight)
     if setWidth != -1: newWidth = min(newWidth, setWidth)
     return (newHeight, newWidth)
-  
+
   def handleKey(self, key):
     """
     Handler for user input. This returns true if the key press was consumed,
     false otherwise.
-    
+
     Arguments:
       key - keycode for the key pressed
     """
-    
+
     return False
-  
+
   def getHelp(self):
     """
     Provides help information for the controls this page provides. This is a
     list of tuples of the form...
     (control, description, status)
     """
-    
+
     return []
-  
+
   def draw(self, width, height):
     """
-    Draws display's content. This is meant to be overwritten by 
+    Draws display's content. This is meant to be overwritten by
     implementations and not called directly (use redraw() instead). The
     dimensions provided are the drawable dimensions, which in terms of width is
     a column less than the actual space.
-    
+
     Arguments:
       width  - horizontal space available for content
       height - vertical space available for content
     """
-    
+
     pass
-  
+
   def redraw(self, forceRedraw=False, block=False):
     """
     Clears display and redraws its content. This can skip redrawing content if
     able (ie, the subwindow's unchanged), instead just refreshing the display.
-    
+
     Arguments:
       forceRedraw - forces the content to be cleared and redrawn if true
       block       - if drawing concurrently with other panels this determines
                     if the request is willing to wait its turn or should be
                     abandoned
     """
-    
+
     # skipped if not currently visible or activity has been halted
     if not self.isVisible() or HALT_ACTIVITY: return
-    
+
     # if the panel's completely outside its parent then this is a no-op
     newHeight, newWidth = self.getPreferredSize()
     if newHeight == 0 or newWidth == 0:
       self.win = None
       return
-    
+
     # recreates the subwindow if necessary
     isNewWindow = self._resetSubwindow()
-    
+
     # The reset argument is disregarded in a couple of situations:
     # - The subwindow's been recreated (obviously it then doesn't have the old
     #   content to refresh).
     # - The subwindow's dimensions have changed since last drawn (this will
     #   likely change the content's layout)
-    
+
     subwinMaxY, subwinMaxX = self.win.getmaxyx()
     if isNewWindow or subwinMaxY != self.maxY or subwinMaxX != self.maxX:
       forceRedraw = True
-    
+
     self.maxY, self.maxX = subwinMaxY, subwinMaxX
     if not CURSES_LOCK.acquire(block): return
     try:
@@ -406,19 +406,19 @@ class Panel():
       self.win.refresh()
     finally:
       CURSES_LOCK.release()
-  
+
   def hline(self, y, x, length, attr=curses.A_NORMAL):
     """
     Draws a horizontal line. This should only be called from the context of a
     panel's draw method.
-    
+
     Arguments:
       y      - vertical location
       x      - horizontal location
       length - length the line spans
       attr   - text attributes
     """
-    
+
     if self.win and self.maxX > x and self.maxY > y:
       try:
         drawLength = min(length, self.maxX - x)
@@ -426,19 +426,19 @@ class Panel():
       except:
         # in edge cases drawing could cause a _curses.error
         pass
-  
+
   def vline(self, y, x, length, attr=curses.A_NORMAL):
     """
     Draws a vertical line. This should only be called from the context of a
     panel's draw method.
-    
+
     Arguments:
       y      - vertical location
       x      - horizontal location
       length - length the line spans
       attr   - text attributes
     """
-    
+
     if self.win and self.maxX > x and self.maxY > y:
       try:
         drawLength = min(length, self.maxY - y)
@@ -446,40 +446,40 @@ class Panel():
       except:
         # in edge cases drawing could cause a _curses.error
         pass
-  
+
   def addch(self, y, x, char, attr=curses.A_NORMAL):
     """
     Draws a single character. This should only be called from the context of a
     panel's draw method.
-    
+
     Arguments:
       y    - vertical location
       x    - horizontal location
       char - character to be drawn
       attr - text attributes
     """
-    
+
     if self.win and self.maxX > x and self.maxY > y:
       try:
         self.win.addch(y, x, char, attr)
       except:
         # in edge cases drawing could cause a _curses.error
         pass
-  
+
   def addstr(self, y, x, msg, attr=curses.A_NORMAL):
     """
     Writes string to subwindow if able. This takes into account screen bounds
     to avoid making curses upset. This should only be called from the context
     of a panel's draw method.
-    
+
     Arguments:
       y    - vertical location
       x    - horizontal location
       msg  - text to be added
       attr - text attributes
     """
-    
-    # subwindows need a single character buffer (either in the x or y 
+
+    # subwindows need a single character buffer (either in the x or y
     # direction) from actual content to prevent crash when shrank
     if self.win and self.maxX > x and self.maxY > y:
       try:
@@ -488,7 +488,7 @@ class Panel():
         # this might produce a _curses.error during edge cases, for instance
         # when resizing with visible popups
         pass
-  
+
   def addfstr(self, y, x, msg):
     """
     Writes string to subwindow. The message can contain xhtml-style tags for
@@ -497,36 +497,36 @@ class Panel():
     <u>text</u>               underline
     <h>text</h>               highlight
     <[color]>text</[color]>   use color (see uiTools.getColor() for constants)
-    
+
     Tag nesting is supported and tag closing is strictly enforced (raising an
     exception for invalid formatting). Unrecognized tags are treated as normal
     text. This should only be called from the context of a panel's draw method.
-    
+
     Text in multiple color tags (for instance "<blue><red>hello</red></blue>")
     uses the bitwise OR of those flags (hint: that's probably not what you
     want).
-    
+
     Arguments:
       y    - vertical location
       x    - horizontal location
       msg  - formatted text to be added
     """
-    
+
     if self.win and self.maxY > y:
       formatting = [curses.A_NORMAL]
       expectedCloseTags = []
       unusedMsg = msg
-      
+
       while self.maxX > x and len(unusedMsg) > 0:
         # finds next consumeable tag (left as None if there aren't any left)
         nextTag, tagStart, tagEnd = None, -1, -1
-        
+
         tmpChecked = 0 # portion of the message cleared for having any valid tags
         expectedTags = FORMAT_TAGS.keys() + expectedCloseTags
         while nextTag == None:
           tagStart = unusedMsg.find("<", tmpChecked)
           tagEnd = unusedMsg.find(">", tagStart) + 1 if tagStart != -1 else -1
-          
+
           if tagStart == -1 or tagEnd == -1: break # no more tags to consume
           else:
             # check if the tag we've found matches anything being expected
@@ -536,7 +536,7 @@ class Panel():
             else:
               # not a valid tag - narrow search to everything after it
               tmpChecked = tagEnd
-        
+
         # splits into text before and after tag
         if nextTag:
           msgSegment = unusedMsg[:tagStart]
@@ -544,18 +544,18 @@ class Panel():
         else:
           msgSegment = unusedMsg
           unusedMsg = ""
-        
+
         # adds text before tag with current formatting
         attr = 0
         for format in formatting: attr |= format
         self.win.addstr(y, x, msgSegment[:self.maxX - x - 1], attr)
         x += len(msgSegment)
-        
+
         # applies tag attributes for future text
         if nextTag:
           formatTag = "<" + nextTag[2:] if nextTag.startswith("</") else nextTag
           formatMatch = FORMAT_TAGS[formatTag][0](FORMAT_TAGS[formatTag][1])
-          
+
           if not nextTag.startswith("</"):
             # open tag - add formatting
             expectedCloseTags.append("</" + nextTag[1:])
@@ -564,25 +564,25 @@ class Panel():
             # close tag - remove formatting
             expectedCloseTags.remove(nextTag)
             formatting.remove(formatMatch)
-      
+
       # only check for unclosed tags if we processed the whole message (if we
       # stopped processing prematurely it might still be valid)
       if expectedCloseTags and not unusedMsg:
         # if we're done then raise an exception for any unclosed tags (tisk, tisk)
         baseMsg = "Unclosed formatting tag%s:" % ("s" if len(expectedCloseTags) > 1 else "")
         raise ValueError("%s: '%s'\n  \"%s\"" % (baseMsg, "', '".join(expectedCloseTags), msg))
-  
+
   def getstr(self, y, x, initialText = "", format = None, maxWidth = None, validator = None):
     """
     Provides a text field where the user can input a string, blocking until
     they've done so and returning the result. If the user presses escape then
     this terminates and provides back None. This should only be called from
     the context of a panel's draw method.
-    
+
     This blanks any content within the space that the input field is rendered
     (otherwise stray characters would be interpreted as part of the initial
     input).
-    
+
     Arguments:
       y           - vertical location
       x           - horizontal location
@@ -591,60 +591,60 @@ class Panel():
       maxWidth    - maximum width for the text field
       validator   - custom TextInputValidator for handling keybindings
     """
-    
+
     if not format: format = curses.A_NORMAL
-    
+
     # makes cursor visible
     try: previousCursorState = curses.curs_set(1)
     except curses.error: previousCursorState = 0
-    
+
     # temporary subwindow for user input
     displayWidth = self.getPreferredSize()[1]
     if maxWidth: displayWidth = min(displayWidth, maxWidth + x)
     inputSubwindow = self.parent.subwin(1, displayWidth - x, self.top + y, self.left + x)
-    
+
     # blanks the field's area, filling it with the font in case it's hilighting
     inputSubwindow.clear()
     inputSubwindow.bkgd(' ', format)
-    
+
     # prepopulates the initial text
     if initialText:
       inputSubwindow.addstr(0, 0, initialText[:displayWidth - x - 1], format)
-    
+
     # Displays the text field, blocking until the user's done. This closes the
     # text panel and returns userInput to the initial text if the user presses
     # escape.
-    
+
     textbox = curses.textpad.Textbox(inputSubwindow)
-    
+
     if not validator:
       validator = textInput.BasicValidator()
-    
+
     textbox.win.attron(format)
     userInput = textbox.edit(lambda key: validator.validate(key, textbox)).strip()
     textbox.win.attroff(format)
     if textbox.lastcmd == curses.ascii.BEL: userInput = None
-    
+
     # reverts visability settings
     try: curses.curs_set(previousCursorState)
     except curses.error: pass
-    
+
     return userInput
-  
+
   def addScrollBar(self, top, bottom, size, drawTop = 0, drawBottom = -1, drawLeft = 0):
     """
     Draws a left justified scroll bar reflecting position within a vertical
     listing. This is shorted if necessary, and left undrawn if no space is
     available. The bottom is squared off, having a layout like:
-     | 
+     |
     *|
     *|
     *|
      |
     -+
-    
+
     This should only be called from the context of a panel's draw method.
-    
+
     Arguments:
       top        - list index for the top-most visible element
       bottom     - list index for the bottom-most visible element
@@ -654,38 +654,38 @@ class Panel():
                    span to the bottom of the panel
       drawLeft   - left offset at which to draw the scroll bar
     """
-    
+
     if (self.maxY - drawTop) < 2: return # not enough room
-    
+
     # sets drawBottom to be the actual row on which the scrollbar should end
     if drawBottom == -1: drawBottom = self.maxY - 1
     else: drawBottom = min(drawBottom, self.maxY - 1)
-    
+
     # determines scrollbar dimensions
     scrollbarHeight = drawBottom - drawTop
     sliderTop = scrollbarHeight * top / size
     sliderSize = scrollbarHeight * (bottom - top) / size
-    
+
     # ensures slider isn't at top or bottom unless really at those extreme bounds
     if top > 0: sliderTop = max(sliderTop, 1)
     if bottom != size: sliderTop = min(sliderTop, scrollbarHeight - sliderSize - 2)
-    
+
     # avoids a rounding error that causes the scrollbar to be too low when at
     # the bottom
     if bottom == size: sliderTop = scrollbarHeight - sliderSize - 1
-    
+
     # draws scrollbar slider
     for i in range(scrollbarHeight):
       if i >= sliderTop and i <= sliderTop + sliderSize:
         self.addstr(i + drawTop, drawLeft, " ", curses.A_STANDOUT)
       else:
         self.addstr(i + drawTop, drawLeft, " ")
-    
+
     # draws box around the scroll bar
     self.vline(drawTop, drawLeft + 1, drawBottom - 1)
     self.addch(drawBottom, drawLeft + 1, curses.ACS_LRCORNER)
     self.addch(drawBottom, drawLeft, curses.ACS_HLINE)
-  
+
   def _resetSubwindow(self):
     """
     Create a new subwindow instance for the panel if:
@@ -698,13 +698,13 @@ class Panel():
       subwindows are never restored to their proper position, resulting in
       graphical glitches if we draw to them.
     - The preferred size is smaller than the actual size (should shrink).
-    
+
     This returns True if a new subwindow instance was created, False otherwise.
     """
-    
+
     newHeight, newWidth = self.getPreferredSize()
     if newHeight == 0: return False # subwindow would be outside its parent
-    
+
     # determines if a new subwindow should be recreated
     recreate = self.win == None
     if self.win:
@@ -712,17 +712,17 @@ class Panel():
       recreate |= subwinMaxY < newHeight              # check for vertical growth
       recreate |= self.top > self.win.getparyx()[0]   # check for displacement
       recreate |= subwinMaxX > newWidth or subwinMaxY > newHeight # shrinking
-    
+
     # I'm not sure if recreating subwindows is some sort of memory leak but the
     # Python curses bindings seem to lack all of the following:
     # - subwindow deletion (to tell curses to free the memory)
     # - subwindow moving/resizing (to restore the displaced windows)
-    # so this is the only option (besides removing subwindows entirely which 
+    # so this is the only option (besides removing subwindows entirely which
     # would mean far more complicated code and no more selective refreshing)
-    
+
     if recreate:
       self.win = self.parent.subwin(newHeight, newWidth, self.top, self.left)
-      
+
       # note: doing this log before setting win produces an infinite loop
       log.debug("recreating panel '%s' with the dimensions of %i/%i" % (self.getName(), newHeight, newWidth))
     return recreate
diff --git a/arm/util/sysTools.py b/arm/util/sysTools.py
index 80e5c12..cd5814d 100644
--- a/arm/util/sysTools.py
+++ b/arm/util/sysTools.py
@@ -30,30 +30,30 @@ def getSysCpuUsage():
   unfortunately, doesn't seem to take popen calls into account. This returns a
   float representing the percentage used.
   """
-  
+
   currentTime = time.time()
-  
+
   # removes any runtimes outside of our sampling period
   while RUNTIMES and currentTime - RUNTIMES[0][0] > SAMPLING_PERIOD:
     RUNTIMES.pop(0)
-  
+
   runtimeSum = sum([entry[1] for entry in RUNTIMES])
   return runtimeSum / SAMPLING_PERIOD
 
 def getResourceTracker(pid, noSpawn = False):
   """
   Provides a running singleton ResourceTracker instance for the given pid.
-  
+
   Arguments:
     pid     - pid of the process being tracked
     noSpawn - returns None rather than generating a singleton instance if True
   """
-  
+
   if pid in RESOURCE_TRACKERS:
     tracker = RESOURCE_TRACKERS[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
@@ -65,93 +65,93 @@ class ResourceTracker(threading.Thread):
   Periodically fetches the resource usage (cpu and memory usage) for a given
   process.
   """
-  
+
   def __init__(self, processPid, resolveRate):
     """
     Initializes a new resolver daemon. When no longer needed it's suggested
     that this is stopped.
-    
+
     Arguments:
       processPid  - pid of the process being tracked
       resolveRate - time between resolving resource usage, resolution is
                     disabled if zero
     """
-    
+
     threading.Thread.__init__(self)
     self.setDaemon(True)
-    
+
     self.processPid = processPid
     self.resolveRate = resolveRate
-    
+
     self.cpuSampling = 0.0  # latest cpu usage sampling
     self.cpuAvg = 0.0       # total average cpu usage
     self.memUsage = 0       # last sampled memory usage in bytes
     self.memUsagePercentage = 0.0 # percentage cpu usage
-    
+
     # resolves usage via proc results if true, ps otherwise
     self._useProc = proc.is_available()
-    
+
     # used to get the deltas when querying cpu time
     self._lastCpuTotal = 0
-    
+
     self.lastLookup = -1
     self._halt = False      # terminates thread if true
     self._valLock = threading.RLock()
     self._cond = threading.Condition()  # used for pausing the thread
-    
+
     # number of successful calls we've made
     self._runCount = 0
-    
+
     # sequential times we've failed with this method of resolution
     self._failureCount = 0
-  
+
   def getResourceUsage(self):
     """
     Provides the last cached resource usage as a tuple of the form:
     (cpuUsage_sampling, cpuUsage_avg, memUsage_bytes, memUsage_percent)
     """
-    
+
     self._valLock.acquire()
     results = (self.cpuSampling, self.cpuAvg, self.memUsage, self.memUsagePercentage)
     self._valLock.release()
-    
+
     return results
-  
+
   def getRunCount(self):
     """
     Provides the number of times we've successfully fetched the resource
     usages.
     """
-    
+
     return self._runCount
-  
+
   def lastQueryFailed(self):
     """
     Provides true if, since we fetched the currently cached results, we've
     failed to get new results. False otherwise.
     """
-    
+
     return self._failureCount != 0
-  
+
   def run(self):
     while not self._halt:
       timeSinceReset = time.time() - self.lastLookup
-      
+
       if self.resolveRate == 0:
         self._cond.acquire()
         if not self._halt: self._cond.wait(0.2)
         self._cond.release()
-        
+
         continue
       elif timeSinceReset < self.resolveRate:
         sleepTime = max(0.2, self.resolveRate - timeSinceReset)
-        
+
         self._cond.acquire()
         if not self._halt: self._cond.wait(sleepTime)
         self._cond.release()
-        
+
         continue # done waiting, try again
-      
+
       newValues = {}
       try:
         if self._useProc:
@@ -161,28 +161,28 @@ class ResourceTracker(threading.Thread):
           newValues["cpuSampling"] = cpuDelta / timeSinceReset
           newValues["cpuAvg"] = totalCpuTime / (time.time() - float(startTime))
           newValues["_lastCpuTotal"] = totalCpuTime
-          
+
           memUsage = int(proc.get_memory_usage(self.processPid)[0])
           totalMemory = proc.get_physical_memory()
           newValues["memUsage"] = memUsage
           newValues["memUsagePercentage"] = float(memUsage) / totalMemory
         else:
           # the ps call formats results as:
-          # 
+          #
           #     TIME     ELAPSED   RSS %MEM
           # 3-08:06:32 21-00:00:12 121844 23.5
-          # 
+          #
           # or if Tor has only recently been started:
-          # 
+          #
           #     TIME      ELAPSED    RSS %MEM
           #  0:04.40        37:57  18772  0.9
-          
+
           psCall = system.call("ps -p %s -o cputime,etime,rss,%%mem" % self.processPid)
-          
+
           isSuccessful = False
           if psCall and len(psCall) >= 2:
             stats = psCall[1].strip().split()
-            
+
             if len(stats) == 4:
               try:
                 totalCpuTime = str_tools.parse_short_time_label(stats[0])
@@ -191,24 +191,24 @@ class ResourceTracker(threading.Thread):
                 newValues["cpuSampling"] = cpuDelta / timeSinceReset
                 newValues["cpuAvg"] = totalCpuTime / uptime
                 newValues["_lastCpuTotal"] = totalCpuTime
-                
+
                 newValues["memUsage"] = int(stats[2]) * 1024 # ps size is in kb
                 newValues["memUsagePercentage"] = float(stats[3]) / 100.0
                 isSuccessful = True
               except ValueError, exc: pass
-          
+
           if not isSuccessful:
             raise IOError("unrecognized output from ps: %s" % psCall)
       except IOError, exc:
         newValues = {}
         self._failureCount += 1
-        
+
         if self._useProc:
           if self._failureCount >= 3:
             # We've failed three times resolving via proc. Warn, and fall back
             # to ps resolutions.
             log.info("Failed three attempts to get process resource usage from proc, falling back to ps (%s)" % exc)
-            
+
             self._useProc = False
             self._failureCount = 1 # prevents lastQueryFailed() from thinking that we succeeded
           else:
@@ -224,7 +224,7 @@ class ResourceTracker(threading.Thread):
           self._cond.acquire()
           if not self._halt: self._cond.wait(sleepTime)
           self._cond.release()
-      
+
       # sets the new values
       if newValues:
         # If this is the first run then the cpuSampling stat is meaningless
@@ -232,7 +232,7 @@ class ResourceTracker(threading.Thread):
         # point). Setting it to the average, which is a fairer estimate.
         if self.lastLookup == -1:
           newValues["cpuSampling"] = newValues["cpuAvg"]
-        
+
         self._valLock.acquire()
         self.cpuSampling = newValues["cpuSampling"]
         self.cpuAvg = newValues["cpuAvg"]
@@ -243,12 +243,12 @@ class ResourceTracker(threading.Thread):
         self._runCount += 1
         self._failureCount = 0
         self._valLock.release()
-  
+
   def stop(self):
     """
     Halts further resolutions and terminates the thread.
     """
-    
+
     self._cond.acquire()
     self._halt = True
     self._cond.notifyAll()
diff --git a/arm/util/textInput.py b/arm/util/textInput.py
index c0f01ce..34a66a0 100644
--- a/arm/util/textInput.py
+++ b/arm/util/textInput.py
@@ -14,40 +14,40 @@ class TextInputValidator:
   Basic interface for validators. Implementations should override the handleKey
   method.
   """
-  
+
   def __init__(self, nextValidator = None):
     self.nextValidator = nextValidator
-  
+
   def validate(self, key, textbox):
     """
     Processes the given key input for the textbox. This may modify the
     textbox's content, cursor position, etc depending on the functionality
     of the validator. This returns the key that the textbox should interpret,
     PASS if this validator doesn't want to take any action.
-    
+
     Arguments:
       key     - key code input from the user
       textbox - curses Textbox instance the input came from
     """
-    
+
     result = self.handleKey(key, textbox)
-    
+
     if result != PASS:
       return result
     elif self.nextValidator:
       return self.nextValidator.validate(key, textbox)
     else: return key
-  
+
   def handleKey(self, key, textbox):
     """
     Process the given keycode with this validator, returning the keycode for
     the textbox to process, and PASS if this doesn't want to modify it.
-    
+
     Arguments:
       key     - key code input from the user
       textbox - curses Textbox instance the input came from
     """
-    
+
     return PASS
 
 class BasicValidator(TextInputValidator):
@@ -58,10 +58,10 @@ class BasicValidator(TextInputValidator):
     arrow
   - home and end keys move to the start/end of the line
   """
-  
+
   def handleKey(self, key, textbox):
     y, x = textbox.win.getyx()
-    
+
     if curses.ascii.isprint(key) and x < textbox.maxx:
       # Shifts the existing text forward so input is an insert method rather
       # than replacement. The curses.textpad accepts an insert mode flag but
@@ -72,7 +72,7 @@ class BasicValidator(TextInputValidator):
       # - The textpad doesn't shift text that has text attributes. This is
       #   because keycodes read by textbox.win.inch() includes formatting,
       #   causing the curses.ascii.isprint() check it does to fail.
-      
+
       currentInput = textbox.gather()
       textbox.win.addstr(y, x + 1, currentInput[x:textbox.maxx - 1])
       textbox.win.move(y, x) # reverts cursor movement during gather call
@@ -85,7 +85,7 @@ class BasicValidator(TextInputValidator):
     elif key in (curses.KEY_END, curses.KEY_RIGHT):
       msgLen = len(textbox.gather())
       textbox.win.move(y, x) # reverts cursor movement during gather call
-      
+
       if key == curses.KEY_END and msgLen > 0 and x < msgLen - 1:
         # if we're in the content then move to the end
         textbox.win.move(y, msgLen - 1)
@@ -97,7 +97,7 @@ class BasicValidator(TextInputValidator):
       # if we're resizing the display during text entry then cancel it
       # (otherwise the input field is filled with nonprintable characters)
       return curses.ascii.BEL
-    
+
     return PASS
 
 class HistoryValidator(TextInputValidator):
@@ -105,48 +105,48 @@ class HistoryValidator(TextInputValidator):
   This intercepts the up and down arrow keys to scroll through a backlog of
   previous commands.
   """
-  
+
   def __init__(self, commandBacklog = [], nextValidator = None):
     TextInputValidator.__init__(self, nextValidator)
-    
+
     # contents that can be scrolled back through, newest to oldest
     self.commandBacklog = commandBacklog
-    
+
     # selected item from the backlog, -1 if we're not on a backlog item
     self.selectionIndex = -1
-    
+
     # the fields input prior to selecting a backlog item
     self.customInput = ""
-  
+
   def handleKey(self, key, textbox):
     if key in (curses.KEY_UP, curses.KEY_DOWN):
       offset = 1 if key == curses.KEY_UP else -1
       newSelection = self.selectionIndex + offset
-      
+
       # constrains the new selection to valid bounds
       newSelection = max(-1, newSelection)
       newSelection = min(len(self.commandBacklog) - 1, newSelection)
-      
+
       # skips if this is a no-op
       if self.selectionIndex == newSelection:
         return None
-      
+
       # saves the previous input if we weren't on the backlog
       if self.selectionIndex == -1:
         self.customInput = textbox.gather().strip()
-      
+
       if newSelection == -1: newInput = self.customInput
       else: newInput = self.commandBacklog[newSelection]
-      
+
       y, _ = textbox.win.getyx()
       _, maxX = textbox.win.getmaxyx()
       textbox.win.clear()
       textbox.win.addstr(y, 0, newInput[:maxX - 1])
       textbox.win.move(y, min(len(newInput), maxX - 1))
-      
+
       self.selectionIndex = newSelection
       return None
-    
+
     return PASS
 
 class TabCompleter(TextInputValidator):
@@ -155,13 +155,13 @@ class TabCompleter(TextInputValidator):
   a single match. This expects a functor that accepts the current input and
   provides matches.
   """
-  
+
   def __init__(self, completer, nextValidator = None):
     TextInputValidator.__init__(self, nextValidator)
-    
+
     # functor that accepts a string and gives a list of matches
     self.completer = completer
-  
+
   def handleKey(self, key, textbox):
     # Matches against the tab key. The ord('\t') is nine, though strangely none
     # of the curses.KEY_*TAB constants match this...
@@ -169,27 +169,27 @@ class TabCompleter(TextInputValidator):
       currentContents = textbox.gather().strip()
       matches = self.completer(currentContents)
       newInput = None
-      
+
       if len(matches) == 1:
         # only a single match, fill it in
         newInput = matches[0]
       elif len(matches) > 1:
         # looks for a common prefix we can complete
         commonPrefix = os.path.commonprefix(matches) # weird that this comes from path...
-        
+
         if commonPrefix != currentContents:
           newInput = commonPrefix
-        
+
         # TODO: somehow display matches... this is not gonna be fun
-      
+
       if newInput:
         y, _ = textbox.win.getyx()
         _, maxX = textbox.win.getmaxyx()
         textbox.win.clear()
         textbox.win.addstr(y, 0, newInput[:maxX - 1])
         textbox.win.move(y, min(len(newInput), maxX - 1))
-      
+
       return None
-    
+
     return PASS
 
diff --git a/arm/util/torConfig.py b/arm/util/torConfig.py
index 978f44c..f6e693c 100644
--- a/arm/util/torConfig.py
+++ b/arm/util/torConfig.py
@@ -52,7 +52,7 @@ CONFIG = conf.config_dict("arm", {
 
 def general_conf_handler(config, key):
   value = config.get(key)
-  
+
   if key.startswith("config.summary."):
     # we'll look for summary keys with a lowercase config name
     CONFIG[key.lower()] = value
@@ -94,7 +94,7 @@ class ManPageEntry:
   """
   Information provided about a tor configuration option in its man page entry.
   """
-  
+
   def __init__(self, option, index, category, argUsage, description):
     self.option = option
     self.index = index
@@ -107,7 +107,7 @@ def getTorrc():
   Singleton constructor for a Controller. Be aware that this starts as being
   unloaded, needing the torrc contents to be loaded before being functional.
   """
-  
+
   global TORRC
   if TORRC == None: TORRC = Torrc()
   return TORRC
@@ -118,21 +118,21 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
   page. This can be a somewhat lengthy call, and raises an IOError if issues
   occure. When successful loading from a file this returns the version for the
   contents loaded.
-  
+
   If available, this can load the configuration descriptions from a file where
   they were previously persisted to cut down on the load time (latency for this
   is around 200ms).
-  
+
   Arguments:
     loadPath     - if set, this attempts to fetch the configuration
                    descriptions from the given path instead of the man page
     checkVersion - discards the results if true and tor's version doens't
                    match the cached descriptors, otherwise accepts anyway
   """
-  
+
   CONFIG_DESCRIPTIONS_LOCK.acquire()
   CONFIG_DESCRIPTIONS.clear()
-  
+
   raisedExc = None
   loadedVersion = ""
   try:
@@ -145,58 +145,58 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
       inputFile = open(loadPath, "r")
       inputFileContents = inputFile.readlines()
       inputFile.close()
-      
+
       try:
         versionLine = inputFileContents.pop(0).rstrip()
-        
+
         if versionLine.startswith("Tor Version "):
           fileVersion = versionLine[12:]
           loadedVersion = fileVersion
           torVersion = torTools.getConn().getInfo("version", "")
-          
+
           if checkVersion and fileVersion != torVersion:
             msg = "wrong version, tor is %s but the file's from %s" % (torVersion, fileVersion)
             raise IOError(msg)
         else:
           raise IOError("unable to parse version")
-        
+
         while inputFileContents:
           # gets category enum, failing if it doesn't exist
           category = inputFileContents.pop(0).rstrip()
           if not category in Category:
             baseMsg = "invalid category in input file: '%s'"
             raise IOError(baseMsg % category)
-          
+
           # gets the position in the man page
           indexArg, indexStr = -1, inputFileContents.pop(0).rstrip()
-          
+
           if indexStr.startswith("index: "):
             indexStr = indexStr[7:]
-            
+
             if indexStr.isdigit(): indexArg = int(indexStr)
             else: raise IOError("non-numeric index value: %s" % indexStr)
           else: raise IOError("malformed index argument: %s"% indexStr)
-          
+
           option = inputFileContents.pop(0).rstrip()
           argument = inputFileContents.pop(0).rstrip()
-          
+
           description, loadedLine = "", inputFileContents.pop(0)
           while loadedLine != PERSIST_ENTRY_DIVIDER:
             description += loadedLine
-            
+
             if inputFileContents: loadedLine = inputFileContents.pop(0)
             else: break
-          
+
           CONFIG_DESCRIPTIONS[option.lower()] = ManPageEntry(option, indexArg, category, argument, description.rstrip())
       except IndexError:
         CONFIG_DESCRIPTIONS.clear()
         raise IOError("input file format is invalid")
     else:
       manCallResults = system.call("man tor", None)
-      
+
       if not manCallResults:
         raise IOError("man page not found")
-      
+
       # Fetches all options available with this tor instance. This isn't
       # vital, and the validOptions are left empty if the call fails.
       conn, validOptions = torTools.getConn(), []
@@ -204,21 +204,21 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
       if configOptionQuery:
         for line in configOptionQuery.strip().split("\n"):
           validOptions.append(line[:line.find(" ")].lower())
-      
+
       optionCount, lastOption, lastArg = 0, None, None
       lastCategory, lastDescription = Category.GENERAL, ""
       for line in manCallResults:
         line = uiTools.getPrintable(line)
         strippedLine = line.strip()
-        
+
         # we have content, but an indent less than an option (ignore line)
         #if strippedLine and not line.startswith(" " * MAN_OPT_INDENT): continue
-        
+
         # line starts with an indent equivilant to a new config option
         isOptIndent = line.startswith(" " * MAN_OPT_INDENT) and line[MAN_OPT_INDENT] != " "
-        
+
         isCategoryLine = not line.startswith(" ") and "OPTIONS" in line
-        
+
         # if this is a category header or a new option, add an entry using the
         # buffered results
         if isOptIndent or isCategoryLine:
@@ -231,13 +231,13 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
             CONFIG_DESCRIPTIONS[lastOption.lower()] = ManPageEntry(lastOption, optionCount, lastCategory, lastArg, strippedDescription)
             optionCount += 1
           lastDescription = ""
-          
+
           # parses the option and argument
           line = line.strip()
           divIndex = line.find(" ")
           if divIndex != -1:
             lastOption, lastArg = line[:divIndex], line[divIndex + 1:]
-          
+
           # if this is a category header then switch it
           if isCategoryLine:
             if line.startswith("OPTIONS"): lastCategory = Category.GENERAL
@@ -255,7 +255,7 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
           # instance the ExitPolicy and TestingTorNetwork entries.
           if lastDescription and lastDescription[-1] != "\n":
             lastDescription += " "
-          
+
           if not strippedLine:
             lastDescription += "\n\n"
           elif line.startswith(" " * MAN_EX_INDENT):
@@ -263,7 +263,7 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
           else: lastDescription += strippedLine
   except IOError, exc:
     raisedExc = exc
-  
+
   CONFIG_DESCRIPTIONS_LOCK.release()
   if raisedExc: raise raisedExc
   else: return loadedVersion
@@ -272,27 +272,27 @@ def saveOptionDescriptions(path):
   """
   Preserves the current configuration descriptors to the given path. This
   raises an IOError or OSError if unable to do so.
-  
+
   Arguments:
     path - location to persist configuration descriptors
   """
-  
+
   # make dir if the path doesn't already exist
   baseDir = os.path.dirname(path)
   if not os.path.exists(baseDir): os.makedirs(baseDir)
   outputFile = open(path, "w")
-  
+
   CONFIG_DESCRIPTIONS_LOCK.acquire()
   sortedOptions = CONFIG_DESCRIPTIONS.keys()
   sortedOptions.sort()
-  
+
   torVersion = torTools.getConn().getInfo("version", "")
   outputFile.write("Tor Version %s\n" % torVersion)
   for i in range(len(sortedOptions)):
     manEntry = getConfigDescription(sortedOptions[i])
     outputFile.write("%s\nindex: %i\n%s\n%s\n%s\n" % (manEntry.category, manEntry.index, manEntry.option, manEntry.argUsage, manEntry.description))
     if i != len(sortedOptions) - 1: outputFile.write(PERSIST_ENTRY_DIVIDER)
-  
+
   outputFile.close()
   CONFIG_DESCRIPTIONS_LOCK.release()
 
@@ -300,22 +300,22 @@ def getConfigSummary(option):
   """
   Provides a short summary description of the configuration option. If none is
   known then this proivdes None.
-  
+
   Arguments:
     option - tor config option
   """
-  
+
   return CONFIG.get("config.summary.%s" % option.lower())
 
 def isImportant(option):
   """
   Provides True if the option has the 'important' flag in the configuration,
   False otherwise.
-  
+
   Arguments:
     option - tor config option
   """
-  
+
   return option.lower() in CONFIG["config.important"]
 
 def getConfigDescription(option):
@@ -324,17 +324,17 @@ def getConfigDescription(option):
   tor man page. This provides None if no such option has been loaded. If the
   man page is in the process of being loaded then this call blocks until it
   finishes.
-  
+
   Arguments:
     option - tor config option
   """
-  
+
   CONFIG_DESCRIPTIONS_LOCK.acquire()
-  
+
   if option.lower() in CONFIG_DESCRIPTIONS:
     returnVal = CONFIG_DESCRIPTIONS[option.lower()]
   else: returnVal = None
-  
+
   CONFIG_DESCRIPTIONS_LOCK.release()
   return returnVal
 
@@ -343,11 +343,11 @@ def getConfigOptions():
   Provides the configuration options from the loaded man page. This is an empty
   list if no man page has been loaded.
   """
-  
+
   CONFIG_DESCRIPTIONS_LOCK.acquire()
-  
+
   returnVal = [CONFIG_DESCRIPTIONS[opt].option for opt in CONFIG_DESCRIPTIONS]
-  
+
   CONFIG_DESCRIPTIONS_LOCK.release()
   return returnVal
 
@@ -356,12 +356,12 @@ def getConfigLocation():
   Provides the location of the torrc, raising an IOError with the reason if the
   path can't be determined.
   """
-  
+
   conn = torTools.getConn()
   configLocation = conn.getInfo("config-file", None)
   torPid, torPrefix = conn.controller.get_pid(None), torTools.get_chroot()
   if not configLocation: raise IOError("unable to query the torrc location")
-  
+
   try:
     torCwd = system.get_cwd(torPid)
     return torPrefix + system.expand_path(configLocation, torCwd)
@@ -373,13 +373,13 @@ def getMultilineParameters():
   Provides parameters that can be defined multiple times in the torrc without
   overwriting the value.
   """
-  
+
   # fetches config options with the LINELIST (aka 'LineList'), LINELIST_S (aka
   # 'Dependent'), and LINELIST_V (aka 'Virtual') types
   global MULTILINE_PARAM
   if MULTILINE_PARAM == None:
     conn, multilineEntries = torTools.getConn(), []
-    
+
     configOptionQuery = conn.getInfo("config/names", None)
     if configOptionQuery:
       for line in configOptionQuery.strip().split("\n"):
@@ -389,26 +389,26 @@ def getMultilineParameters():
     else:
       # unable to query tor connection, so not caching results
       return ()
-    
+
     MULTILINE_PARAM = multilineEntries
-  
+
   return tuple(MULTILINE_PARAM)
 
 def getCustomOptions(includeValue = False):
   """
   Provides the torrc parameters that differ from their defaults.
-  
+
   Arguments:
     includeValue - provides the current value with results if true, otherwise
                    this just contains the options
   """
-  
+
   configText = torTools.getConn().getInfo("config-text", "").strip()
   configLines = configText.split("\n")
-  
+
   # removes any duplicates
   configLines = list(set(configLines))
-  
+
   # The "GETINFO config-text" query only provides options that differ
   # from Tor's defaults with the exception of its Log and Nickname entries
   # which, even if undefined, returns "Log notice stdout" as per:
@@ -417,16 +417,16 @@ def getCustomOptions(includeValue = False):
   # If this is from the deb then it will be "Log notice file /var/log/tor/log"
   # due to special patching applied to it, as per:
   # https://trac.torproject.org/projects/tor/ticket/4602
-  
+
   try: configLines.remove("Log notice stdout")
   except ValueError: pass
-  
+
   try: configLines.remove("Log notice file /var/log/tor/log")
   except ValueError: pass
-  
+
   try: configLines.remove("Nickname %s" % socket.gethostname())
   except ValueError: pass
-  
+
   if includeValue: return configLines
   else: return [line[:line.find(" ")] for line in configLines]
 
@@ -436,77 +436,77 @@ def saveConf(destination = None, contents = None):
   issuing a SAVECONF (the contents and destination match what tor's using)
   then that's done. Otherwise, this writes the contents directly. This raises
   an IOError if unsuccessful.
-  
+
   Arguments:
     destination - path to be saved to, the current config location if None
     contents    - configuration to be saved, the current config if None
   """
-  
+
   if destination:
     destination = os.path.abspath(destination)
-  
+
   # fills default config values, and sets isSaveconf to false if they differ
   # from the arguments
   isSaveconf, startTime = True, time.time()
-  
+
   currentConfig = getCustomOptions(True)
   if not contents: contents = currentConfig
   else: isSaveconf &= contents == currentConfig
-  
+
   # The "GETINFO config-text" option was introduced in Tor version 0.2.2.7. If
   # we're writing custom contents then this is fine, but if we're trying to
   # save the current configuration then we need to fail if it's unavailable.
   # Otherwise we'd write a blank torrc as per...
   # https://trac.torproject.org/projects/tor/ticket/3614
-  
+
   if contents == ['']:
     # double check that "GETINFO config-text" is unavailable rather than just
     # giving an empty result
-    
+
     if torTools.getConn().getInfo("config-text", None) == None:
       raise IOError("determining the torrc requires Tor version 0.2.2.7")
-  
+
   currentLocation = None
   try:
     currentLocation = getConfigLocation()
     if not destination: destination = currentLocation
     else: isSaveconf &= destination == currentLocation
   except IOError: pass
-  
+
   if not destination: raise IOError("unable to determine the torrc's path")
   logMsg = "Saved config by %%s to %s (runtime: %%0.4f)" % destination
-  
+
   # attempts SAVECONF if we're updating our torrc with the current state
   if isSaveconf:
     try:
       torTools.getConn().saveConf()
-      
+
       try: getTorrc().load()
       except IOError: pass
-      
+
       log.debug(logMsg % ("SAVECONF", time.time() - startTime))
       return # if successful then we're done
     except:
       pass
-  
+
   # if the SAVECONF fails or this is a custom save then write contents directly
   try:
     # make dir if the path doesn't already exist
     baseDir = os.path.dirname(destination)
     if not os.path.exists(baseDir): os.makedirs(baseDir)
-    
+
     # saves the configuration to the file
     configFile = open(destination, "w")
     configFile.write("\n".join(contents))
     configFile.close()
   except (IOError, OSError), exc:
     raise IOError(exc)
-  
+
   # reloads the cached torrc if overwriting it
   if destination == currentLocation:
     try: getTorrc().load()
     except IOError: pass
-  
+
   log.debug(logMsg % ("directly writing", time.time() - startTime))
 
 def validate(contents = None):
@@ -514,15 +514,15 @@ def validate(contents = None):
   Performs validation on the given torrc contents, providing back a listing of
   (line number, issue, msg) tuples for issues found. If the issue occures on a
   multiline torrc entry then the line number is for the last line of the entry.
-  
+
   Arguments:
     contents - torrc contents
   """
-  
+
   conn = torTools.getConn()
   customOptions = getCustomOptions()
   issuesFound, seenOptions = [], []
-  
+
   # Strips comments and collapses multiline multi-line entries, for more
   # information see:
   # https://trac.torproject.org/projects/tor/ticket/1929
@@ -532,21 +532,21 @@ def validate(contents = None):
     else:
       line = multilineBuffer + line
       multilineBuffer = ""
-      
+
       if line.endswith("\\"):
         multilineBuffer = line[:-1]
         strippedContents.append("")
       else:
         strippedContents.append(line.strip())
-  
+
   for lineNumber in range(len(strippedContents) - 1, -1, -1):
     lineText = strippedContents[lineNumber]
     if not lineText: continue
-    
+
     lineComp = lineText.split(None, 1)
     if len(lineComp) == 2: option, value = lineComp
     else: option, value = lineText, ""
-    
+
     # Tor is case insensetive when parsing its torrc. This poses a bit of an
     # issue for us because we want all of our checks to be case insensetive
     # too but also want messages to match the normal camel-case conventions.
@@ -556,56 +556,56 @@ def validate(contents = None):
     # value check will fail. Hence using that hash to correct the case.
     #
     # TODO: when refactoring for stem make this less confusing...
-    
+
     for customOpt in customOptions:
       if customOpt.lower() == option.lower():
         option = customOpt
         break
-    
+
     # if an aliased option then use its real name
     if option in CONFIG["torrc.alias"]:
       option = CONFIG["torrc.alias"][option]
-    
+
     # most parameters are overwritten if defined multiple times
     if option in seenOptions and not option in getMultilineParameters():
       issuesFound.append((lineNumber, ValidationError.DUPLICATE, option))
       continue
     else: seenOptions.append(option)
-    
+
     # checks if the value isn't necessary due to matching the defaults
     if not option in customOptions:
       issuesFound.append((lineNumber, ValidationError.IS_DEFAULT, option))
-    
+
     # replace aliases with their recognized representation
     if option in CONFIG["torrc.alias"]:
       option = CONFIG["torrc.alias"][option]
-    
+
     # tor appears to replace tabs with a space, for instance:
     # "accept\t*:563" is read back as "accept *:563"
     value = value.replace("\t", " ")
-    
+
     # parse value if it's a size or time, expanding the units
     value, valueType = _parseConfValue(value)
-    
+
     # issues GETCONF to get the values tor's currently configured to use
     torValues = conn.getOption(option, [], True)
-    
+
     # multiline entries can be comma separated values (for both tor and conf)
     valueList = [value]
     if option in getMultilineParameters():
       valueList = [val.strip() for val in value.split(",")]
-      
+
       fetchedValues, torValues = torValues, []
       for fetchedValue in fetchedValues:
         for fetchedEntry in fetchedValue.split(","):
           fetchedEntry = fetchedEntry.strip()
           if not fetchedEntry in torValues:
             torValues.append(fetchedEntry)
-    
+
     for val in valueList:
       # checks if both the argument and tor's value are empty
       isBlankMatch = not val and not torValues
-      
+
       if not isBlankMatch and not val in torValues:
         # converts corrections to reader friedly size values
         displayValues = torValues
@@ -613,9 +613,9 @@ def validate(contents = None):
           displayValues = [str_tools.get_size_label(int(val)) for val in torValues]
         elif valueType == ValueType.TIME:
           displayValues = [str_tools.get_time_label(int(val)) for val in torValues]
-        
+
         issuesFound.append((lineNumber, ValidationError.MISMATCH, ", ".join(displayValues)))
-  
+
   # checks if any custom options are missing from the torrc
   for option in customOptions:
     # In new versions the 'DirReqStatistics' option is true by default and
@@ -623,12 +623,12 @@ def validate(contents = None):
     # missing then that's most likely the reason.
     #
     # https://trac.torproject.org/projects/tor/ticket/4237
-    
+
     if option == "DirReqStatistics": continue
-    
+
     if not option in seenOptions:
       issuesFound.append((None, ValidationError.MISSING, option))
-  
+
   return issuesFound
 
 def _parseConfValue(confArg):
@@ -636,48 +636,48 @@ def _parseConfValue(confArg):
   Converts size or time values to their lowest units (bytes or seconds) which
   is what GETCONF calls provide. The returned is a tuple of the value and unit
   type.
-  
+
   Arguments:
     confArg - torrc argument
   """
-  
+
   if confArg.count(" ") == 1:
     val, unit = confArg.lower().split(" ", 1)
     if not val.isdigit(): return confArg, ValueType.UNRECOGNIZED
     mult, multType = _getUnitType(unit)
-    
+
     if mult != None:
       return str(int(val) * mult), multType
-  
+
   return confArg, ValueType.UNRECOGNIZED
 
 def _getUnitType(unit):
   """
   Provides the type and multiplier for an argument's unit. The multiplier is
   None if the unit isn't recognized.
-  
+
   Arguments:
     unit - string representation of a unit
   """
-  
+
   for label in SIZE_MULT:
     if unit in CONFIG["torrc.label.size." + label]:
       return SIZE_MULT[label], ValueType.SIZE
-  
+
   for label in TIME_MULT:
     if unit in CONFIG["torrc.label.time." + label]:
       return TIME_MULT[label], ValueType.TIME
-  
+
   return None, ValueType.UNRECOGNIZED
 
 def _stripComments(contents):
   """
   Removes comments and extra whitespace from the given torrc contents.
-  
+
   Arguments:
     contents - torrc contents
   """
-  
+
   strippedContents = []
   for line in contents:
     if line and "#" in line: line = line[:line.find("#")]
@@ -688,38 +688,38 @@ class Torrc():
   """
   Wrapper for the torrc. All getters provide None if the contents are unloaded.
   """
-  
+
   def __init__(self):
     self.contents = None
     self.configLocation = None
     self.valsLock = threading.RLock()
-    
+
     # cached results for the current contents
     self.displayableContents = None
     self.strippedContents = None
     self.corrections = None
-    
+
     # flag to indicate if we've given a load failure warning before
     self.isLoadFailWarned = False
-  
+
   def load(self, logFailure = False):
     """
     Loads or reloads the torrc contents, raising an IOError if there's a
     problem.
-    
+
     Arguments:
       logFailure - if the torrc fails to load and we've never provided a
                    warning for this before then logs a warning
     """
-    
+
     self.valsLock.acquire()
-    
+
     # clears contents and caches
     self.contents, self.configLocation = None, None
     self.displayableContents = None
     self.strippedContents = None
     self.corrections = None
-    
+
     try:
       self.configLocation = getConfigLocation()
       configFile = open(self.configLocation, "r")
@@ -729,37 +729,37 @@ class Torrc():
       if logFailure and not self.isLoadFailWarned:
         log.warn("Unable to load torrc (%s)" % exc.strerror)
         self.isLoadFailWarned = True
-      
+
       self.valsLock.release()
       raise exc
-    
+
     self.valsLock.release()
-  
+
   def isLoaded(self):
     """
     Provides true if there's loaded contents, false otherwise.
     """
-    
+
     return self.contents != None
-  
+
   def getConfigLocation(self):
     """
     Provides the location of the loaded configuration contents. This may be
     available, even if the torrc failed to be loaded.
     """
-    
+
     return self.configLocation
-  
+
   def getContents(self):
     """
     Provides the contents of the configuration file.
     """
-    
+
     self.valsLock.acquire()
     returnVal = list(self.contents) if self.contents else None
     self.valsLock.release()
     return returnVal
-  
+
   def getDisplayContents(self, strip = False):
     """
     Provides the contents of the configuration file, formatted in a rendering
@@ -768,80 +768,80 @@ class Torrc():
       layouts since it's counted as a single character, but occupies several
       cells.
     - Strips control and unprintable characters.
-    
+
     Arguments:
       strip - removes comments and extra whitespace if true
     """
-    
+
     self.valsLock.acquire()
-    
+
     if not self.isLoaded(): returnVal = None
     else:
       if self.displayableContents == None:
         # restricts contents to displayable characters
         self.displayableContents = []
-        
+
         for lineNum in range(len(self.contents)):
           lineText = self.contents[lineNum]
           lineText = lineText.replace("\t", "   ")
           lineText = uiTools.getPrintable(lineText)
           self.displayableContents.append(lineText)
-      
+
       if strip:
         if self.strippedContents == None:
           self.strippedContents = _stripComments(self.displayableContents)
-        
+
         returnVal = list(self.strippedContents)
       else: returnVal = list(self.displayableContents)
-    
+
     self.valsLock.release()
     return returnVal
-  
+
   def getCorrections(self):
     """
     Performs validation on the loaded contents and provides back the
     corrections. If validation is disabled then this won't provide any
     results.
     """
-    
+
     self.valsLock.acquire()
-    
+
     if not self.isLoaded(): returnVal = None
     else:
       torVersion = torTools.getConn().getVersion()
       skipValidation = not CONFIG["features.torrc.validate"]
       skipValidation |= (torVersion is None or not torVersion >= stem.version.Requirement.GETINFO_CONFIG_TEXT)
-      
+
       if skipValidation:
         log.info("Skipping torrc validation (requires tor 0.2.2.7-alpha)")
         returnVal = {}
       else:
         if self.corrections == None:
           self.corrections = validate(self.contents)
-        
+
         returnVal = list(self.corrections)
-    
+
     self.valsLock.release()
     return returnVal
-  
+
   def getLock(self):
     """
     Provides the lock governing concurrent access to the contents.
     """
-    
+
     return self.valsLock
-  
+
   def logValidationIssues(self):
     """
     Performs validation on the loaded contents, and logs warnings for issues
     that are found.
     """
-    
+
     corrections = self.getCorrections()
-    
+
     if corrections:
       duplicateOptions, defaultOptions, mismatchLines, missingOptions = [], [], [], []
-      
+
       for lineNum, issue, msg in corrections:
         if issue == ValidationError.DUPLICATE:
           duplicateOptions.append("%s (line %i)" % (msg, lineNum + 1))
@@ -849,51 +849,51 @@ class Torrc():
           defaultOptions.append("%s (line %i)" % (msg, lineNum + 1))
         elif issue == ValidationError.MISMATCH: mismatchLines.append(lineNum + 1)
         elif issue == ValidationError.MISSING: missingOptions.append(msg)
-      
+
       if duplicateOptions or defaultOptions:
         msg = "Unneeded torrc entries found. They've been highlighted in blue on the torrc page."
-        
+
         if duplicateOptions:
           if len(duplicateOptions) > 1:
             msg += "\n- entries ignored due to having duplicates: "
           else:
             msg += "\n- entry ignored due to having a duplicate: "
-          
+
           duplicateOptions.sort()
           msg += ", ".join(duplicateOptions)
-        
+
         if defaultOptions:
           if len(defaultOptions) > 1:
             msg += "\n- entries match their default values: "
           else:
             msg += "\n- entry matches its default value: "
-          
+
           defaultOptions.sort()
           msg += ", ".join(defaultOptions)
-        
+
         log.notice(msg)
-      
+
       if mismatchLines or missingOptions:
         msg = "The torrc differs from what tor's using. You can issue a sighup to reload the torrc values by pressing x."
-        
+
         if mismatchLines:
           if len(mismatchLines) > 1:
             msg += "\n- torrc values differ on lines: "
           else:
             msg += "\n- torrc value differs on line: "
-          
+
           mismatchLines.sort()
           msg += ", ".join([str(val + 1) for val in mismatchLines])
-          
+
         if missingOptions:
           if len(missingOptions) > 1:
             msg += "\n- configuration values are missing from the torrc: "
           else:
             msg += "\n- configuration value is missing from the torrc: "
-          
+
           missingOptions.sort()
           msg += ", ".join(missingOptions)
-        
+
         log.warn(msg)
 
 def _testConfigDescriptions():
@@ -901,17 +901,17 @@ def _testConfigDescriptions():
   Tester for the loadOptionDescriptions function, fetching the man page
   contents and dumping its parsed results.
   """
-  
+
   loadOptionDescriptions()
   sortedOptions = CONFIG_DESCRIPTIONS.keys()
   sortedOptions.sort()
-  
+
   for i in range(len(sortedOptions)):
     option = sortedOptions[i]
     argument, description = getConfigDescription(option)
     optLabel = "OPTION: \"%s\"" % option
     argLabel = "ARGUMENT: \"%s\"" % argument
-    
+
     print "     %-45s %s" % (optLabel, argLabel)
     print "\"%s\"" % description
     if i != len(sortedOptions) - 1: print "-" * 80
@@ -920,31 +920,31 @@ def isRootNeeded(torrcPath):
   """
   Returns True if the given torrc needs root permissions to be ran, False
   otherwise. This raises an IOError if the torrc can't be read.
-  
+
   Arguments:
     torrcPath - torrc to be checked
   """
-  
+
   try:
     torrcFile = open(torrcPath, "r")
     torrcLines = torrcFile.readlines()
     torrcFile.close()
-    
+
     for line in torrcLines:
       line = line.strip()
-      
+
       isPortOpt = False
       for opt in PORT_OPT:
         if line.startswith(opt):
           isPortOpt = True
           break
-      
+
       if isPortOpt and " " in line:
         arg = line.split(" ")[1]
-        
+
         if arg.isdigit() and int(arg) <= 1024 and int(arg) != 0:
           return True
-    
+
     return False
   except Exception, exc:
     raise IOError(exc)
@@ -959,26 +959,26 @@ def renderTorrc(template, options, commentIndent = 30):
     [IF <opt1> | <opt2>]  # logical or of the options
     [ELSE]          # if the prior conditional evaluated to false
     [END IF]        # ends the control block
-    
+
     [<option>]      # inputs the option value, omitting the line if it maps
                     # to a boolean or empty string
     [NEWLINE]       # empty line, otherwise templating white space is ignored
-  
+
   Arguments:
     template      - torrc template lines used to generate the results
     options       - mapping of keywords to their given values, with values
                     being booleans or strings (possibly multi-line)
     commentIndent - minimum column that comments align on
   """
-  
+
   results = []
   templateIter = iter(template)
   commentLineFormat = "%%-%is%%s" % commentIndent
-  
+
   try:
     while True:
       line = templateIter.next().strip()
-      
+
       if line.startswith("[IF ") and line.endswith("]"):
         # checks if any of the conditional options are true or a non-empty string
         evaluatesTrue = False
@@ -987,30 +987,30 @@ def renderTorrc(template, options, commentIndent = 30):
           if cond.startswith("NOT "):
             isInverse = True
             cond = cond[4:]
-          
+
           if isInverse != bool(options.get(cond.strip())):
             evaluatesTrue = True
             break
-        
+
         if evaluatesTrue:
           continue
         else:
           # skips lines until we come to an else or the end of the block
           depth = 0
-          
+
           while depth != -1:
             line = templateIter.next().strip()
-            
+
             if line.startswith("[IF ") and line.endswith("]"): depth += 1
             elif line == "[END IF]": depth -= 1
             elif depth == 0 and line == "[ELSE]": depth -= 1
       elif line == "[ELSE]":
         # an else block we aren't using - skip to the end of it
         depth = 0
-        
+
         while depth != -1:
           line = templateIter.next().strip()
-          
+
           if line.startswith("[IF "): depth += 1
           elif line == "[END IF]": depth -= 1
       elif line == "[NEWLINE]":
@@ -1027,12 +1027,12 @@ def renderTorrc(template, options, commentIndent = 30):
         # torrc option line
         option, arg, comment = "", "", ""
         parsedLine = line
-        
+
         if "#" in parsedLine:
           parsedLine, comment = parsedLine.split("#", 1)
           parsedLine = parsedLine.strip()
           comment = "# %s" % comment.strip()
-        
+
         # parses the argument from the option
         if " " in parsedLine.strip():
           option, arg = parsedLine.split(" ", 1)
@@ -1040,19 +1040,19 @@ def renderTorrc(template, options, commentIndent = 30):
         else:
           log.info("torrc template option lacks an argument: '%s'" % line)
           continue
-        
+
         # inputs dynamic arguments
         if arg.startswith("[") and arg.endswith("]"):
           arg = options.get(arg[1:-1])
-        
+
         # skips argument if it's false or an empty string
         if not arg: continue
-        
+
         torrcEntry = "%s %s" % (option, arg)
         if comment: results.append(commentLineFormat % (torrcEntry + " ", comment))
         else: results.append(torrcEntry)
   except StopIteration: pass
-  
+
   return "\n".join(results)
 
 def loadConfigurationDescriptions(pathPrefix):
@@ -1060,46 +1060,46 @@ def loadConfigurationDescriptions(pathPrefix):
   Attempts to load descriptions for tor's configuration options, fetching them
   from the man page and persisting them to a file to speed future startups.
   """
-  
+
   # It is important that this is loaded before entering the curses context,
   # otherwise the man call pegs the cpu for around a minute (I'm not sure
   # why... curses must mess the terminal in a way that's important to man).
-  
+
   if CONFIG["features.config.descriptions.enabled"]:
     isConfigDescriptionsLoaded = False
-    
+
     # determines the path where cached descriptions should be persisted (left
     # undefined if caching is disabled)
     descriptorPath = None
     if CONFIG["features.config.descriptions.persist"]:
       dataDir = CONFIG["startup.dataDirectory"]
       if not dataDir.endswith("/"): dataDir += "/"
-      
+
       descriptorPath = os.path.expanduser(dataDir + "cache/") + CONFIG_DESC_FILENAME
-    
+
     # attempts to load configuration descriptions cached in the data directory
     if descriptorPath:
       try:
         loadStartTime = time.time()
         loadOptionDescriptions(descriptorPath)
         isConfigDescriptionsLoaded = True
-        
+
         log.info(DESC_LOAD_SUCCESS_MSG % (descriptorPath, time.time() - loadStartTime))
       except IOError, exc:
         log.info(DESC_LOAD_FAILED_MSG % exc.strerror)
-    
+
     # fetches configuration options from the man page
     if not isConfigDescriptionsLoaded:
       try:
         loadStartTime = time.time()
         loadOptionDescriptions()
         isConfigDescriptionsLoaded = True
-        
+
         log.info(DESC_READ_MAN_SUCCESS_MSG % (time.time() - loadStartTime))
       except IOError, exc:
         log.notice(DESC_READ_MAN_FAILED_MSG % exc.strerror)
-      
-      # persists configuration descriptions 
+
+      # persists configuration descriptions
       if isConfigDescriptionsLoaded and descriptorPath:
         try:
           loadStartTime = time.time()
@@ -1109,7 +1109,7 @@ def loadConfigurationDescriptions(pathPrefix):
           log.notice(DESC_SAVE_FAILED_MSG % exc.strerror)
         except OSError, exc:
           log.notice(DESC_SAVE_FAILED_MSG % exc)
-    
+
     # finally fall back to the cached descriptors provided with arm (this is
     # often the case for tbb and manual builds)
     if not isConfigDescriptionsLoaded:
diff --git a/arm/util/torTools.py b/arm/util/torTools.py
index 63dc257..8c7631e 100644
--- a/arm/util/torTools.py
+++ b/arm/util/torTools.py
@@ -43,7 +43,7 @@ def getConn():
   Singleton constructor for a Controller. Be aware that this starts as being
   uninitialized, needing a stem Controller before it's fully functional.
   """
-  
+
   global CONTROLLER
   if CONTROLLER == None: CONTROLLER = Controller()
   return CONTROLLER
@@ -90,7 +90,7 @@ class Controller:
   TorCtl), listener functionality for tor's state, and the capability for
   controller connections to be restarted if closed.
   """
-  
+
   def __init__(self):
     self.controller = None
     self.connLock = threading.RLock()
@@ -101,30 +101,30 @@ class Controller:
     self._consensusLookupCache = {}     # lookup cache with network status entries
     self._descriptorLookupCache = {}    # lookup cache with relay descriptors
     self._lastNewnym = 0                # time we last sent a NEWNYM signal
-  
+
   def init(self, controller):
     """
     Uses the given stem instance for future operations, notifying listeners
     about the change.
-    
+
     Arguments:
       controller - stem based Controller instance
     """
-    
+
     # TODO: We should reuse our controller instance so event listeners will be
     # re-attached. This is a point of regression until we do... :(
-    
+
     if controller.is_alive() and controller != self.controller:
       self.connLock.acquire()
-      
+
       if self.controller: self.close() # shut down current connection
       self.controller = controller
       log.info("Stem connected to tor version %s" % self.controller.get_version())
-      
+
       self.controller.add_event_listener(self.ns_event, stem.control.EventType.NS)
       self.controller.add_event_listener(self.new_consensus_event, stem.control.EventType.NEWCONSENSUS)
       self.controller.add_event_listener(self.new_desc_event, stem.control.EventType.NEWDESC)
-      
+
       # reset caches for ip -> fingerprint lookups
       self._fingerprintMappings = None
       self._fingerprintLookupCache = {}
@@ -132,22 +132,22 @@ class Controller:
       self._addressLookupCache = {}
       self._consensusLookupCache = {}
       self._descriptorLookupCache = {}
-      
+
       # time that we sent our last newnym signal
       self._lastNewnym = 0
-      
+
       self.connLock.release()
-  
+
   def close(self):
     """
     Closes the current stem instance and notifies listeners.
     """
-    
+
     self.connLock.acquire()
     if self.controller:
       self.controller.close()
     self.connLock.release()
-  
+
   def getController(self):
     return self.controller
 
@@ -156,37 +156,37 @@ class Controller:
     Returns True if this has been initialized with a working stem instance,
     False otherwise.
     """
-    
+
     self.connLock.acquire()
-    
+
     result = False
     if self.controller:
       if self.controller.is_alive(): result = True
       else: self.close()
-    
+
     self.connLock.release()
     return result
-  
+
   def getInfo(self, param, default = UNDEFINED):
     """
     Queries the control port for the given GETINFO option, providing the
     default if the response is undefined or fails for any reason (error
     response, control port closed, initiated, etc).
-    
+
     Arguments:
       param   - GETINFO option to be queried
       default - result if the query fails
     """
-    
+
     self.connLock.acquire()
-    
+
     try:
       if not self.isAlive():
         if default != UNDEFINED:
           return default
         else:
           raise stem.SocketClosed()
-      
+
       if default != UNDEFINED:
         return self.controller.get_info(param, default)
       else:
@@ -196,30 +196,30 @@ class Controller:
       raise exc
     finally:
       self.connLock.release()
-  
+
   def getOption(self, param, default = UNDEFINED, multiple = False):
     """
     Queries the control port for the given configuration option, providing the
     default if the response is undefined or fails for any reason. If multiple
     values exist then this arbitrarily returns the first unless the multiple
     flag is set.
-    
+
     Arguments:
       param     - configuration option to be queried
       default   - result if the query fails
       multiple  - provides a list with all returned values if true, otherwise
                   this just provides the first result
     """
-    
+
     self.connLock.acquire()
-    
+
     try:
       if not self.isAlive():
         if default != UNDEFINED:
           return default
         else:
           raise stem.SocketClosed()
-      
+
       if default != UNDEFINED:
         return self.controller.get_conf(param, default, multiple)
       else:
@@ -229,129 +229,129 @@ class Controller:
       raise exc
     finally:
       self.connLock.release()
-  
+
   def setOption(self, param, value = None):
     """
     Issues a SETCONF to set the given option/value pair. An exeptions raised
     if it fails to be set. If no value is provided then this sets the option to
     0 or NULL.
-    
+
     Arguments:
       param - configuration option to be set
       value - value to set the parameter to (this can be either a string or a
               list of strings)
     """
-    
+
     self.connLock.acquire()
-    
+
     try:
       if not self.isAlive():
         raise stem.SocketClosed()
-      
+
       self.controller.set_conf(param, value)
     except stem.SocketClosed, exc:
       self.close()
       raise exc
     finally:
       self.connLock.release()
-  
+
   def saveConf(self):
     """
     Calls tor's SAVECONF method.
     """
-    
+
     self.connLock.acquire()
-    
+
     if self.isAlive():
       self.controller.save_conf()
-    
+
     self.connLock.release()
-  
+
   def sendNewnym(self):
     """
     Sends a newnym request to Tor. These are rate limited so if it occures
     more than once within a ten second window then the second is delayed.
     """
-    
+
     self.connLock.acquire()
-    
+
     if self.isAlive():
       self._lastNewnym = time.time()
       self.controller.signal(stem.Signal.NEWNYM)
-    
+
     self.connLock.release()
-  
+
   def isNewnymAvailable(self):
     """
     True if Tor will immediately respect a newnym request, false otherwise.
     """
-    
+
     if self.isAlive():
       return self.getNewnymWait() == 0
     else: return False
-  
+
   def getNewnymWait(self):
     """
     Provides the number of seconds until a newnym signal would be respected.
     """
-    
+
     # newnym signals can occure at the rate of one every ten seconds
     # TODO: this can't take other controllers into account :(
     return max(0, math.ceil(self._lastNewnym + 10 - time.time()))
-  
+
   def getCircuits(self, default = []):
     """
     This provides a list with tuples of the form:
     (circuitID, status, purpose, (fingerprint1, fingerprint2...))
-    
+
     Arguments:
       default - value provided back if unable to query the circuit-status
     """
-    
+
     # TODO: We're losing caching around this. We should check to see the call
     # volume of this and probably add it to stem.
-    
+
     results = []
-    
+
     for entry in self.controller.get_circuits():
       fingerprints = []
-      
+
       for fp, nickname in entry.path:
         if not fp:
           consensusEntry = self.controller.get_network_status(nickname, None)
-          
+
           if consensusEntry:
             fp = consensusEntry.fingerprint
-          
+
           # 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 not fp:
             log.warn("Unable to determine the fingerprint for a relay in our own circuit: %s" % nickname)
             fp = "0" * 40
-        
+
         fingerprints.append(fp)
-      
+
       results.append((int(entry.id), entry.status, entry.purpose, fingerprints))
-    
+
     if results:
       return results
     else:
       return default
-  
+
   def getHiddenServicePorts(self, default = []):
     """
     Provides the target ports hidden services are configured to use.
-    
+
     Arguments:
       default - value provided back if unable to query the hidden service ports
     """
-    
+
     result = []
     hs_options = self.controller.get_conf_map("HiddenServiceOptions", {})
-    
+
     for entry in hs_options.get("HiddenServicePort", []):
       # HiddenServicePort entries are of the form...
       #
@@ -359,12 +359,12 @@ class Controller:
       #
       # ... with the TARGET being an address, port, or address:port. If the
       # target port isn't defined then uses the VIRTPORT.
-      
+
       hs_port = None
-      
+
       if ' ' in entry:
         virtport, target = entry.split(' ', 1)
-        
+
         if ':' in target:
           hs_port = target.split(':', 1)[1]  # target is an address:port
         elif target.isdigit():
@@ -373,62 +373,62 @@ class Controller:
           hs_port = virtport  # target is an address
       else:
         hs_port = entry  # just has the virtual port
-      
+
       if hs_port.isdigit():
         result.append(hsPort)
-    
+
     if result:
       return result
     else:
       return default
-  
+
   def getMyBandwidthRate(self, default = None):
     """
     Provides the effective relaying bandwidth rate of this relay. Currently
     this doesn't account for SETCONF events.
-    
+
     Arguments:
       default - result if the query fails
     """
-    
+
     # effective relayed bandwidth is the minimum of BandwidthRate,
     # MaxAdvertisedBandwidth, and RelayBandwidthRate (if set)
     effectiveRate = int(self.getOption("BandwidthRate", None))
-    
+
     relayRate = self.getOption("RelayBandwidthRate", None)
     if relayRate and relayRate != "0":
       effectiveRate = min(effectiveRate, int(relayRate))
-    
+
     maxAdvertised = self.getOption("MaxAdvertisedBandwidth", None)
     if maxAdvertised: effectiveRate = min(effectiveRate, int(maxAdvertised))
-    
+
     if effectiveRate is not None:
       return effectiveRate
     else:
       return default
-  
+
   def getMyBandwidthBurst(self, default = None):
     """
     Provides the effective bandwidth burst rate of this relay. Currently this
     doesn't account for SETCONF events.
-    
+
     Arguments:
       default - result if the query fails
     """
-    
+
     # effective burst (same for BandwidthBurst and RelayBandwidthBurst)
     effectiveBurst = int(self.getOption("BandwidthBurst", None))
-    
+
     relayBurst = self.getOption("RelayBandwidthBurst", None)
-    
+
     if relayBurst and relayBurst != "0":
       effectiveBurst = min(effectiveBurst, int(relayBurst))
-    
+
     if effectiveBurst is not None:
       return effectiveBurst
     else:
       return default
-  
+
   def getMyBandwidthObserved(self, default = None):
     """
     Provides the relay's current observed bandwidth (the throughput determined
@@ -436,21 +436,21 @@ class Controller:
     heuristic used for path selection if the measured bandwidth is undefined.
     This is fetched from the descriptors and hence will get stale if
     descriptors aren't periodically updated.
-    
+
     Arguments:
       default - result if the query fails
     """
-    
+
     myFingerprint = self.getInfo("fingerprint", None)
-    
+
     if myFingerprint:
       myDescriptor = self.controller.get_server_descriptor(myFingerprint)
-      
+
       if myDescriptor:
         result = myDescriptor.observed_bandwidth
-    
+
     return default
-  
+
   def getMyBandwidthMeasured(self, default = None):
     """
     Provides the relay's current measured bandwidth (the throughput as noted by
@@ -459,51 +459,51 @@ class Controller:
     on the circumstances this can be from a variety of things (observed,
     measured, weighted measured, etc) as described by:
     https://trac.torproject.org/projects/tor/ticket/1566
-    
+
     Arguments:
       default - result if the query fails
     """
-    
+
     # TODO: Tor is documented as providing v2 router status entries but
     # actually looks to be v3. This needs to be sorted out between stem
     # and tor.
-    
+
     myFingerprint = self.getInfo("fingerprint", None)
-    
+
     if myFingerprint:
       myStatusEntry = self.controller.get_network_status(myFingerprint)
-      
+
       if myStatusEntry and hasattr(myStatusEntry, 'bandwidth'):
         return myStatusEntry.bandwidth
-    
+
     return default
-  
+
   def getMyFlags(self, default = None):
     """
     Provides the flags held by this relay.
-    
+
     Arguments:
       default - result if the query fails or this relay isn't a part of the consensus yet
     """
-    
+
     myFingerprint = self.getInfo("fingerprint", None)
-    
+
     if myFingerprint:
       myStatusEntry = self.controller.get_network_status(myFingerprint)
-      
+
       if myStatusEntry:
         return myStatusEntry.flags
 
     return default
-  
+
   def getVersion(self):
     """
     Provides the version of our tor instance, this is None if we don't have a
     connection.
     """
-    
+
     self.connLock.acquire()
-    
+
     try:
       return self.controller.get_version()
     except stem.SocketClosed, exc:
@@ -513,108 +513,108 @@ class Controller:
       return None
     finally:
       self.connLock.release()
-  
+
   def isGeoipUnavailable(self):
     """
     Provides true if we've concluded that our geoip database is unavailable,
     false otherwise.
     """
-    
+
     if self.isAlive():
       return self.controller.is_geoip_unavailable()
     else:
       return False
-  
+
   def getMyUser(self):
     """
     Provides the user this process is running under. If unavailable this
     provides None.
     """
-    
+
     return self.controller.get_user(None)
-  
+
   def getMyFileDescriptorUsage(self):
     """
     Provides the number of file descriptors currently being used by this
     process. This returns None if this can't be determined.
     """
-    
+
     # The file descriptor usage is the size of the '/proc/<pid>/fd' contents
     # http://linuxshellaccount.blogspot.com/2008/06/finding-number-of-open-file-descriptors.html
     # I'm not sure about other platforms (like BSD) so erroring out there.
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive() and proc.is_available():
       myPid = self.controller.get_pid(None)
-      
+
       if myPid:
         try: result = len(os.listdir("/proc/%s/fd" % myPid))
         except: pass
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getMyFileDescriptorLimit(self):
     """
     Provides the maximum number of file descriptors this process can have.
     Only the Tor process itself reliably knows this value, and the option for
     getting this was added in Tor 0.2.3.x-final. If that's unavailable then
     we can only estimate the file descriptor limit based on other factors.
-    
+
     The return result is a tuple of the form:
     (fileDescLimit, isEstimate)
     and if all methods fail then both values are None.
     """
-    
+
     # provides -1 if the query fails
     queriedLimit = self.getInfo("process/descriptor-limit", None)
-    
+
     if queriedLimit != None and queriedLimit != "-1":
       return (int(queriedLimit), False)
-    
+
     torUser = self.getMyUser()
-    
+
     # This is guessing the open file limit. Unfortunately there's no way
     # (other than "/usr/proc/bin/pfiles pid | grep rlimit" under Solaris)
     # to get the file descriptor limit for an arbitrary process.
-    
+
     if torUser == "debian-tor":
       # probably loaded via /etc/init.d/tor which changes descriptor limit
       return (8192, True)
     else:
       # uses ulimit to estimate (-H is for hard limit, which is what tor uses)
       ulimitResults = system.call("ulimit -Hn")
-      
+
       if ulimitResults:
         ulimit = ulimitResults[0].strip()
-        
+
         if ulimit.isdigit():
           return (int(ulimit), True)
 
     return (None, None)
-  
+
   def getStartTime(self):
     """
     Provides the unix time for when the tor process first started. If this
     can't be determined then this provides None.
     """
-    
+
     try:
       return system.get_start_time(self.controller.get_pid())
     except:
       return None
-  
+
   def isExitingAllowed(self, ipAddress, port):
     """
     Checks if the given destination can be exited to by this relay, returning
     True if so and False otherwise.
     """
-    
+
     self.connLock.acquire()
-    
+
     result = False
     if self.isAlive():
       # If we allow any exiting then this could be relayed DNS queries,
@@ -622,82 +622,82 @@ class Controller:
       # test when exiting isn't allowed, but nothing is relayed over them.
       # I'm registering these as non-exiting to avoid likely user confusion:
       # https://trac.torproject.org/projects/tor/ticket/965
-      
+
       our_policy = self.getExitPolicy()
-      
+
       if our_policy and our_policy.is_exiting_allowed() and port == "53": result = True
       else: result = our_policy and our_policy.can_exit_to(ipAddress, port)
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getExitPolicy(self):
     """
     Provides an ExitPolicy instance for the head of this relay's exit policy
     chain. If there's no active connection then this provides None.
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       try:
         result = self.controller.get_exit_policy(param)
       except:
         pass
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getConsensusEntry(self, relayFingerprint):
     """
     Provides the most recently available consensus information for the given
     relay. This is none if no such information exists.
-    
+
     Arguments:
       relayFingerprint - fingerprint of the relay
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       if not relayFingerprint in self._consensusLookupCache:
         nsEntry = self.getInfo("ns/id/%s" % relayFingerprint, None)
         self._consensusLookupCache[relayFingerprint] = nsEntry
-      
+
       result = self._consensusLookupCache[relayFingerprint]
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getDescriptorEntry(self, relayFingerprint):
     """
     Provides the most recently available descriptor information for the given
     relay. Unless FetchUselessDescriptors is set this may frequently be
     unavailable. If no such descriptor is available then this returns None.
-    
+
     Arguments:
       relayFingerprint - fingerprint of the relay
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       if not relayFingerprint in self._descriptorLookupCache:
         descEntry = self.getInfo("desc/id/%s" % relayFingerprint, None)
         self._descriptorLookupCache[relayFingerprint] = descEntry
-      
+
       result = self._descriptorLookupCache[relayFingerprint]
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getRelayFingerprint(self, relayAddress, relayPort = None, getAllMatches = False):
     """
     Provides the fingerprint associated with the given address. If there's
@@ -705,7 +705,7 @@ class Controller:
     None. This disambiguates the fingerprint if there's multiple relays on
     the same ip address by several methods, one of them being to pick relays
     we have a connection with.
-    
+
     Arguments:
       relayAddress  - address of relay to be returned
       relayPort     - orport of relay (to further narrow the results)
@@ -713,16 +713,16 @@ class Controller:
                       (port, fingerprint) tuples matching the given
                       address
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       if getAllMatches:
         # populates the ip -> fingerprint mappings if not yet available
         if self._fingerprintMappings == None:
           self._fingerprintMappings = self._getFingerprintMappings()
-        
+
         if relayAddress in self._fingerprintMappings:
           result = self._fingerprintMappings[relayAddress]
         else: result = []
@@ -731,24 +731,24 @@ class Controller:
         if not (relayAddress, relayPort) in self._fingerprintLookupCache:
           relayFingerprint = self._getRelayFingerprint(relayAddress, relayPort)
           self._fingerprintLookupCache[(relayAddress, relayPort)] = relayFingerprint
-        
+
         result = self._fingerprintLookupCache[(relayAddress, relayPort)]
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getRelayNickname(self, relayFingerprint):
     """
     Provides the nickname associated with the given relay. This provides None
     if no such relay exists, and "Unnamed" if the name hasn't been set.
-    
+
     Arguments:
       relayFingerprint - fingerprint of the relay
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       # query the nickname if it isn't yet cached
@@ -759,16 +759,16 @@ class Controller:
           self._nicknameLookupCache[relayFingerprint] = myNickname
         else:
           nsEntry = self.controller.get_network_status(relayFingerprint, None)
-          
+
           if nsEntry:
             self._nicknameLookupCache[relayFingerprint] = nsEntry.nickname
-      
+
       result = self._nicknameLookupCache[relayFingerprint]
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getRelayExitPolicy(self, relayFingerprint):
     """
     Provides the ExitPolicy instance associated with the given relay. The tor
@@ -776,36 +776,36 @@ class Controller:
     address-specific policies, so this is only used as a fallback if a recent
     descriptor is unavailable. This returns None if unable to determine the
     policy.
-    
+
     Arguments:
       relayFingerprint - fingerprint of the relay
     """
-    
+
     self.connLock.acquire()
-    
+
     result = None
     if self.isAlive():
       # attempts to fetch the policy via the descriptor
       descriptor = self.controller.get_server_descriptor(relayFingerprint, None)
-      
+
       if descriptor:
         result = descriptor.exit_policy
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def getRelayAddress(self, relayFingerprint, default = None):
     """
     Provides the (IP Address, ORPort) tuple for a given relay. If the lookup
     fails then this returns the default.
-    
+
     Arguments:
       relayFingerprint - fingerprint of the relay
     """
-    
+
     self.connLock.acquire()
-    
+
     result = default
     if self.isAlive():
       # query the address if it isn't yet cached
@@ -814,65 +814,65 @@ class Controller:
           # this is us, simply check the config
           myAddress = self.getInfo("address", None)
           myOrPort = self.getOption("ORPort", None)
-          
+
           if myAddress and myOrPort:
             self._addressLookupCache[relayFingerprint] = (myAddress, myOrPort)
         else:
           # check the consensus for the relay
           nsEntry = self.getConsensusEntry(relayFingerprint)
-          
+
           if nsEntry:
             nsLineComp = nsEntry.split("\n")[0].split(" ")
-            
+
             if len(nsLineComp) >= 8:
               self._addressLookupCache[relayFingerprint] = (nsLineComp[6], nsLineComp[7])
-      
+
       result = self._addressLookupCache.get(relayFingerprint, default)
-    
+
     self.connLock.release()
-    
+
     return result
-  
+
   def addEventListener(self, listener, *eventTypes):
     """
     Directs further tor controller events to callback functions of the
     listener. If a new control connection is initialized then this listener is
     reattached.
     """
-    
+
     self.connLock.acquire()
     if self.isAlive(): self.controller.add_event_listener(listener, *eventTypes)
     self.connLock.release()
-  
+
   def removeEventListener(self, listener):
     """
     Stops the given event listener from being notified of further events.
     """
-    
+
     self.connLock.acquire()
     if self.isAlive(): self.controller.remove_event_listener(listener)
     self.connLock.release()
-  
+
   def addStatusListener(self, callback):
     """
     Directs further events related to tor's controller status to the callback
     function.
-    
+
     Arguments:
       callback - functor that'll accept the events, expected to be of the form:
                  myFunction(controller, eventType)
     """
-    
+
     self.controller.add_status_listener(callback)
-  
+
   def reload(self):
     """
     This resets tor (sending a RELOAD signal to the control port) causing tor's
     internal state to be reset and the torrc reloaded.
     """
-    
+
     self.connLock.acquire()
-    
+
     try:
       if self.isAlive():
         try:
@@ -882,72 +882,72 @@ class Controller:
           raise IOError(str(exc))
     finally:
       self.connLock.release()
-  
+
   def shutdown(self, force = False):
     """
     Sends a shutdown signal to the attached tor instance. For relays the
     actual shutdown is delayed for thirty seconds unless the force flag is
     given. This raises an IOError if a signal is sent but fails.
-    
+
     Arguments:
       force - triggers an immediate shutdown for relays if True
     """
-    
+
     self.connLock.acquire()
-    
+
     raisedException = None
     if self.isAlive():
       try:
         isRelay = self.getOption("ORPort", None) != None
-        
+
         if force:
           self.controller.signal(stem.Signal.HALT)
         else:
           self.controller.signal(stem.Signal.SHUTDOWN)
-        
+
         # shuts down control connection if we aren't making a delayed shutdown
         if force or not isRelay: self.close()
       except Exception, exc:
         raisedException = IOError(str(exc))
-    
+
     self.connLock.release()
-    
+
     if raisedException: raise raisedException
-  
+
   def ns_event(self, event):
     self._consensusLookupCache = {}
-  
+
   def new_consensus_event(self, event):
     self.connLock.acquire()
-    
+
     # reconstructs consensus based mappings
     self._fingerprintLookupCache = {}
     self._nicknameLookupCache = {}
     self._addressLookupCache = {}
     self._consensusLookupCache = {}
-    
+
     if self._fingerprintMappings != None:
       self._fingerprintMappings = self._getFingerprintMappings(event.desc)
-    
+
     self.connLock.release()
-  
+
   def new_desc_event(self, event):
     self.connLock.acquire()
-    
+
     myFingerprint = self.getInfo("fingerprint", None)
     desc_fingerprints = [fingerprint for (fingerprint, nickname) in event.relays]
-    
+
     # If we're tracking ip address -> fingerprint mappings then update with
     # the new relays.
     self._fingerprintLookupCache = {}
     self._descriptorLookupCache = {}
-    
+
     if self._fingerprintMappings != None:
       for fingerprint in desc_fingerprints:
         # gets consensus data for the new descriptor
         try: desc = self.controller.get_network_status(fingerprint)
         except stem.ControllerError: continue
-        
+
         # updates fingerprintMappings with new data
         if desc.address in self._fingerprintMappings:
           # if entry already exists with the same orport, remove it
@@ -956,68 +956,68 @@ class Controller:
             if entryPort == desc.or_port:
               orportMatch = (entryPort, entryFingerprint)
               break
-          
+
           if orportMatch: self._fingerprintMappings[desc.address].remove(orportMatch)
-          
+
           # add the new entry
           self._fingerprintMappings[desc.address].append((desc.or_port, desc.fingerprint))
         else:
           self._fingerprintMappings[desc.address] = [(desc.or_port, desc.fingerprint)]
-    
+
     self.connLock.release()
-  
+
   def _getFingerprintMappings(self, descriptors = None):
     """
     Provides IP address to (port, fingerprint) tuple mappings for all of the
     currently cached relays.
-    
+
     Arguments:
       descriptors - router status entries (fetched if not provided)
     """
-    
+
     results = {}
     if self.isAlive():
       # fetch the current network status if not provided
       if not descriptors:
         try: descriptors = self.controller.get_network_statuses()
         except stem.ControllerError: descriptors = []
-      
+
       # construct mappings of ips to relay data
       for desc in descriptors:
         results.setdefault(desc.address, []).append((desc.or_port, desc.fingerprint))
-    
+
     return results
-  
+
   def _getRelayFingerprint(self, relayAddress, relayPort):
     """
     Provides the fingerprint associated with the address/port combination.
-    
+
     Arguments:
       relayAddress - address of relay to be returned
       relayPort    - orport of relay (to further narrow the results)
     """
-    
+
     # If we were provided with a string port then convert to an int (so
     # lookups won't mismatch based on type).
     if isinstance(relayPort, str): relayPort = int(relayPort)
-    
+
     # checks if this matches us
     if relayAddress == self.getInfo("address", None):
       if not relayPort or relayPort == self.getOption("ORPort", None):
         return self.getInfo("fingerprint", None)
-    
+
     # if we haven't yet populated the ip -> fingerprint mappings then do so
     if self._fingerprintMappings == None:
       self._fingerprintMappings = self._getFingerprintMappings()
-    
+
     potentialMatches = self._fingerprintMappings.get(relayAddress)
     if not potentialMatches: return None # no relay matches this ip address
-    
+
     if len(potentialMatches) == 1:
       # There's only one relay belonging to this ip address. If the port
       # matches then we're done.
       match = potentialMatches[0]
-      
+
       if relayPort and match[0] != relayPort: return None
       else: return match[1]
     elif relayPort:
@@ -1025,6 +1025,6 @@ class Controller:
       for entryPort, entryFingerprint in potentialMatches:
         if entryPort == relayPort:
           return entryFingerprint
-    
+
     return None
 
diff --git a/arm/util/uiTools.py b/arm/util/uiTools.py
index 2aac55a..999186c 100644
--- a/arm/util/uiTools.py
+++ b/arm/util/uiTools.py
@@ -53,7 +53,7 @@ def demoGlyphs():
   undocumented in the pydocs. For more information see the following man page:
   http://www.mkssoftware.com/docs/man5/terminfo.5.asp
   """
-  
+
   try: curses.wrapper(_showGlyphs)
   except KeyboardInterrupt: pass # quit
 
@@ -61,37 +61,37 @@ def _showGlyphs(stdscr):
   """
   Renders a chart with the ACS glyphs.
   """
-  
+
   # allows things like semi-transparent backgrounds
   try: curses.use_default_colors()
   except curses.error: pass
-  
+
   # attempts to make the cursor invisible
   try: curses.curs_set(0)
   except curses.error: pass
-  
+
   acsOptions = [item for item in curses.__dict__.items() if item[0].startswith("ACS_")]
   acsOptions.sort(key=lambda i: (i[1])) # order by character codes
-  
+
   # displays a chart with all the glyphs and their representations
   height, width = stdscr.getmaxyx()
   if width < 30: return # not enough room to show a column
   columns = width / 30
-  
+
   # display title
   stdscr.addstr(0, 0, "Curses Glyphs:", curses.A_STANDOUT)
-  
+
   x, y = 0, 1
   while acsOptions:
     name, keycode = acsOptions.pop(0)
     stdscr.addstr(y, x * 30, "%s (%i)" % (name, keycode))
     stdscr.addch(y, (x * 30) + 25, keycode)
-    
+
     x += 1
     if x >= columns:
       x, y = 0, y + 1
       if y >= height: break
-  
+
   stdscr.getch() # quit on keyboard input
 
 def isUnicodeAvailable():
@@ -99,31 +99,31 @@ def isUnicodeAvailable():
   True if curses has wide character support, false otherwise or if it can't be
   determined.
   """
-  
+
   global IS_UNICODE_SUPPORTED
   if IS_UNICODE_SUPPORTED == None:
     if CONFIG["features.printUnicode"]:
       # Checks if our LANG variable is unicode. This is what will be respected
       # when printing multi-byte characters after calling...
       # locale.setlocale(locale.LC_ALL, '')
-      # 
+      #
       # so if the LANG isn't unicode then setting this would be pointless.
-      
+
       isLangUnicode = "utf-" in os.environ.get("LANG", "").lower()
       IS_UNICODE_SUPPORTED = isLangUnicode and _isWideCharactersAvailable()
     else: IS_UNICODE_SUPPORTED = False
-  
+
   return IS_UNICODE_SUPPORTED
 
 def getPrintable(line, keepNewlines = True):
   """
   Provides the line back with non-printable characters stripped.
-  
+
   Arguments:
     line          - string to be processed
     stripNewlines - retains newlines if true, stripped otherwise
   """
-  
+
   line = line.replace('\xc2', "'")
   line = "".join([char for char in line if (isprint(char) or (keepNewlines and char == "\n"))])
   return line
@@ -132,7 +132,7 @@ def isColorSupported():
   """
   True if the display supports showing color, false otherwise.
   """
-  
+
   if COLOR_IS_SUPPORTED == None: _initColors()
   return COLOR_IS_SUPPORTED
 
@@ -142,14 +142,14 @@ def getColor(color):
   include:
   red       green     yellow    blue
   cyan      magenta   black     white
-  
-  If color support isn't available or colors can't be initialized then this uses the 
+
+  If color support isn't available or colors can't be initialized then this uses the
   terminal's default coloring scheme.
-  
+
   Arguments:
     color - name of the foreground color to be returned
   """
-  
+
   colorOverride = getColorOverride()
   if colorOverride: color = colorOverride
   if not COLOR_ATTR_INITIALIZED: _initColors()
@@ -159,12 +159,12 @@ def setColorOverride(color = None):
   """
   Overwrites all requests for color with the given color instead. This raises
   a ValueError if the color is invalid.
-  
+
   Arguments:
     color - name of the color to overwrite requests with, None to use normal
             coloring
   """
-  
+
   if color == None:
     CONFIG["features.colorOverride"] = "none"
   elif color in COLOR_LIST.keys():
@@ -175,7 +175,7 @@ def getColorOverride():
   """
   Provides the override color used by the interface, None if it isn't set.
   """
-  
+
   colorOverride = CONFIG.get("features.colorOverride", "none")
   if colorOverride == "none": return None
   else: return colorOverride
@@ -188,16 +188,16 @@ def cropStr(msg, size, minWordLen = 4, minCrop = 0, endType = Ending.ELLIPSE, ge
   including those) then this provides an empty string. If a cropped string ends
   with a comma or period then it's stripped (unless we're providing the
   remainder back). Examples:
-  
+
   cropStr("This is a looooong message", 17)
   "This is a looo..."
-  
+
   cropStr("This is a looooong message", 12)
   "This is a..."
-  
+
   cropStr("This is a looooong message", 3)
   ""
-  
+
   Arguments:
     msg          - source text
     size         - room available for text
@@ -211,81 +211,81 @@ def cropStr(msg, size, minWordLen = 4, minCrop = 0, endType = Ending.ELLIPSE, ge
     getRemainder - returns a tuple instead, with the second part being the
                    cropped portion of the message
   """
-  
+
   # checks if there's room for the whole message
   if len(msg) <= size:
     if getRemainder: return (msg, "")
     else: return msg
-  
+
   # avoids negative input
   size = max(0, size)
   if minWordLen != None: minWordLen = max(0, minWordLen)
   minCrop = max(0, minCrop)
-  
+
   # since we're cropping, the effective space available is less with an
   # ellipse, and cropping words requires an extra space for hyphens
   if endType == Ending.ELLIPSE: size -= 3
   elif endType == Ending.HYPHEN and minWordLen != None: minWordLen += 1
-  
+
   # checks if there isn't the minimum space needed to include anything
   lastWordbreak = msg.rfind(" ", 0, size + 1)
-  
+
   if lastWordbreak == -1:
     # we're splitting the first word
     if minWordLen == None or size < minWordLen:
       if getRemainder: return ("", msg)
       else: return ""
-    
+
     includeCrop = True
   else:
     lastWordbreak = len(msg[:lastWordbreak].rstrip()) # drops extra ending whitespaces
     if (minWordLen != None and size < minWordLen) or (minWordLen == None and lastWordbreak < 1):
       if getRemainder: return ("", msg)
       else: return ""
-    
+
     if minWordLen == None: minWordLen = sys.maxint
     includeCrop = size - lastWordbreak - 1 >= minWordLen
-  
+
   # if there's a max crop size then make sure we're cropping at least that many characters
   if includeCrop and minCrop:
     nextWordbreak = msg.find(" ", size)
     if nextWordbreak == -1: nextWordbreak = len(msg)
     includeCrop = nextWordbreak - size + 1 >= minCrop
-  
+
   if includeCrop:
     returnMsg, remainder = msg[:size], msg[size:]
     if endType == Ending.HYPHEN:
       remainder = returnMsg[-1] + remainder
       returnMsg = returnMsg[:-1].rstrip() + "-"
   else: returnMsg, remainder = msg[:lastWordbreak], msg[lastWordbreak:]
-  
+
   # if this is ending with a comma or period then strip it off
   if not getRemainder and returnMsg and returnMsg[-1] in (",", "."):
     returnMsg = returnMsg[:-1]
-  
+
   if endType == Ending.ELLIPSE:
     returnMsg = returnMsg.rstrip() + "..."
-  
+
   if getRemainder: return (returnMsg, remainder)
   else: return returnMsg
 
 def padStr(msg, size, cropExtra = False):
   """
   Provides the string padded with whitespace to the given length.
-  
+
   Arguments:
     msg       - string to be padded
     size      - length to be padded to
     cropExtra - crops string if it's longer than the size if true
   """
-  
+
   if cropExtra: msg = msg[:size]
   return ("%%-%is" % size) % msg
 
 def drawBox(panel, top, left, width, height, attr=curses.A_NORMAL):
   """
   Draws a box in the panel with the given bounds.
-  
+
   Arguments:
     panel  - panel in which to draw
     top    - vertical position of the box's top
@@ -294,15 +294,15 @@ def drawBox(panel, top, left, width, height, attr=curses.A_NORMAL):
     height - height of the drawn box
     attr   - text attributes
   """
-  
+
   # draws the top and bottom
   panel.hline(top, left + 1, width - 2, attr)
   panel.hline(top + height - 1, left + 1, width - 2, attr)
-  
+
   # draws the left and right sides
   panel.vline(top + 1, left, height - 2, attr)
   panel.vline(top + 1, left + width - 1, height - 2, attr)
-  
+
   # draws the corners
   panel.addch(top, left, curses.ACS_ULCORNER, attr)
   panel.addch(top, left + width - 1, curses.ACS_URCORNER, attr)
@@ -311,22 +311,22 @@ def drawBox(panel, top, left, width, height, attr=curses.A_NORMAL):
 def isSelectionKey(key):
   """
   Returns true if the keycode matches the enter or space keys.
-  
+
   Argument:
     key - keycode to be checked
   """
-  
+
   return key in (curses.KEY_ENTER, 10, ord(' '))
 
 def isScrollKey(key):
   """
   Returns true if the keycode is recognized by the getScrollPosition function
   for scrolling.
-  
+
   Argument:
     key - keycode to be checked
   """
-  
+
   return key in SCROLL_KEYS
 
 def getScrollPosition(key, position, pageHeight, contentHeight, isCursor = False):
@@ -338,9 +338,9 @@ def getScrollPosition(key, position, pageHeight, contentHeight, isCursor = False
   Page Up / Page Down - scrolls by the pageHeight
   Home - top of the content
   End - bottom of the content
-  
+
   This provides the input position if the key doesn't correspond to the above.
-  
+
   Arguments:
     key           - keycode for the user's input
     position      - starting position
@@ -348,7 +348,7 @@ def getScrollPosition(key, position, pageHeight, contentHeight, isCursor = False
     contentHeight - total lines of content that can be scrolled
     isCursor      - tracks a cursor position rather than scroll if true
   """
-  
+
   if isScrollKey(key):
     shift = 0
     if key == curses.KEY_UP: shift = -1
@@ -357,7 +357,7 @@ def getScrollPosition(key, position, pageHeight, contentHeight, isCursor = False
     elif key == curses.KEY_NPAGE: shift = pageHeight - 1 if isCursor else pageHeight
     elif key == curses.KEY_HOME: shift = -contentHeight
     elif key == curses.KEY_END: shift = contentHeight
-    
+
     # returns the shift, restricted to valid bounds
     maxLoc = contentHeight - 1 if isCursor else contentHeight - pageHeight
     return max(0, min(position + shift, maxLoc))
@@ -368,59 +368,59 @@ class Scroller:
   Tracks the scrolling position when there might be a visible cursor. This
   expects that there is a single line displayed per an entry in the contents.
   """
-  
+
   def __init__(self, isCursorEnabled):
     self.scrollLoc, self.cursorLoc = 0, 0
     self.cursorSelection = None
     self.isCursorEnabled = isCursorEnabled
-  
+
   def getScrollLoc(self, content, pageHeight):
     """
     Provides the scrolling location, taking into account its cursor's location
     content size, and page height.
-    
+
     Arguments:
       content    - displayed content
       pageHeight - height of the display area for the content
     """
-    
+
     if content and pageHeight:
       self.scrollLoc = max(0, min(self.scrollLoc, len(content) - pageHeight + 1))
-      
+
       if self.isCursorEnabled:
         self.getCursorSelection(content) # resets the cursor location
-        
+
         # makes sure the cursor is visible
         if self.cursorLoc < self.scrollLoc:
           self.scrollLoc = self.cursorLoc
         elif self.cursorLoc > self.scrollLoc + pageHeight - 1:
           self.scrollLoc = self.cursorLoc - pageHeight + 1
-      
+
       # checks if the bottom would run off the content (this could be the
       # case when the content's size is dynamic and entries are removed)
       if len(content) > pageHeight:
         self.scrollLoc = min(self.scrollLoc, len(content) - pageHeight)
-    
+
     return self.scrollLoc
-  
+
   def getCursorSelection(self, content):
     """
     Provides the selected item in the content. This is the same entry until
     the cursor moves or it's no longer available (in which case it moves on to
     the next entry).
-    
+
     Arguments:
       content - displayed content
     """
-    
+
     # TODO: needs to handle duplicate entries when using this for the
     # connection panel
-    
+
     if not self.isCursorEnabled: return None
     elif not content:
       self.cursorLoc, self.cursorSelection = 0, None
       return None
-    
+
     self.cursorLoc = min(self.cursorLoc, len(content) - 1)
     if self.cursorSelection != None and self.cursorSelection in content:
       # moves cursor location to track the selection
@@ -428,24 +428,24 @@ class Scroller:
     else:
       # select the next closest entry
       self.cursorSelection = content[self.cursorLoc]
-    
+
     return self.cursorSelection
-  
+
   def handleKey(self, key, content, pageHeight):
     """
     Moves either the scroll or cursor according to the given input.
-    
+
     Arguments:
       key        - key code of user input
       content    - displayed content
       pageHeight - height of the display area for the content
     """
-    
+
     if self.isCursorEnabled:
       self.getCursorSelection(content) # resets the cursor location
       startLoc = self.cursorLoc
     else: startLoc = self.scrollLoc
-    
+
     newLoc = getScrollPosition(key, startLoc, pageHeight, len(content), self.isCursorEnabled)
     if startLoc != newLoc:
       if self.isCursorEnabled: self.cursorSelection = content[newLoc]
@@ -458,16 +458,16 @@ def _isWideCharactersAvailable():
   True if curses has wide character support (which is required to print
   unicode). False otherwise.
   """
-  
+
   try:
     # gets the dynamic library used by the interpretor for curses
-    
+
     import _curses
     cursesLib = _curses.__file__
-    
+
     # Uses 'ldd' (Linux) or 'otool -L' (Mac) to determine the curses
     # library dependencies.
-    # 
+    #
     # atagar at fenrir:~/Desktop$ ldd /usr/lib/python2.6/lib-dynload/_curses.so
     #   linux-gate.so.1 =>  (0x00a51000)
     #   libncursesw.so.5 => /lib/libncursesw.so.5 (0x00faa000)
@@ -475,24 +475,24 @@ def _isWideCharactersAvailable():
     #   libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00158000)
     #   libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0x00398000)
     #   /lib/ld-linux.so.2 (0x00ca8000)
-    # 
+    #
     # atagar$ otool -L /System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/lib-dynload/_curses.so
     # /System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/lib-dynload/_curses.so:
     #   /usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
     #   /usr/lib/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)
     #   /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 111.1.6)
-    
+
     libDependencyLines = None
     if system.is_available("ldd"):
       libDependencyLines = system.call("ldd %s" % cursesLib)
     elif system.is_available("otool"):
       libDependencyLines = system.call("otool -L %s" % cursesLib)
-    
+
     if libDependencyLines:
       for line in libDependencyLines:
         if "libncursesw" in line: return True
   except: pass
-  
+
   return False
 
 def _initColors():
@@ -500,7 +500,7 @@ def _initColors():
   Initializes color mappings usable by curses. This can only be done after
   calling curses.initscr().
   """
-  
+
   global COLOR_ATTR_INITIALIZED, COLOR_IS_SUPPORTED
   if not COLOR_ATTR_INITIALIZED:
     # hack to replace all ACS characters with '+' if ACS support has been
@@ -509,27 +509,27 @@ def _initColors():
       for item in curses.__dict__:
         if item.startswith("ACS_"):
           curses.__dict__[item] = ord('+')
-      
+
       # replace a few common border pipes that are better rendered as '|' or
       # '-' instead
-      
+
       curses.ACS_SBSB = ord('|')
       curses.ACS_VLINE = ord('|')
       curses.ACS_BSBS = ord('-')
       curses.ACS_HLINE = ord('-')
-    
+
     COLOR_ATTR_INITIALIZED = True
     COLOR_IS_SUPPORTED = False
     if not CONFIG["features.colorInterface"]: return
-    
+
     try: COLOR_IS_SUPPORTED = curses.has_colors()
     except curses.error: return # initscr hasn't been called yet
-    
+
     # initializes color mappings if color support is available
     if COLOR_IS_SUPPORTED:
       colorpair = 0
       log.info("Terminal color support detected and enabled")
-      
+
       for colorName in COLOR_LIST:
         fgColor = COLOR_LIST[colorName]
         bgColor = -1 # allows for default (possibly transparent) background
diff --git a/setup.py b/setup.py
index 64d4986..616222c 100644
--- a/setup.py
+++ b/setup.py
@@ -10,18 +10,18 @@ def getResources(dst, sourceDir):
   """
   Provides a list of tuples of the form...
   [(destination, (file1, file2...)), ...]
-  
+
   for the given contents of the arm directory (that's right, distutils isn't
   smart enough to know how to copy directories).
   """
-  
+
   results = []
-  
+
   for root, _, files in os.walk(os.path.join("arm", sourceDir)):
     if files:
       fileListing = tuple([os.path.join(root, file) for file in files])
       results.append((os.path.join(dst, root[4:]), fileListing))
-  
+
   return results
 
 # Use 'tor-arm' instead of 'arm' in the path for the sample armrc if we're
@@ -43,7 +43,7 @@ try:
   docPathFlagIndex = sys.argv.index("--docPath")
   if docPathFlagIndex < len(sys.argv) - 1:
     docPath = sys.argv[docPathFlagIndex + 1]
-    
+
     # remove the custom --docPath argument (otherwise the setup call will
     # complain about them)
     del sys.argv[docPathFlagIndex:docPathFlagIndex + 3]
@@ -62,28 +62,28 @@ except ValueError: pass # --docPath flag not found
 manFilename = "arm/resoureces/arm.1"
 if "install" in sys.argv:
   sys.argv += ["--install-purelib", "/usr/share"]
-  
+
   # Compresses the man page. This is a temporary file that we'll install. If
   # something goes wrong then we'll print the issue and use the uncompressed man
   # page instead.
-  
+
   try:
     manInputFile = open('arm/resources/arm.1', 'r')
     manContents = manInputFile.read()
     manInputFile.close()
-    
+
     # temporary destination for the man page guarenteed to be unoccupied (to
     # avoid conflicting with files that are already there)
     tmpFilename = tempfile.mktemp("/arm.1.gz")
-    
+
     # make dir if the path doesn't already exist
     baseDir = os.path.dirname(tmpFilename)
     if not os.path.exists(baseDir): os.makedirs(baseDir)
-    
+
     manOutputFile = gzip.open(tmpFilename, 'wb')
     manOutputFile.write(manContents)
     manOutputFile.close()
-    
+
     # places in tmp rather than a relative path to avoid having this copy appear
     # in the deb and rpm builds
     manFilename = tmpFilename
@@ -105,7 +105,7 @@ setup(name='arm',
                   ("/usr/share/man/man1", [manFilename]),
                   (docPath, ["armrc.sample"]),
                   ("/usr/share/arm/gui", ["arm/gui/arm.xml"]),
-                  ("/usr/share/arm", ["arm/settings.cfg", "arm/uninstall"])] + 
+                  ("/usr/share/arm", ["arm/settings.cfg", "arm/uninstall"])] +
                   getResources("/usr/share/arm", "resources"),
      )
 





More information about the tor-commits mailing list