[or-cvs] r19629: {arm} Offloaded resizing to the curses wrapper and switched from a (arm/trunk/interface)

atagar at seul.org atagar at seul.org
Fri Jun 5 08:54:07 UTC 2009


Author: atagar
Date: 2009-06-05 04:54:07 -0400 (Fri, 05 Jun 2009)
New Revision: 19629

Modified:
   arm/trunk/interface/bandwidthPanel.py
   arm/trunk/interface/controller.py
   arm/trunk/interface/logPanel.py
   arm/trunk/interface/staticPanel.py
   arm/trunk/interface/util.py
Log:
Offloaded resizing to the curses wrapper and switched from a functional to an OO implementation which further simplified the controller. As an added plus this should make adding additional 'pages' trivial.
Also dealt with another curses wtf bug where panels wouldn't repaint unless done in a specific order.


Modified: arm/trunk/interface/bandwidthPanel.py
===================================================================
--- arm/trunk/interface/bandwidthPanel.py	2009-06-05 01:31:16 UTC (rev 19628)
+++ arm/trunk/interface/bandwidthPanel.py	2009-06-05 08:54:07 UTC (rev 19629)
@@ -12,31 +12,17 @@
 BANDWIDTH_GRAPH_COLOR_DL = "green"  # download section color
 BANDWIDTH_GRAPH_COLOR_UL = "cyan"   # upload section color
 
-def drawBandwidthLabel(scr, staticInfo):
-  """ Draws bandwidth label text (drops stats if not enough room). """
-  scr.clear()
-  maxX = scr.maxX
-  
-  rateLabel = util.getSizeLabel(int(staticInfo["BandwidthRate"]))
-  burstLabel = util.getSizeLabel(int(staticInfo["BandwidthBurst"]))
-  labelContents = "Bandwidth (cap: %s, burst: %s):" % (rateLabel, burstLabel)
-  if maxX < len(labelContents):
-    labelContents = "%s):" % labelContents[:labelContents.find(",")]  # removes burst measure
-    if maxX < len(labelContents): labelContents = "Bandwidth:"           # removes both
-  
-  scr.addstr(0, 0, labelContents, util.LABEL_ATTR)
-  scr.refresh()
-
-class BandwidthMonitor(TorCtl.PostEventListener):
+class BandwidthMonitor(TorCtl.PostEventListener, util.Panel):
   """
   Tor event listener, taking bandwidth sampling and drawing bar graph. This is
   updated every second by the BW events and graph samples are spaced at
   BANDWIDTH_GRAPH_SAMPLES second intervals.
   """
   
-  def __init__(self, scr):
+  def __init__(self, lock, conn):
     TorCtl.PostEventListener.__init__(self)
-    self.scr = scr                # associated subwindow
+    util.Panel.__init__(self, lock, 9)
+    self.conn = conn              # Tor control port connection
     self.tick = 0                 # number of updates performed
     self.lastDownloadRate = 0     # most recently sampled rates
     self.lastUploadRate = 0
@@ -48,6 +34,13 @@
     # graphed download (read) and upload (write) rates - first index accumulator
     self.downloadRates = [0] * (BANDWIDTH_GRAPH_COL + 1)
     self.uploadRates = [0] * (BANDWIDTH_GRAPH_COL + 1)
+    
+    # retrieves static stats for label
+    if conn:
+      bwStats = conn.get_option(['BandwidthRate', 'BandwidthBurst'])
+      self.bwRate = util.getSizeLabel(int(bwStats[0][1]))
+      self.bwBurst = util.getSizeLabel(int(bwStats[1][1]))
+    else: self.bwRate, self.bwBurst = -1, -1
   
   def bandwidth_event(self, event):
     if self.isPaused: self.pauseBuffer.bandwidth_event(event)
@@ -68,45 +61,53 @@
         self.uploadRates.insert(0, 0)
         del self.uploadRates[BANDWIDTH_GRAPH_COL + 1:]
       
-      self.refreshDisplay()
+      self.redraw()
   
-  def refreshDisplay(self):
+  def redraw(self):
     """ Redraws bandwidth panel. """
     # doesn't draw if headless (indicating that the instance is for a pause buffer)
-    if self.scr:
-      if not self.scr.lock.acquire(False): return
+    if self.win:
+      if not self.lock.acquire(False): return
       try:
-        self.scr.clear()
+        self.clear()
         dlColor = util.getColor(BANDWIDTH_GRAPH_COLOR_DL)
         ulColor = util.getColor(BANDWIDTH_GRAPH_COLOR_UL)
         
