[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r23680: {arm} Revisions in preparation for a config editor panel - Using s (in arm/trunk: . src/interface src/util)
Author: atagar
Date: 2010-10-26 16:48:35 +0000 (Tue, 26 Oct 2010)
New Revision: 23680
Added:
arm/trunk/src/interface/configFilePanel.py
Removed:
arm/trunk/src/interface/confPanel.py
Modified:
arm/trunk/armrc.sample
arm/trunk/src/interface/controller.py
arm/trunk/src/util/torTools.py
arm/trunk/src/util/torrc.py
Log:
Revisions in preparation for a config editor panel
- Using singleton pattern for fetching the torrc, allowing the controller to
handle loading and validation logging independently of the the torrc display.
- Dropping tor/arm state support from the display for now, greatly simplifying
the configFile panel. The next work will be to make an independent panel for
showing/editing tor's current state.
Modified: arm/trunk/armrc.sample
===================================================================
--- arm/trunk/armrc.sample 2010-10-26 16:47:34 UTC (rev 23679)
+++ arm/trunk/armrc.sample 2010-10-26 16:48:35 UTC (rev 23680)
@@ -13,6 +13,9 @@
# Renders the interface with color if set and the terminal supports it
features.colorInterface true
+# Checks the torrc for issues, warning and hilighting problems if true
+features.torrc.validate true
+
# Set this if you're running in a chroot jail or other environment where tor's
# resources (log, state, etc) should have a prefix in their paths.
features.pathPrefix
@@ -55,15 +58,12 @@
# ---------------------------
# type
# 0 -> tor state, 1 -> torrc, 2 -> arm state, 3 -> armrc
-# validate
-# checks torrc and armrcs for issues, warning and hilighting problems if true
# showScrollbars
# displays scrollbars when the torrc content is longer than the display
# maxLinesPerEntry
# max number of lines to display for a single entry in the torrc
features.config.type 0
-features.config.validate true
features.config.showScrollbars true
features.config.maxLinesPerEntry 8
@@ -169,9 +169,9 @@
log.logPanel.logFileOpened NOTICE
log.logPanel.logFileWriteFailed ERR
log.logPanel.forceDoubleRedraw DEBUG
-log.confPanel.torrcReadFailed WARN
-log.torrcValidation.duplicateEntries NOTICE
-log.torrcValidation.torStateDiffers NOTICE
+log.torrc.readFailed WARN
+log.torrc.validation.duplicateEntries NOTICE
+log.torrc.validation.torStateDiffers NOTICE
log.connLookupFailed INFO
log.connLookupFailover NOTICE
log.connLookupAbandon WARN
Deleted: arm/trunk/src/interface/confPanel.py
===================================================================
--- arm/trunk/src/interface/confPanel.py 2010-10-26 16:47:34 UTC (rev 23679)
+++ arm/trunk/src/interface/confPanel.py 2010-10-26 16:48:35 UTC (rev 23680)
@@ -1,366 +0,0 @@
-"""
-Panel displaying the torrc and validation done against it.
-"""
-
-import math
-import curses
-import threading
-
-from util import conf, log, panel, torrc, torTools, uiTools
-
-DEFAULT_CONFIG = {"features.config.type": 0,
- "features.config.validate": True,
- "features.config.showScrollbars": True,
- "features.config.maxLinesPerEntry": 8,
- "log.confPanel.torrcReadFailed": log.WARN,
- "log.torrcValidation.duplicateEntries": log.NOTICE,
- "log.torrcValidation.torStateDiffers": log.NOTICE,
- "torrc.map": {}}
-
-# configurations that can be displayed
-TOR_STATE, TORRC, ARM_STATE, ARMRC = range(4)
-CONFIG_LABELS = {TORRC: "torrc", TOR_STATE: "tor state", ARMRC: "armrc", ARM_STATE: "arm state"}
-
-class ConfPanel(panel.Panel):
- """
- Presents torrc, armrc, or loaded settings with syntax highlighting in a
- scrollable area.
- """
-
- def __init__(self, stdscr, config=None):
- panel.Panel.__init__(self, stdscr, "conf", 0)
-
- self._config = dict(DEFAULT_CONFIG)
- if config:
- config.update(self._config, {
- "features.config.type": (0, 3),
- "features.config.maxLinesPerEntry": 1})
-
- self.valsLock = threading.RLock()
- self.scroll = 0
- self.showLabel = True # shows top label if true, hides otherwise
- self.showLineNum = True
- self.stripComments = False
-
- # type of config currently being displayed
- self.configType = self._config["features.config.type"]
-
- # Mappings of config types to tuples of:
- # (contents, corrections, confLocation)
- # This maps to None if they haven't been loaded yet or failed to load.
- self.configs = {TORRC: None, TOR_STATE: None, ARMRC: None, ARM_STATE: None}
-
- # 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
-
- self.loadConfig(TOR_STATE)
- self.loadConfig(TORRC)
-
- def loadConfig(self, configType = None, logErrors = True):
- """
- Reloads configuration or state contents and resets scroll height. Returns
- True if successful, else false.
-
- Arguments:
- configType - configuration type to load (displayed config type if None)
- logErrors - logs if unable to read the torrc or issues are found during
- validation
- """
-
- self.valsLock.acquire()
- if configType == None: configType = self.configType
- confContents, corrections, confLocation = [], {}, None
-
- if configType in (TORRC, ARMRC):
- # load configuration file
- try:
- if configType == TORRC: confLocation = torrc.getConfigLocation()
- else:
- confLocation = conf.getConfig("arm").path
- if not confLocation: raise IOError("no armrc has been loaded")
-
- confFile = open(confLocation, "r")
- confContents = confFile.readlines()
- confFile.close()
- self.scroll = 0
- except IOError, exc:
- self.configs[configType] = None
- msg = "Unable to load torrc (%s)" % exc
- if logErrors: log.log(self._config["log.confPanel.torrcReadFailed"], msg)
- self.valsLock.release()
- return False
-
- if configType == TORRC and self._config["features.config.validate"]:
- # TODO: add armrc validation
- corrections = torrc.validate(confContents)
-
- if corrections and logErrors:
- # logs issues found during validation
- irrelevantLines, mismatchLines = [], []
- for lineNum in corrections:
- problem = corrections[lineNum][0]
- if problem == torrc.VAL_DUPLICATE: irrelevantLines.append(lineNum)
- elif problem == torrc.VAL_MISMATCH: mismatchLines.append(lineNum)
-
- if irrelevantLines:
- irrelevantLines.sort()
-
- if len(irrelevantLines) > 1: first, second, third = "Entries", "are", ", including lines"
- else: first, second, third = "Entry", "is", " on line"
- msgStart = "%s in your torrc %s ignored due to duplication%s" % (first, second, third)
- msgLines = ", ".join([str(val + 1) for val in irrelevantLines])
- msg = "%s: %s (highlighted in blue)" % (msgStart, msgLines)
- log.log(self._config["log.torrcValidation.duplicateEntries"], msg)
-
- if mismatchLines:
- mismatchLines.sort()
- msgStart = "Tor's state differs from loaded torrc on line%s" % ("s" if len(mismatchLines) > 1 else "")
- msgLines = ", ".join([str(val + 1) for val in mismatchLines])
- msg = "%s: %s" % (msgStart, msgLines)
- log.log(self._config["log.torrcValidation.torStateDiffers"], msg)
-
- if confContents:
- # Restricts contents to be displayable characters:
- # - Tabs print as three spaces. Keeping them as tabs is problematic for
- # the layout since it's counted as a single character, but occupies
- # several cells.
- # - Strips control and unprintable characters.
- for lineNum in range(len(confContents)):
- lineText = confContents[lineNum]
- lineText = lineText.replace("\t", " ")
- lineText = "".join([char for char in lineText if curses.ascii.isprint(char)])
- confContents[lineNum] = lineText
- elif configType == TOR_STATE:
- # for all recognized tor config options, provide their current value
- conn = torTools.getConn()
- configOptionQuery = conn.getInfo("config/names", "").strip().split("\n")
-
- for lineNum in range(len(configOptionQuery)):
- # lines are of the form "<option> <type>", like:
- # UseEntryGuards Boolean
- line = configOptionQuery[lineNum]
- confOption, confType = line.strip().split(" ", 1)
-
- confValue = None
- if confOption in self._config["torrc.map"]:
- confMappings = conn.getOptionMap(self._config["torrc.map"][confOption], {})
- if confOption in confMappings: confValue = confMappings[confOption]
- fetchConfOption = self._config["torrc.map"][confOption]
- else:
- confValue = ", ".join(conn.getOption(confOption, [], True))
-
- # provides nicer values for recognized types
- if not confValue: confValue = "<none>"
- elif confType == "Boolean" and confValue in ("0", "1"):
- confValue = "False" if confValue == "0" else "True"
- elif confType == "DataSize" and confValue.isdigit():
- confValue = uiTools.getSizeLabel(int(confValue))
- elif confType == "TimeInterval" and confValue.isdigit():
- confValue = uiTools.getTimeLabel(int(confValue), isLong = True)
-
- confContents.append("%s %s\n" % (confOption, confValue))
-
- # hijacks the correction field to display the value's type
- corrections[lineNum] = (None, confType)
- elif configType == ARM_STATE:
- # loaded via the conf utility
- armConf = conf.getConfig("arm")
- for key in armConf.getKeys():
- confContents.append("%s %s\n" % (key, ", ".join(armConf.getValue(key, [], True))))
- confContents.sort()
-
- self.configs[configType] = (confContents, corrections, confLocation)
-
- # sets the content height to be something somewhat reasonable
- self._lastContentHeight = len(confContents)
- self._lastContentHeightArgs = None
-
- self.valsLock.release()
- return True
-
- def handleKey(self, key):
- self.valsLock.acquire()
- if uiTools.isScrollKey(key) and self.configs[self.configType] != None:
- pageHeight = self.getPreferredSize()[0] - 1
- newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
-
- if self.scroll != newScroll:
- self.scroll = newScroll
- self.redraw(True)
- elif key == ord('n') or key == ord('N'):
- self.showLineNum = not self.showLineNum
- self._lastContentHeightArgs = None
- self.redraw(True)
- elif key == ord('s') or key == ord('S'):
- self.stripComments = not self.stripComments
- self._lastContentHeightArgs = None
- self.redraw(True)
-
- self.valsLock.release()
-
- def setConfigType(self, configType):
- """
- Sets the type of configuration to be displayed. If the configuration isn't
- already loaded then this fetches it.
-
- Arguments
- configType - enum representing the type of configuration to be loaded
- """
-
- if self.configType != configType or not self.configs[configType]:
- self.valsLock.acquire()
- self.configType = configType
-
- if not self.configs[configType]: self.loadConfig()
-
- self._lastContentHeightArgs = None
- self.redraw(True)
- self.valsLock.release()
-
- def draw(self, subwindow, 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.configs[self.configType]:
- renderedContents, corrections, confLocation = self.configs[self.configType]
-
- if renderedContents == None:
- renderedContents = ["### Unable to load the %s ###" % CONFIG_LABELS[self.configType]]
- elif self.stripComments:
- renderedContents = torrc.stripComments(renderedContents)
-
- # 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 self._config["features.config.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.showLabel:
- sourceLabel = "Tor" if self.configType in (TORRC, TOR_STATE) else "Arm"
- typeLabel = "Config" if self.configType in (TORRC, ARMRC) else "State"
- locationLabel = " (%s)" % confLocation if confLocation else ""
- self.addstr(0, 0, "%s %s%s:" % (sourceLabel, typeLabel, locationLabel), curses.A_STANDOUT)
-
- for lineNumber in range(0, len(renderedContents)):
- lineText = renderedContents[lineNumber]
- lineText = lineText.rstrip() # remove ending whitespace
-
- # blank lines are hidden when stripping comments, and undefined
- # values are dropped if showing tor's state
- if self.stripComments:
- if not lineText: continue
- elif self.configType == TOR_STATE and "<none>" in 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(" ")
- if optionIndex == -1:
- lineComp["option"][0] = lineText # no argument provided
- else:
- optionText = strippedLine[:optionIndex]
- optionEnd = lineText.find(optionText) + len(optionText)
- lineComp["option"][0] = lineText[:optionEnd]
- lineComp["argument"][0] = lineText[optionEnd:]
-
- # gets the correction
- if lineNumber in corrections:
- lineIssue, lineIssueMsg = corrections[lineNumber]
-
- if lineIssue == torrc.VAL_DUPLICATE:
- lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
- lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
- elif lineIssue == torrc.VAL_MISMATCH:
- lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
- lineComp["correction"][0] = " (%s)" % lineIssueMsg
- else:
- # For some types of configs the correction field is simply used to
- # 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 = self._config["features.config.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
- if lineOffset == maxLinesPerEntry - 1:
- msg = uiTools.cropStr(msg, maxMsgSize)
- else:
- includeBreak = True
- msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_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()
-
- def redraw(self, forceRedraw=False, block=False):
- panel.Panel.redraw(self, forceRedraw, block)
-
Copied: arm/trunk/src/interface/configFilePanel.py (from rev 23670, arm/trunk/src/interface/confPanel.py)
===================================================================
--- arm/trunk/src/interface/configFilePanel.py (rev 0)
+++ arm/trunk/src/interface/configFilePanel.py 2010-10-26 16:48:35 UTC (rev 23680)
@@ -0,0 +1,212 @@
+"""
+Panel displaying the torrc or armrc with the validation done against it.
+"""
+
+import math
+import curses
+import threading
+
+from util import conf, panel, torrc, uiTools
+
+DEFAULT_CONFIG = {"features.config.showScrollbars": True,
+ "features.config.maxLinesPerEntry": 8}
+
+TORRC, ARMRC = range(1, 3) # configuration file types that can be displayed
+
+class ConfigFilePanel(panel.Panel):
+ """
+ Renders the current torrc or armrc with syntax highlighting in a scrollable
+ area.
+ """
+
+ def __init__(self, stdscr, configType, config=None):
+ panel.Panel.__init__(self, stdscr, "conf", 0)
+
+ self._config = dict(DEFAULT_CONFIG)
+ if config:
+ config.update(self._config, {"features.config.maxLinesPerEntry": 1})
+
+ self.valsLock = threading.RLock()
+ self.configType = configType
+ self.scroll = 0
+ self.showLabel = True # shows top label (hides otherwise)
+ 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
+
+ def handleKey(self, key):
+ self.valsLock.acquire()
+ 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)
+ elif key == ord('n') or key == ord('N'):
+ self.showLineNum = not self.showLineNum
+ self._lastContentHeightArgs = None
+ self.redraw(True)
+ elif key == ord('s') or key == ord('S'):
+ self.stripComments = not self.stripComments
+ self._lastContentHeightArgs = None
+ self.redraw(True)
+
+ self.valsLock.release()
+
+ def draw(self, subwindow, 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 == TORRC:
+ loadedTorrc = torrc.getTorrc()
+ loadedTorrc.getLock().acquire()
+ confLocation = loadedTorrc.getConfigLocation()
+
+ if not loadedTorrc.isLoaded():
+ renderedContents = ["### Unable to load the torrc ###"]
+ else:
+ renderedContents = loadedTorrc.getDisplayContents(self.stripComments)
+ corrections = loadedTorrc.getCorrections()
+
+ loadedTorrc.getLock().release()
+ else:
+ # TODO: The armrc use case is incomplete. There should be equivilant
+ # reloading and validation capabilities to the torrc.
+ loadedArmrc = conf.getConfig("arm")
+ confLocation = loadedArmrc.path
+ renderedContents = list(loadedArmrc.rawContents)
+
+ # 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 self._config["features.config.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.showLabel:
+ sourceLabel = "Tor" if self.configType == TORRC else "Arm"
+ locationLabel = " (%s)" % confLocation if confLocation else ""
+ self.addstr(0, 0, "%s Config%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)
+
+ 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(" ")
+ if optionIndex == -1:
+ lineComp["option"][0] = lineText # no argument provided
+ else:
+ optionText = strippedLine[:optionIndex]
+ optionEnd = lineText.find(optionText) + len(optionText)
+ lineComp["option"][0] = lineText[:optionEnd]
+ lineComp["argument"][0] = lineText[optionEnd:]
+
+ # gets the correction
+ if lineNumber in corrections:
+ lineIssue, lineIssueMsg = corrections[lineNumber]
+
+ if lineIssue == torrc.VAL_DUPLICATE:
+ lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
+ lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
+ elif lineIssue == torrc.VAL_MISMATCH:
+ lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
+ lineComp["correction"][0] = " (%s)" % lineIssueMsg
+ else:
+ # For some types of configs the correction field is simply used to
+ # 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 = self._config["features.config.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
+ if lineOffset == maxLinesPerEntry - 1:
+ msg = uiTools.cropStr(msg, maxMsgSize)
+ else:
+ includeBreak = True
+ msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_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()
+
+ def redraw(self, forceRedraw=False, block=False):
+ panel.Panel.redraw(self, forceRedraw, block)
+
Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py 2010-10-26 16:47:34 UTC (rev 23679)
+++ arm/trunk/src/interface/controller.py 2010-10-26 16:48:35 UTC (rev 23680)
@@ -18,11 +18,11 @@
import graphing.graphPanel
import logPanel
import connPanel
-import confPanel
+import configFilePanel
import descriptorPopup
import fileDescriptorPopup
-from util import conf, log, connections, hostnames, panel, sysTools, torTools, uiTools
+from util import conf, log, connections, hostnames, panel, sysTools, torrc, torTools, uiTools
import graphing.bandwidthStats
import graphing.connStats
import graphing.psStats
@@ -42,13 +42,16 @@
["torrc"]]
PAUSEABLE = ["header", "graph", "log", "conn"]
-CONFIG = {"features.graph.type": 1,
+CONFIG = {"log.torrc.readFailed": log.WARN,
+ "features.graph.type": 1,
"queries.refreshRate.rate": 5,
"log.torEventTypeUnrecognized": log.NOTICE,
"features.graph.bw.prepopulate": True,
"log.startTime": log.INFO,
"log.refreshRate": log.DEBUG,
- "log.configEntryUndefined": log.NOTICE}
+ "log.configEntryUndefined": log.NOTICE,
+ "log.torrc.validation.duplicateEntries": log.NOTICE,
+ "log.torrc.validation.torStateDiffers": log.NOTICE}
class ControlPanel(panel.Panel):
""" Draws single line label for interface controls. """
@@ -356,6 +359,46 @@
#except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
# confLocation = ""
+ # loads the torrc and provides warnings in case of validation errors
+ loadedTorrc = torrc.getTorrc()
+ loadedTorrc.getLock().acquire()
+
+ try:
+ loadedTorrc.load()
+ except IOError, exc:
+ excMsg = str(exc)
+ if excMsg.startswith("[Errno "): excMsg = excMsg[10:]
+ msg = "Unable to load torrc (%s)" % excMsg
+ log.log(CONFIG["log.torrc.readFailed"], msg)
+
+ if loadedTorrc.isLoaded():
+ corrections = loadedTorrc.getCorrections()
+ irrelevantLines, mismatchLines = [], []
+
+ for lineNum in corrections:
+ problem = corrections[lineNum][0]
+ if problem == torrc.VAL_DUPLICATE: irrelevantLines.append(lineNum)
+ elif problem == torrc.VAL_MISMATCH: mismatchLines.append(lineNum)
+
+ if irrelevantLines:
+ irrelevantLines.sort()
+
+ if len(irrelevantLines) > 1: first, second, third = "Entries", "are", ", including lines"
+ else: first, second, third = "Entry", "is", " on line"
+ msgStart = "%s in your torrc %s ignored due to duplication%s" % (first, second, third)
+ msgLines = ", ".join([str(val + 1) for val in irrelevantLines])
+ msg = "%s: %s (highlighted in blue)" % (msgStart, msgLines)
+ log.log(CONFIG["log.torrc.validation.duplicateEntries"], msg)
+
+ if mismatchLines:
+ mismatchLines.sort()
+ msgStart = "Tor's state differs from loaded torrc on line%s" % ("s" if len(mismatchLines) > 1 else "")
+ msgLines = ", ".join([str(val + 1) for val in mismatchLines])
+ msg = "%s: %s" % (msgStart, msgLines)
+ log.log(CONFIG["log.torrc.validation.torStateDiffers"], msg)
+
+ loadedTorrc.getLock().release()
+
# minor refinements for connection resolver
if not isBlindMode:
resolver = connections.getResolver("tor")
@@ -379,7 +422,7 @@
panels["conn"] = connPanel.ConnPanel(stdscr, conn, isBlindMode)
panels["control"] = ControlPanel(stdscr, isBlindMode)
- panels["torrc"] = confPanel.ConfPanel(stdscr, config)
+ panels["torrc"] = configFilePanel.ConfigFilePanel(stdscr, configFilePanel.TORRC, config)
# provides error if pid coulnd't be determined (hopefully shouldn't happen...)
if not torPid: log.log(log.WARN, "Unable to resolve tor pid, abandoning connection listing")
@@ -710,7 +753,7 @@
popup.addfstr(4, 2, "<b>r</b>: reload torrc")
popup.addfstr(4, 41, "<b>x</b>: reset tor (issue sighup)")
- popup.addfstr(5, 2, "<b>c</b>: displayed configuration (<b>%s</b>)" % confPanel.CONFIG_LABELS[panels["torrc"].configType])
+ #popup.addfstr(5, 2, "<b>c</b>: displayed configuration (<b>%s</b>)" % configFilePanel.CONFIG_LABELS[panels["torrc"].configType])
popup.addstr(7, 2, "Press any key...")
popup.refresh()
@@ -1365,11 +1408,24 @@
panel.CURSES_LOCK.release()
elif page == 2 and key == ord('r') or key == ord('R'):
# reloads torrc, providing a notice if successful or not
- isSuccessful = panels["torrc"].loadConfig(logErrors = False)
- confTypeLabel = confPanel.CONFIG_LABELS[panels["torrc"].configType]
- resetMsg = "%s reloaded" % confTypeLabel if isSuccessful else "failed to reload %s" % confTypeLabel
- if isSuccessful: panels["torrc"].redraw(True)
+ loadedTorrc = torrc.getTorrc()
+ loadedTorrc.getLock().acquire()
+ try:
+ loadedTorrc.load()
+ isSuccessful = True
+ except IOError:
+ isSuccessful = False
+
+ loadedTorrc.getLock().release()
+
+ #isSuccessful = panels["torrc"].loadConfig(logErrors = False)
+ #confTypeLabel = confPanel.CONFIG_LABELS[panels["torrc"].configType]
+ resetMsg = "torrc reloaded" if isSuccessful else "failed to reload torrc"
+ if isSuccessful:
+ panels["torrc"]._lastContentHeightArgs = None
+ panels["torrc"].redraw(True)
+
panels["control"].setMsg(resetMsg, curses.A_STANDOUT)
panels["control"].redraw(True)
time.sleep(1)
@@ -1404,9 +1460,11 @@
setPauseState(panels, isPaused, page)
finally:
panel.CURSES_LOCK.release()
- elif page == 2 and (key == ord('c') or key == ord('C')):
+ elif page == 2 and (key == ord('c') or key == ord('C')) and False:
+ # TODO: disabled for now (pending the ConfigStatePanel implementation)
# provides menu to pick config being displayed
- options = [confPanel.CONFIG_LABELS[confType] for confType in range(4)]
+ #options = [confPanel.CONFIG_LABELS[confType] for confType in range(4)]
+ options = []
initialSelection = panels["torrc"].configType
# hides top label of the graph panel and pauses panels
Modified: arm/trunk/src/util/torTools.py
===================================================================
--- arm/trunk/src/util/torTools.py 2010-10-26 16:47:34 UTC (rev 23679)
+++ arm/trunk/src/util/torTools.py 2010-10-26 16:48:35 UTC (rev 23680)
@@ -152,7 +152,7 @@
def getConn():
"""
- Singleton constructor for a Controller. Be aware that this start
+ Singleton constructor for a Controller. Be aware that this starts as being
uninitialized, needing a TorCtl instance before it's fully functional.
"""
Modified: arm/trunk/src/util/torrc.py
===================================================================
--- arm/trunk/src/util/torrc.py 2010-10-26 16:47:34 UTC (rev 23679)
+++ arm/trunk/src/util/torrc.py 2010-10-26 16:48:35 UTC (rev 23680)
@@ -2,9 +2,13 @@
Helper functions for working with tor's configuration file.
"""
+import curses
+import threading
+
from util import sysTools, torTools, uiTools
-CONFIG = {"torrc.map": {},
+CONFIG = {"features.torrc.validate": True,
+ "torrc.map": {},
"torrc.multiline": [],
"torrc.alias": {},
"torrc.label.size.b": [],
@@ -28,8 +32,7 @@
# VAL_MISMATCH - the value doesn't match tor's current state
VAL_DUPLICATE, VAL_MISMATCH = range(1, 3)
-# cached results for the stripComments function
-STRIP_COMMENTS_ARG, STRIP_COMMENTS_RESULT = None, None
+TORRC = None # singleton torrc instance
def loadConfig(config):
CONFIG["torrc.map"] = config.get("torrc.map", {})
@@ -42,6 +45,16 @@
configValues = config.get(configKey, "").split(",")
if configValues: CONFIG[configKey] = [val.strip() for val in configValues]
+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
+
def getConfigLocation():
"""
Provides the location of the torrc, raising an IOError with the reason if the
@@ -77,33 +90,8 @@
return torTools.getPathPrefix() + configLocation
-def stripComments(contents):
+def validate(contents = None):
"""
- Provides the torrc contents back with comments and extra whitespace stripped.
-
- Arguments:
- contents - torrc contents
- """
-
- global STRIP_COMMENTS_ARG, STRIP_COMMENTS_RESULT
-
- if contents == STRIP_COMMENTS_ARG:
- return list(STRIP_COMMENTS_RESULT)
-
- strippedContents = []
- for line in contents:
- # strips off comment
- if line and "#" in line:
- line = line[:line.find("#")]
-
- strippedContents.append(line.strip())
-
- STRIP_COMMENTS_ARG = list(contents)
- STRIP_COMMENTS_RESULT = list(strippedContents)
- return strippedContents
-
-def validate(contents):
- """
Performs validation on the given torrc contents, providing back a mapping of
line numbers to tuples of the (issue, msg) found on them.
@@ -112,7 +100,7 @@
"""
conn = torTools.getConn()
- contents = stripComments(contents)
+ contents = _stripComments(contents)
issuesFound, seenOptions = {}, []
for lineNumber in range(len(contents) - 1, -1, -1):
lineText = contents[lineNumber]
@@ -215,3 +203,146 @@
return None, 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("#")]
+ strippedContents.append(line.strip())
+ return strippedContents
+
+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
+
+ def load(self):
+ """
+ Loads or reloads the torrc contents, raising an IOError if there's a
+ problem.
+ """
+
+ 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")
+ self.contents = configFile.readlines()
+ configFile.close()
+ except IOError, exc:
+ 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.relese()
+ return returnVal
+
+ def getDisplayContents(self, strip = False):
+ """
+ Provides the contents of the configuration file, formatted in a rendering
+ frindly fashion:
+ - Tabs print as three spaces. Keeping them as tabs is problematic for
+ 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 = "".join([char for char in lineText if curses.ascii.isprint(char)])
+ 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
+ elif not CONFIG["features.torrc.validate"]: returnVal = {}
+ else:
+ if self.corrections == None:
+ self.corrections = validate(self.contents)
+
+ returnVal = dict(self.corrections)
+
+ self.valsLock.release()
+ return returnVal
+
+ def getLock(self):
+ """
+ Provides the lock governing concurrent access to the contents.
+ """
+
+ return self.valsLock
+