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") if configOptionQuery: configOptionLines = configOptionQuery.strip().split("\n") for line in configOptionLines: # lines are of the form "<option> <type>[ <documentation>]", like: # UseEntryGuards Boolean # documentation is aparently only in older versions (for instance, # 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 self._config[ "features.config.state.showPrivateOptions"] and confOption.startswith( "__"): continue elif not self._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.getConfig("arm") for key in armConf.getKeys(): 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 __init__(self, stdscr, configType, config=None): panel.Panel.__init__(self, stdscr, "configState", 0) self.sortOrdering = DEFAULT_SORT_ORDER self._config = dict(DEFAULT_CONFIG) if config: config.update( self._config, { "features.config.selectionDetails.height": 0, "features.config.state.colWidth.option": 5, "features.config.state.colWidth.value": 5 }) self.sortOrdering = config.getIntCSV("features.config.order", self.sortOrdering, 3, 0, 6) self.configType = configType self.confContents = [] self.scroller = uiTools.Scroller(True) self.valsLock = threading.RLock() if self.configType == TOR_STATE: conn = torTools.getConn() customOptions = torConfig.getCustomOptions() configOptionLines = conn.getInfo("config/names", "").strip().split("\n") for line in configOptionLines: # lines are of the form "<option> <type>", like: # UseEntryGuards Boolean confOption, confType = line.strip().split(" ", 1) # skips private and virtual entries if not set to show them if not self._config[ "features.config.state.showPrivateOptions"] and confOption.startswith( "__"): continue elif not self._config[ "features.config.state.showVirtualOptions"] and confType == "Virtual": continue manEntry = torConfig.getConfigDescription(confOption) self.confContents.append( ConfigEntry(confOption, confType, not confOption in customOptions, manEntry)) self.setSortOrder() # initial sorting of the contents elif self.configType == ARM_STATE: # loaded via the conf utility armConf = conf.getConfig("arm") for key in armConf.getKeys(): pass # TODO: implement
def loadLogMessages(): """ Fetches a mapping of common log messages to their runlevels from the config. """ global COMMON_LOG_MESSAGES armConf = conf.getConfig("arm") COMMON_LOG_MESSAGES = {} for confKey in armConf.getKeys(): if confKey.startswith("msg."): eventType = confKey[4:].upper() messages = armConf.get(confKey, []) COMMON_LOG_MESSAGES[eventType] = messages
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") if configOptionQuery: configOptionLines = configOptionQuery.strip().split("\n") for line in configOptionLines: # lines are of the form "<option> <type>[ <documentation>]", like: # UseEntryGuards Boolean # documentation is aparently only in older versions (for instance, # 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 self._config["features.config.state.showPrivateOptions"] and confOption.startswith("__"): continue elif not self._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.getConfig("arm") for key in armConf.getKeys(): 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 __init__(self, stdscr, configType, config=None): panel.Panel.__init__(self, stdscr, "configState", 0) self.sortOrdering = DEFAULT_SORT_ORDER self._config = dict(DEFAULT_CONFIG) if config: config.update(self._config, { "features.config.selectionDetails.height": 0, "features.config.state.colWidth.option": 5, "features.config.state.colWidth.value": 5}) self.sortOrdering = config.getIntCSV("features.config.order", self.sortOrdering, 3, 0, 6) self.configType = configType self.confContents = [] self.scroller = uiTools.Scroller(True) self.valsLock = threading.RLock() if self.configType == TOR_STATE: conn = torTools.getConn() customOptions = torConfig.getCustomOptions() configOptionLines = conn.getInfo("config/names", "").strip().split("\n") for line in configOptionLines: # lines are of the form "<option> <type>", like: # UseEntryGuards Boolean confOption, confType = line.strip().split(" ", 1) # skips private and virtual entries if not set to show them if not self._config["features.config.state.showPrivateOptions"] and confOption.startswith("__"): continue elif not self._config["features.config.state.showVirtualOptions"] and confType == "Virtual": continue manEntry = torConfig.getConfigDescription(confOption) self.confContents.append(ConfigEntry(confOption, confType, not confOption in customOptions, manEntry)) self.setSortOrder() # initial sorting of the contents elif self.configType == ARM_STATE: # loaded via the conf utility armConf = conf.getConfig("arm") for key in armConf.getKeys(): pass # TODO: implement
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.getConfig("arm").getUnusedKeys(): log.log(CONFIG["log.configEntryUndefined"], "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 msg = "arm started (initialization took %0.3f seconds)" % (time.time() - startTime) log.log(CONFIG["log.startTime"], msg) # main draw loop overrideKey = None # uses this rather than waiting on user input isUnresponsive = False # flag for heartbeat responsiveness check if not torTools.getConn().isAlive(): overrideKey = ord('w') # shows wizard 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: control.prevPage() elif key == ord('p') or key == ord('P'): control.setPaused(not control.isPaused()) elif key == ord('m') or key == ord('M'): cli.menu.menu.showMenu() elif key == ord('q') or key == ord('Q'): # provides prompt to confirm that arm should exit if CONFIG["features.confirmQuit"]: msg = "Are you sure (q again to confirm)?" confirmationKey = cli.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 = cli.popups.showMsg(msg, attr=curses.A_BOLD) if confirmationKey in (ord('x'), ord('X')): try: torTools.getConn().reload() except IOError, exc: log.log( log.ERR, "Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc)) elif key == ord('h') or key == ord('H'): overrideKey = cli.popups.showHelpPopup()
def initController(stdscr, startTime): """ Spawns the controller, and related panels for it. Arguments: stdscr - curses window """ global ARM_CONTROLLER config = conf.getConfig("arm") # initializes the panels stickyPanels = [ cli.headerPanel.HeaderPanel(stdscr, startTime, config), LabelPanel(stdscr) ] pagePanels, firstPagePanels = [], [] # first page: graph and log if CONFIG["features.panels.show.graph"]: firstPagePanels.append(cli.graphing.graphPanel.GraphPanel(stdscr)) if CONFIG["features.panels.show.log"]: expandedEvents = cli.logPanel.expandEvents(CONFIG["startup.events"]) firstPagePanels.append( cli.logPanel.LogPanel(stdscr, expandedEvents, config)) if firstPagePanels: pagePanels.append(firstPagePanels) # second page: connections if not CONFIG["startup.blindModeEnabled"] and CONFIG[ "features.panels.show.connection"]: pagePanels.append( [cli.connections.connPanel.ConnectionPanel(stdscr, config)]) # third page: config if CONFIG["features.panels.show.config"]: pagePanels.append([ cli.configPanel.ConfigPanel(stdscr, cli.configPanel.State.TOR, config) ]) # fourth page: torrc if CONFIG["features.panels.show.torrc"]: pagePanels.append([ cli.torrcPanel.TorrcPanel(stdscr, cli.torrcPanel.Config.TORRC, config) ]) if CONFIG["features.panels.show.interpretor"]: pagePanels.append([cli.interpretorPanel.InterpretorPanel(stdscr)]) # 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 = cli.graphing.bandwidthStats.BandwidthStats(config) graphPanel.addStats(GraphStat.BANDWIDTH, bwStats) graphPanel.addStats(GraphStat.SYSTEM_RESOURCES, cli.graphing.resourceStats.ResourceStats()) if not CONFIG["startup.blindModeEnabled"]: graphPanel.addStats(GraphStat.CONNECTIONS, cli.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() if isSuccessful: graphPanel.updateInterval = 4
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # initializes interface configs config = conf.getConfig("arm") config.update(CONFIG, { "features.redrawRate": 1, "features.refreshRate": 0 }) cli.graphing.graphPanel.loadConfig(config) cli.connections.connEntry.loadConfig(config) cli.wizard.loadConfig(config) # 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.getMyPid() if not torPid and conn.isAlive(): msg = "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable." log.log(CONFIG["log.unknownTorPid"], msg) # 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"]: 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 = sysTools.getProcessName(torPid, "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 = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.log( CONFIG["log.torEventTypeUnrecognized"], "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 KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (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.getConfig("arm").getUnusedKeys(): log.log(CONFIG["log.configEntryUndefined"], "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 msg = "arm started (initialization took %0.3f seconds)" % (time.time() - startTime) log.log(CONFIG["log.startTime"], msg) # main draw loop overrideKey = None # uses this rather than waiting on user input isUnresponsive = False # flag for heartbeat responsiveness check if not torTools.getConn().isAlive(): overrideKey = ord('w') # shows wizard 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: control.prevPage() elif key == ord('p') or key == ord('P'): control.setPaused(not control.isPaused()) elif key == ord('m') or key == ord('M'): cli.menu.menu.showMenu() elif key == ord('q') or key == ord('Q'): # provides prompt to confirm that arm should exit if CONFIG["features.confirmQuit"]: msg = "Are you sure (q again to confirm)?" confirmationKey = cli.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 = cli.popups.showMsg(msg, attr = curses.A_BOLD) if confirmationKey in (ord('x'), ord('X')): try: torTools.getConn().reload() except IOError, exc: log.log(log.ERR, "Error detected when reloading tor: %s" % sysTools.getFileErrorMsg(exc)) elif key == ord('h') or key == ord('H'): overrideKey = cli.popups.showHelpPopup()
def initController(stdscr, startTime): """ Spawns the controller, and related panels for it. Arguments: stdscr - curses window """ global ARM_CONTROLLER config = conf.getConfig("arm") # initializes the panels stickyPanels = [cli.headerPanel.HeaderPanel(stdscr, startTime, config), LabelPanel(stdscr)] pagePanels, firstPagePanels = [], [] # first page: graph and log if CONFIG["features.panels.show.graph"]: firstPagePanels.append(cli.graphing.graphPanel.GraphPanel(stdscr)) if CONFIG["features.panels.show.log"]: expandedEvents = cli.logPanel.expandEvents(CONFIG["startup.events"]) firstPagePanels.append(cli.logPanel.LogPanel(stdscr, expandedEvents, config)) if firstPagePanels: pagePanels.append(firstPagePanels) # second page: connections if not CONFIG["startup.blindModeEnabled"] and CONFIG["features.panels.show.connection"]: pagePanels.append([cli.connections.connPanel.ConnectionPanel(stdscr, config)]) # third page: config if CONFIG["features.panels.show.config"]: pagePanels.append([cli.configPanel.ConfigPanel(stdscr, cli.configPanel.State.TOR, config)]) # fourth page: torrc if CONFIG["features.panels.show.torrc"]: pagePanels.append([cli.torrcPanel.TorrcPanel(stdscr, cli.torrcPanel.Config.TORRC, config)]) if CONFIG["features.panels.show.interpretor"]: pagePanels.append([cli.interpretorPanel.InterpretorPanel(stdscr)]) # 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 = cli.graphing.bandwidthStats.BandwidthStats(config) graphPanel.addStats(GraphStat.BANDWIDTH, bwStats) graphPanel.addStats(GraphStat.SYSTEM_RESOURCES, cli.graphing.resourceStats.ResourceStats()) if not CONFIG["startup.blindModeEnabled"]: graphPanel.addStats(GraphStat.CONNECTIONS, cli.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() if isSuccessful: graphPanel.updateInterval = 4
def startTorMonitor(startTime): """ Initializes the interface and starts the main draw loop. Arguments: startTime - unix time for when arm was started """ # initializes interface configs config = conf.getConfig("arm") config.update(CONFIG, { "features.redrawRate": 1, "features.refreshRate": 0}) cli.graphing.graphPanel.loadConfig(config) cli.connections.connEntry.loadConfig(config) cli.wizard.loadConfig(config) # 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.getMyPid() if not torPid and conn.isAlive(): msg = "Unable to determine Tor's pid. Some information, like its resource usage will be unavailable." log.log(CONFIG["log.unknownTorPid"], msg) # 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"]: 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 = sysTools.getProcessName(torPid, "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 = cli.logPanel.getMissingEventTypes() if missingEventTypes: pluralLabel = "s" if len(missingEventTypes) > 1 else "" log.log(CONFIG["log.torEventTypeUnrecognized"], "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 KeyboardInterrupt: # Skip printing stack trace in case of keyboard interrupt. The # HALT_ACTIVITY attempts to prevent daemons from triggering a curses redraw # (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 getRefreshRate(self): # provides the rate at which the panel has new stats to display if self._config["features.graph.ps.cachedOnly"]: return int(conf.getConfig("arm").get("queries.ps.rate", 5)) else: return 1
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.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.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(" ") if isMultiline: # part of a multiline entry started on a previous line so everything # is part of the argument lineComp["argument"][0] = lineText elif optionIndex == -1: # no argument provided lineComp["option"][0] = lineText else: optionText = strippedLine[:optionIndex] 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") elif lineIssue == torConfig.ValidationError.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.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 if lineOffset == maxLinesPerEntry - 1: msg = uiTools.cropStr(msg, maxMsgSize) else: 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()
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 = 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.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.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.showLabel: sourceLabel = "Tor" if self.configType == 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(" ") if isMultiline: # part of a multiline entry started on a previous line so everything # is part of the argument lineComp["argument"][0] = lineText elif optionIndex == -1: # no argument provided lineComp["option"][0] = lineText else: optionText = strippedLine[:optionIndex] 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.VAL_DUPLICATE, torConfig.VAL_IS_DEFAULT): lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue") lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue") elif lineIssue == torConfig.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.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 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()