+        # draws label, dropping stats if there's not enough room
+        labelContents = "Bandwidth (cap: %s, burst: %s):" % (self.bwRate, self.bwBurst)
+        if self.maxX < len(labelContents):
+          labelContents = "%s):" % labelContents[:labelContents.find(",")]  # removes burst measure
+          if self.maxX < len(labelContents): labelContents = "Bandwidth:"   # removes both
+        
+        self.addstr(0, 0, labelContents, util.LABEL_ATTR)
+        
         # current numeric measures
-        self.scr.addstr(0, 0, "Downloaded (%s/sec):" % util.getSizeLabel(self.lastDownloadRate), curses.A_BOLD | dlColor)
-        self.scr.addstr(0, 35, "Uploaded (%s/sec):" % util.getSizeLabel(self.lastUploadRate), curses.A_BOLD | ulColor)
+        self.addstr(1, 0, "Downloaded (%s/sec):" % util.getSizeLabel(self.lastDownloadRate), curses.A_BOLD | dlColor)
+        self.addstr(1, 35, "Uploaded (%s/sec):" % util.getSizeLabel(self.lastUploadRate), curses.A_BOLD | ulColor)
         
         # graph bounds in KB (uses highest recorded value as max)
-        self.scr.addstr(1, 0, "%4s" % str(self.maxDownloadRate / 1024 / BANDWIDTH_GRAPH_SAMPLES), dlColor)
-        self.scr.addstr(6, 0, "   0", dlColor)
+        self.addstr(2, 0, "%4s" % str(self.maxDownloadRate / 1024 / BANDWIDTH_GRAPH_SAMPLES), dlColor)
+        self.addstr(7, 0, "   0", dlColor)
         
-        self.scr.addstr(1, 35, "%4s" % str(self.maxUploadRate / 1024 / BANDWIDTH_GRAPH_SAMPLES), ulColor)
-        self.scr.addstr(6, 35, "   0", ulColor)
+        self.addstr(2, 35, "%4s" % str(self.maxUploadRate / 1024 / BANDWIDTH_GRAPH_SAMPLES), ulColor)
+        self.addstr(7, 35, "   0", ulColor)
         
         # creates bar graph of bandwidth usage over time
         for col in range(BANDWIDTH_GRAPH_COL):
           bytesDownloaded = self.downloadRates[col + 1]
           colHeight = min(5, 5 * bytesDownloaded / self.maxDownloadRate)
           for row in range(colHeight):
-            self.scr.addstr(6 - row, col + 5, " ", curses.A_STANDOUT | dlColor)
+            self.addstr(7 - row, col + 5, " ", curses.A_STANDOUT | dlColor)
         
         for col in range(BANDWIDTH_GRAPH_COL):
           bytesUploaded = self.uploadRates[col + 1]
           colHeight = min(5, 5 * bytesUploaded / self.maxUploadRate)
           for row in range(colHeight):
-            self.scr.addstr(6 - row, col + 40, " ", curses.A_STANDOUT | ulColor)
+            self.addstr(7 - row, col + 40, " ", curses.A_STANDOUT | ulColor)
         
-        self.scr.refresh()
+        self.refresh()
       finally:
-        self.scr.lock.release()
+        self.lock.release()
   
   def setPaused(self, isPause):
     """
@@ -117,7 +118,7 @@
     
     self.isPaused = isPause
     if self.isPaused:
-      if self.pauseBuffer == None: self.pauseBuffer = BandwidthMonitor(None)
+      if self.pauseBuffer == None: self.pauseBuffer = BandwidthMonitor(None, None)
       
       self.pauseBuffer.tick = self.tick
       self.pauseBuffer.lastDownloadRate = self.lastDownloadRate
@@ -134,5 +135,5 @@
       self.maxUploadRate = self.pauseBuffer.maxUploadRate
       self.downloadRates = self.pauseBuffer.downloadRates
       self.uploadRates = self.pauseBuffer.uploadRates
-      self.refreshDisplay()
+      self.redraw()
 

Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py	2009-06-05 01:31:16 UTC (rev 19628)
+++ arm/trunk/interface/controller.py	2009-06-05 08:54:07 UTC (rev 19629)
@@ -18,31 +18,40 @@
 
 REFRESH_RATE = 5        # seconds between redrawing screen
 cursesLock = RLock()    # global curses lock (curses isn't thread safe and
-                        # concurrency bugs produce especially sinister glitches
+                        # concurrency bugs produce especially sinister glitches)
 
-CTL_HELP, CTL_PAUSED, CTL_EVENT_INPUT, CTL_EVENT_ERR = range(4) # enums for message in control label
+# enums for message in control label
+CTL_HELP, CTL_PAUSED, CTL_EVENT_INPUT, CTL_EVENT_ERR = range(4)
 
-# mapping of panels to (height, start y), -1 if unlimited
-PANEL_INFO = {
-  "summary":        (6, 0),     # top static content
-  "control":        (1, 6),     # line for user input
-  "bandwidthLabel": (1, 7),     # bandwidth section label
-  "bandwidth":      (8, 8),     # bandwidth measurements / graph
-  "logLabel":       (1, 16),    # message log label
-  "log":            (-1, 17)}   # uses all remaining space for message log
+# panel order per page
+PAGE_1 = ["summary", "control", "bandwidth", "log"]
+# TODO: page 2: configuration information
+# TODO: page 3: current connections
 
-def drawControlLabel(scr, msgType, arg=""):
+class ControlPanel(util.Panel):
   """ Draws single line label for interface controls. """
-  scr.clear()
   
-  if msgType == CTL_HELP: scr.addstr(0, 0, "q: quit, e: change events, p: pause")
-  elif msgType == CTL_PAUSED: scr.addstr(0, 0, "Paused", curses.A_STANDOUT)
-  elif msgType == CTL_EVENT_INPUT: scr.addstr(0, 0, "Events to log: ")
-  elif msgType == CTL_EVENT_ERR: scr.addstr(0, 0, arg, curses.A_STANDOUT)
-  else:
-    assert False, "Unrecognized event type for control label: " + str(msgType)
+  def __init__(self, lock):
+    util.Panel.__init__(self, lock, 1)
+    self.msgType = CTL_HELP
+    self.arg = ""
   
-  scr.refresh()
+  def setMsg(self, msgType, arg=""):
+    self.msgType = msgType
+    self.arg = arg
+  
+  def redraw(self):
+    if self.win:
+      self.clear()
+      
+      if self.msgType == CTL_HELP: self.addstr(0, 0, "q: quit, e: change events, p: pause")
+      elif self.msgType == CTL_PAUSED: self.addstr(0, 0, "Paused", curses.A_STANDOUT)
+      elif self.msgType == CTL_EVENT_INPUT: self.addstr(0, 0, "Events to log: ")
+      elif self.msgType == CTL_EVENT_ERR: self.addstr(0, 0, self.arg, curses.A_STANDOUT)
+      else:
+        assert False, "Unrecognized event type for control label: " + str(self.msgType)
+      
+      self.refresh()
 
 def setEventListening(loggedEvents, conn, logListener):
   """
@@ -81,45 +90,6 @@
   loggedEvents.sort() # alphabetizes
   return loggedEvents
 
-def refreshSubwindows(stdscr, panels={}):
-  """
-  Creates constituent parts of the display. Any subwindows that have been
-  displaced are recreated to take advantage of the maximum bounds.
-  """
-  
-  y, x = stdscr.getmaxyx()
-  
-  if panels == {}:
-    # initializes subwindows - upper left must be a valid coordinate
-    for panelName, (maxHeight, startY) in PANEL_INFO.items():
-      if maxHeight == -1: height = max(1, y - startY)
-      else: height = max(1, min(maxHeight, y - startY))
-      startY = min(startY, y - 1)
-      panels[panelName] = util.TermSubwindow(stdscr.subwin(height, x, startY, 0), cursesLock, startY)
-  else:
-    # 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 entirly which 
-    # would mean more complicated code and no more selective refreshing)
-    
-    for panelName, (maxHeight, startY) in PANEL_INFO.items():
-      if startY > y: continue # out of bounds - ignore
-      panelSrc = panels[panelName]
-      currentY, currentX = panelSrc.win.getparyx()
-      currentHeight, currentWidth = panelSrc.win.getmaxyx()
-      
-      # figure out panel can grow - if so recreate
-      if maxHeight == -1: height = max(1, y - startY)
-      else: height = max(1, min(maxHeight, y - startY))
-      startY = min(startY, y - 1)
-      
-      if currentY < startY or currentHeight < height:
-        panels[panelName].win = stdscr.subwin(height, x, startY, 0)
-  
-  return panels
-
 def drawTorMonitor(stdscr, conn, loggedEvents):
   """
   Starts arm interface reflecting information on provided control port.
@@ -138,18 +108,21 @@
   try: curses.curs_set(0)
   except curses.error: pass
   
-  panels = refreshSubwindows(stdscr)
   staticInfo = staticPanel.getStaticInfo(conn)
+  panels = {
+    "summary": staticPanel.SummaryPanel(cursesLock, staticInfo),
+    "control": ControlPanel(cursesLock),
+    "bandwidth": bandwidthPanel.BandwidthMonitor(cursesLock, conn),
+    "log": logPanel.LogMonitor(cursesLock, loggedEvents)}
   
   # listeners that update bandwidth and log panels with Tor status
-  logListener = logPanel.LogMonitor(panels["log"], "BW" in loggedEvents, "UNKNOWN" in loggedEvents)
-  conn.add_event_listener(logListener)
+  conn.add_event_listener(panels["log"])
+  conn.add_event_listener(panels["bandwidth"])
   
-  bandwidthListener = bandwidthPanel.BandwidthMonitor(panels["bandwidth"])
-  conn.add_event_listener(bandwidthListener)
+  # tells Tor to listen to the events we're interested
+  loggedEvents = setEventListening(loggedEvents, conn, panels["log"])
+  panels["log"].loggedEvents = loggedEvents # strips any that couldn't be set
   
-  loggedEvents = setEventListening(loggedEvents, conn, logListener)
-  eventsListing = ", ".join(loggedEvents)
   oldY, oldX = -1, -1
   isUnresponsive = False    # true if it's been over five seconds since the last BW event (probably due to Tor closing)
   isPaused = False          # if true updates are frozen
@@ -162,28 +135,24 @@
     cursesLock.acquire()
     try:
       y, x = stdscr.getmaxyx()
-      if y > oldY: panels = refreshSubwindows(stdscr, panels)
+      if y > oldY:
+        # gives panels a chance to take advantage of the maximum bounds
+        startY = 0
+        for panelKey in PAGE_1:
+          panels[panelKey].recreate(stdscr, startY)
+          startY += panels[panelKey].height
       
       # if it's been at least five seconds since the last BW event Tor's probably done
-      if not isUnresponsive and logListener.getHeartbeat() >= 5:
+      if not isUnresponsive and panels["log"].getHeartbeat() >= 5:
         isUnresponsive = True
-        logListener.monitor_event("NOTICE", "Relay unresponsive (last heartbeat: %s)" % time.ctime(logListener.lastHeartbeat))
-      elif isUnresponsive and logListener.getHeartbeat() < 5:
+        panels["log"].monitor_event("NOTICE", "Relay unresponsive (last heartbeat: %s)" % time.ctime(panels["log"].lastHeartbeat))
+      elif isUnresponsive and panels["log"].getHeartbeat() < 5:
         # this really shouldn't happen - BW events happen every second...
         isUnresponsive = False
-        logListener.monitor_event("WARN", "Relay resumed")
+        panels["log"].monitor_event("WARN", "Relay resumed")
       
-      staticPanel.drawSummary(panels["summary"], staticInfo)
-      
-      msgType = CTL_PAUSED if isPaused else CTL_HELP
-      drawControlLabel(panels["control"], msgType)
-      
-      bandwidthPanel.drawBandwidthLabel(panels["bandwidthLabel"], staticInfo)
-      bandwidthListener.refreshDisplay()
-      
-      logPanel.drawEventLogLabel(panels["logLabel"], eventsListing)
-      logListener.refreshDisplay()
-      
+      # I haven't the foggiest why, but doesn't work if redrawn out of order...
+      for panelKey in PAGE_1: panels[panelKey].redraw()
       oldY, oldX = y, x
       stdscr.refresh()
     finally:
@@ -198,13 +167,14 @@
       try:
         # pauses listeners so events can still be handed (otherwise they wait
         # on curses lock which might get demanding if the user takes their time)
-        isBwPaused = bandwidthListener.isPaused
-        isLogPaused = logListener.isPaused
-        bandwidthListener.setPaused(True)
-        logListener.setPaused(True)
+        isBwPaused = panels["bandwidth"].isPaused
+        isLogPaused = panels["log"].isPaused
+        panels["bandwidth"].setPaused(True)
+        panels["log"].setPaused(True)
         
         # provides prompt
-        drawControlLabel(panels["control"], CTL_EVENT_INPUT)
+        panels["control"].setMsg(CTL_EVENT_INPUT)
+        panels["control"].redraw()
         
         # makes cursor and typing visible
         try: curses.curs_set(1)
@@ -212,24 +182,20 @@
         curses.echo()
         
         # switches bandwidth area to list event types
-        panels["bandwidthLabel"].clear()
-        panels["bandwidthLabel"].addstr(0, 0, "Event Types:", util.LABEL_ATTR)
-        panels["bandwidthLabel"].refresh()
-        
-        panels["bandwidth"].clear()
-        lineNum = 0
+        bwPanel = panels["bandwidth"]
+        bwPanel.clear()
+        bwPanel.addstr(0, 0, "Event Types:", util.LABEL_ATTR)
+        lineNum = 1
         for line in logPanel.EVENT_LISTING.split("\n"):
           line = line.strip()
-          panels["bandwidth"].addstr(lineNum, 0, line[:x - 1])
+          bwPanel.addstr(lineNum, 0, line[:x - 1])
           lineNum += 1
-        panels["bandwidth"].refresh()
+        bwPanel.refresh()
         
         # gets user input (this blocks monitor updates)
         eventsInput = panels["control"].win.getstr(0, 15)
+        eventsInput = eventsInput.replace(' ', '') # strips spaces
         
-        # strips spaces
-        eventsInput = eventsInput.replace(' ', '')
-        
         # reverts visability settings
         try: curses.curs_set(0)
         except curses.error: pass
@@ -239,18 +205,19 @@
         if eventsInput != "":
           try:
             expandedEvents = logPanel.expandEvents(eventsInput)
-            logListener.includeBW = "BW" in expandedEvents
-            logListener.includeUnknown = "UNKNOWN" in expandedEvents
-            
-            loggedEvents = setEventListening(expandedEvents, conn, logListener)
-            eventsListing = ", ".join(loggedEvents)
+            loggedEvents = setEventListening(expandedEvents, conn, panels["log"])
+            panels["log"].loggedEvents = loggedEvents
           except ValueError, exc:
-            drawControlLabel(panels["control"], CTL_EVENT_ERR, "Invalid flags: %s" % str(exc))
+            panels["control"].setMsg(CTL_EVENT_ERR, "Invalid flags: %s" % str(exc))
+            panels["control"].redraw()
             time.sleep(2)
         
+        msgType = CTL_PAUSED if isPaused else CTL_HELP
+        panels["control"].setMsg(msgType)
+        
         # returns listeners to previous pause status
-        bandwidthListener.setPaused(isBwPaused)
-        logListener.setPaused(isLogPaused)
+        panels["bandwidth"].setPaused(isBwPaused)
+        panels["log"].setPaused(isLogPaused)
       finally:
         cursesLock.release()
     elif key == ord('p') or key == ord('P'):
@@ -258,10 +225,10 @@
       cursesLock.acquire()
       try:
         isPaused = not isPaused
-        logListener.setPaused(isPaused)
-        bandwidthListener.setPaused(isPaused)
+        panels["log"].setPaused(isPaused)
+        panels["bandwidth"].setPaused(isPaused)
         msgType = CTL_PAUSED if isPaused else CTL_HELP
-        drawControlLabel(panels["control"], msgType)
+        panels["control"].setMsg(msgType)
       finally:
         cursesLock.release()
 

Modified: arm/trunk/interface/logPanel.py
===================================================================
--- arm/trunk/interface/logPanel.py	2009-06-05 01:31:16 UTC (rev 19628)
+++ arm/trunk/interface/logPanel.py	2009-06-05 08:54:07 UTC (rev 19629)
@@ -58,46 +58,18 @@
   if invalidFlags: raise ValueError(invalidFlags)
   else: return expandedEvents
 
-def drawEventLogLabel(scr, eventsListing):
+class LogMonitor(TorCtl.PostEventListener, util.Panel):
   """
-  Draws single line label for event log. Uses ellipsis if too long, for instance:
-  Events (DEBUG, INFO, NOTICE, WARN...):
+  Tor event listener, noting messages, the time, and their type in a panel.
   """
-  scr.clear()
-  maxX = scr.maxX
   
-  eventsLabel = "Events"
-  
-  firstLabelLen = eventsListing.find(", ")
-  if firstLabelLen == -1: firstLabelLen = len(eventsListing)
-  else: firstLabelLen += 3
-  
-  if maxX > 10 + firstLabelLen:
-    eventsLabel += " ("
-    if len(eventsListing) > maxX - 11:
-      labelBreak = eventsListing[:maxX - 12].rfind(", ")
-      eventsLabel += "%s..." % eventsListing[:labelBreak]
-    else: eventsLabel += eventsListing
-    eventsLabel += ")"
-  eventsLabel += ":"
-  
-  scr.addstr(0, 0, eventsLabel, util.LABEL_ATTR)
-  scr.refresh()
-
-class LogMonitor(TorCtl.PostEventListener):
-  """
-  Tor event listener, noting messages, the time, and their type in a curses
-  subwindow.
-  """
-  
-  def __init__(self, scr, includeBW, includeUnknown):
+  def __init__(self, lock, loggedEvents):
     TorCtl.PostEventListener.__init__(self)
-    self.scr = scr                        # associated subwindow
+    util.Panel.__init__(self, lock, -1)
     self.msgLog = []                      # tuples of (logText, color)
     self.isPaused = False
     self.pauseBuffer = []                 # location where messages are buffered if paused
-    self.includeBW = includeBW            # true if we're supposed to listen for BW events
-    self.includeUnknown = includeUnknown  # true if registering unrecognized events
+    self.loggedEvents = loggedEvents      # events we're listening to
     self.lastHeartbeat = time.time()      # time of last BW event
   
   # Listens for all event types and redirects to registerEvent
@@ -133,7 +105,7 @@
   
   def bandwidth_event(self, event):
     self.lastHeartbeat = time.time()
-    if self.includeBW: self.registerEvent("BW", "READ: %i, WRITTEN: %i" % (event.read, event.written), "cyan")
+    if "BW" in self.loggedEvents: self.registerEvent("BW", "READ: %i, WRITTEN: %i" % (event.read, event.written), "cyan")
   
   def msg_event(self, event):
     self.registerEvent(event.level, event.msg, RUNLEVEL_EVENT_COLOR[event.level])
@@ -160,7 +132,7 @@
     self.registerEvent("NEWCONSENSUS", "Listed (%i): %s" % (len(event.nslist), msg), "magenta")
   
   def unknown_event(self, event):
-    if self.includeUnknown: self.registerEvent("UNKNOWN", event.event_string, "red")
+    if "UNKNOWN" in self.loggedEvents: self.registerEvent("UNKNOWN", event.event_string, "red")
   
   def monitor_event(self, level, msg):
     # events provided by the arm monitor - types use the same as runlevel
@@ -183,36 +155,57 @@
     else:
       self.msgLog.insert(0, (msgLine, color))
       if len(self.msgLog) > MAX_LOG_ENTRIES: del self.msgLog[MAX_LOG_ENTRIES:]
-      self.refreshDisplay()
+      self.redraw()
   
-  def refreshDisplay(self):
+  def redraw(self):
     """
     Redraws message log. Entries stretch to use available space and may
     contain up to two lines. Starts with newest entries.
     """
     
-    if self.scr:
-      if not self.scr.lock.acquire(False): return
+    if self.win:
+      if not self.lock.acquire(False): return
       try:
-        self.scr.clear()
-        x, y = self.scr.maxX, self.scr.maxY
-        lineCount = 0
+        self.clear()
         
+        # draws label - uses ellipsis if too long, for instance:
+        # Events (DEBUG, INFO, NOTICE, WARN...):
+        eventsLabel = "Events"
+        eventsListing = ", ".join(self.loggedEvents)
+        
+        firstLabelLen = eventsListing.find(", ")
+        if firstLabelLen == -1: firstLabelLen = len(eventsListing)
+        else: firstLabelLen += 3
+        
+        if self.maxX > 10 + firstLabelLen:
+          eventsLabel += " ("
+          if len(eventsListing) > self.maxX - 11:
+            labelBreak = eventsListing[:self.maxX - 12].rfind(", ")
+            eventsLabel += "%s..." % eventsListing[:labelBreak]
+          else: eventsLabel += eventsListing
+          eventsLabel += ")"
+        eventsLabel += ":"
+        
+        self.addstr(0, 0, eventsLabel, util.LABEL_ATTR)
+        
+        # log entries
+        lineCount = 1
+        
         for (line, color) in self.msgLog:
           # splits over too lines if too long
-          if len(line) < x:
-            self.scr.addstr(lineCount, 0, line, util.getColor(color))
+          if len(line) < self.maxX:
+            self.addstr(lineCount, 0, line, util.getColor(color))
             lineCount += 1
           else:
-            (line1, line2) = self._splitLine(line, x)
-            self.scr.addstr(lineCount, 0, line1, util.getColor(color))
-            self.scr.addstr(lineCount + 1, 0, line2, util.getColor(color))
+            (line1, line2) = self._splitLine(line, self.maxX)
+            self.addstr(lineCount, 0, line1, util.getColor(color))
+            self.addstr(lineCount + 1, 0, line2, util.getColor(color))
             lineCount += 2
           
-          if lineCount >= y: break # further log messages wouldn't fit
-        self.scr.refresh()
+          if lineCount >= self.maxY: break # further log messages wouldn't fit
+        self.refresh()
       finally:
-        self.scr.lock.release()
+        self.lock.release()
   
   def setPaused(self, isPause):
     """
@@ -225,7 +218,7 @@
     if self.isPaused: self.pauseBuffer = []
     else:
       self.msgLog = (self.pauseBuffer + self.msgLog)[:MAX_LOG_ENTRIES]
-      self.refreshDisplay()
+      self.redraw()
   
   def getHeartbeat(self):
     """

Modified: arm/trunk/interface/staticPanel.py
===================================================================
--- arm/trunk/interface/staticPanel.py	2009-06-05 01:31:16 UTC (rev 19628)
+++ arm/trunk/interface/staticPanel.py	2009-06-05 08:54:07 UTC (rev 19629)
@@ -14,12 +14,12 @@
   corresponding string values. Keys include:
   info - version, config-file, address, fingerprint
   sys - sys-name, sys-os, sys-version
-  config - Nickname, ORPort, DirPort, ControlPort, ExitPolicy, BandwidthRate, BandwidthBurst
-  config booleans - IsPasswordAuthSet, IsCookieAuthSet
+  config - Nickname, ORPort, DirPort, ControlPort, ExitPolicy
+  config booleans - IsPasswordAuthSet, IsCookieAuthSet, IsAccountingEnabled
   """
   
   vals = conn.get_info(["version", "config-file"])
-  
+ 
   # gets parameters that throw errors if unavailable
   for param in ["address", "fingerprint"]:
     try:
@@ -34,16 +34,18 @@
   vals["sys-version"] = unameVals[2]
   
   # parameters from the user's torrc
-  configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy", "BandwidthRate", "BandwidthBurst"]
+  configFields = ["Nickname", "ORPort", "DirPort", "ControlPort", "ExitPolicy"]
   vals.update(dict([(key, conn.get_option(key)[0][1]) for key in configFields]))
   
   # simply keeps booleans for if authentication info is set
   vals["IsPasswordAuthSet"] = not conn.get_option("HashedControlPassword")[0][1] == None
   vals["IsCookieAuthSet"] = conn.get_option("CookieAuthentication")[0][1] == "1"
   
+  vals["IsAccountingEnabled"] = conn.get_info('accounting/enabled')['accounting/enabled'] == "1"
+  
   return vals
 
-def drawSummary(scr, vals):
+class SummaryPanel(util.Panel):
   """
   Draws top area containing static information.
   
@@ -61,44 +63,52 @@
   Exit Policy: reject *:*
   """
   
-  # extra erase/refresh is needed to avoid internal caching screwing up and
-  # refusing to redisplay content in the case of graphical glitches - probably
-  # an obscure curses bug...
-  scr.win.erase()
-  scr.win.refresh()
+  def __init__(self, lock, vals):
+    util.Panel.__init__(self, lock, 6)
+    self.vals = vals          # mapping of information to be presented
   
-  scr.clear()
-  
-  # Line 1
-  scr.addstr(0, 0, "arm - %s (%s %s)" % (vals["sys-name"], vals["sys-os"], vals["sys-version"]))
-  scr.addstr(0, 45, "Tor %s" % vals["version"])
-  
-  # Line 2 (authentication label red if open, green if credentials required)
-  dirPortLabel = "Dir Port: %s, " % vals["DirPort"] if not vals["DirPort"] == None else ""
-  
-  # TODO: if both cookie and password are set then which takes priority?
-  if vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
-  elif vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
-  else: controlPortAuthLabel = "open"
-  controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
-  
-  labelStart = "%s - %s:%s, %sControl Port (" % (vals["Nickname"], vals["address"], vals["ORPort"], dirPortLabel)
-  scr.addstr(1, 0, labelStart)
-  xLoc = len(labelStart)
-  scr.addstr(1, xLoc, controlPortAuthLabel, util.getColor(controlPortAuthColor))
-  xLoc += len(controlPortAuthLabel)
-  scr.addstr(1, xLoc, "): %s" % vals["ControlPort"])
-  
-  # Lines 3-5
-  scr.addstr(2, 0, "Fingerprint: %s" % vals["fingerprint"])
-  scr.addstr(3, 0, "Config: %s" % vals["config-file"])
-  exitPolicy = vals["ExitPolicy"]
-  
-  # adds note when default exit policy is appended
-  if exitPolicy == None: exitPolicy = "<default>"
-  elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
-    exitPolicy += ", <default>"
-  scr.addstr(4, 0, "Exit Policy: %s" % exitPolicy)
-  
-  scr.refresh()
+  def redraw(self):
+    i = 1
+    
+    if self.win:
+      # extra erase/refresh is needed to avoid internal caching screwing up and
+      # refusing to redisplay content in the case of graphical glitches - probably
+      # an obscure curses bug...
+      self.win.erase()
+      self.win.refresh()
+      
+      self.clear()
+      
+      # Line 1
+      self.addstr(0, 0, "arm - %s (%s %s)" % (self.vals["sys-name"], self.vals["sys-os"], self.vals["sys-version"]))
+      self.addstr(0, 45, "Tor %s" % self.vals["version"])
+      
+      # Line 2 (authentication label red if open, green if credentials required)
+      dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if not self.vals["DirPort"] == None else ""
+      
+      # TODO: if both cookie and password are set then which takes priority?
+      if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
+      elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
+      else: controlPortAuthLabel = "open"
+      controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+      
+      labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
+      self.addstr(1, 0, labelStart)
+      xLoc = len(labelStart)
+      self.addstr(1, xLoc, controlPortAuthLabel, util.getColor(controlPortAuthColor))
+      xLoc += len(controlPortAuthLabel)
+      self.addstr(1, xLoc, "): %s" % self.vals["ControlPort"])
+      
+      # Lines 3-5
+      self.addstr(2, 0, "Fingerprint: %s" % self.vals["fingerprint"])
+      self.addstr(3, 0, "Config: %s" % self.vals["config-file"])
+      exitPolicy = self.vals["ExitPolicy"]
+      
+      # adds note when default exit policy is appended
+      if exitPolicy == None: exitPolicy = "<default>"
+      elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
+        exitPolicy += ", <default>"
+      self.addstr(4, 0, "Exit Policy: %s" % exitPolicy)
+      
+      self.refresh()
 

Modified: arm/trunk/interface/util.py
===================================================================
--- arm/trunk/interface/util.py	2009-06-05 01:31:16 UTC (rev 19628)
+++ arm/trunk/interface/util.py	2009-06-05 08:54:07 UTC (rev 19629)
@@ -62,33 +62,74 @@
   elif bytes >= 1024: return "%i KB" % (bytes / 1024)
   else: return "%i bytes" % bytes
 
-class TermSubwindow():
+class Panel():
   """
-  Wrapper for curses subwindows. This provides safe proxies to common methods.
+  Wrapper for curses subwindows. This provides safe proxies to common methods
+  and is extended by panels.
   """
   
-  def __init__(self, win, lock, startY):
-    self.win = win          # associated curses subwindow
-    self.lock = lock        # global curses lock
-    self.startY = startY    # y-coordinate where made
-    self.disable = False    # set if we detect being displaced
-    self._resetBounds()     # sets last known dimensions of win
+  def __init__(self, lock, height):
+    self.win = None           # associated curses subwindow
+    self.lock = lock          # global curses lock
+    self.startY = -1          # top in parent window when created
+    self.height = height      # preferred (max) height of panel, -1 if infinite
+    self.isDisplaced = False  # window isn't in the right location - don't redraw
+    self._resetBounds()       # sets last known dimensions of win (maxX and maxY)
   
+  def redraw(self):
+    pass # overwritten by implementations
+  
+  def recreate(self, stdscr, startY):
+    """
+    Creates a new subwindow for the panel if:
+    - panel currently doesn't have a subwindow
+    - the panel is being moved (startY is different)
+    - there's room for the panel to grow
+    
+    Returns True if subwindow's created, False otherwise.
+    """
+    
+    # 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 entirly which 
+    # would mean more complicated code and no more selective refreshing)
+    
+    y, x = stdscr.getmaxyx()
+    self._resetBounds()
+    
+    if self.win and startY > y:
+      return False # trying to make panel out of bounds
+    
+    newHeight = y - startY
+    if self.height != -1: newHeight = min(newHeight, self.height)
+    
+    if self.startY != startY or newHeight > self.maxY or self.isDisplaced:
+      # window growing or moving - recreate
+      self.startY = startY
+      startY = min(startY, y - 1) # better create a displaced window than leave it as None
+    
+      self.win = stdscr.subwin(newHeight, x, startY, 0)
+      return True
+    else: return False
+  
   def clear(self):
     """
     Erases window and resets bounds used in writting to it.
     """
     
-    self.disable = self.startY > self.win.getparyx()[0]
-    if not self.disable: self.win.erase()
-    self._resetBounds()
+    if self.win:
+      self.isDisplaced = self.startY > self.win.getparyx()[0]
+      if not self.isDisplaced: self.win.erase()
+      self._resetBounds()
   
   def refresh(self):
     """
     Proxy for window refresh.
     """
     
-    if not self.disable: self.win.refresh()
+    if self.win and not self.isDisplaced: self.win.refresh()
   
   def addstr(self, y, x, msg, attr=curses.A_NORMAL):
     """
@@ -98,9 +139,10 @@
     
     # subwindows need a character buffer (either in the x or y direction) from
     # actual content to prevent crash when shrank
-    if self.maxX > x and self.maxY > y:
-      if not self.disable: self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
+    if self.win and self.maxX > x and self.maxY > y:
+      if not self.isDisplaced: self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
   
   def _resetBounds(self):
-    self.maxY, self.maxX = self.win.getmaxyx()
+    if self.win: self.maxY, self.maxX = self.win.getmaxyx()
+    else: self.maxY, self.maxX = -1, -1
 



More information about the tor-commits mailing list