Author: atagar Date: 2011-03-21 16:39:27 +0000 (Mon, 21 Mar 2011) New Revision: 24402
Added: arm/trunk/src/interface/connections/clientEntry.py Modified: arm/trunk/armrc.sample arm/trunk/src/interface/connections/connEntry.py arm/trunk/src/interface/connections/connPanel.py arm/trunk/src/util/torTools.py arm/trunk/src/util/uiTools.py Log: Presenting full circuit paths in the connection panel. For a somewhat outdated screenshot of what this includes see 'http://www.atagar.com/transfer/tmp/connPanelWithCircs.png'.
Modified: arm/trunk/armrc.sample =================================================================== --- arm/trunk/armrc.sample 2011-03-21 15:14:17 UTC (rev 24401) +++ arm/trunk/armrc.sample 2011-03-21 16:39:27 UTC (rev 24402) @@ -182,7 +182,7 @@ features.connection.showColumn.fingerprint true features.connection.showColumn.nickname true features.connection.showColumn.destination true -features.connection.showColumn.expanedIp true +features.connection.showColumn.expandedIp true
# Thread pool size for hostname resolutions # Determines the maximum number of concurrent requests. Upping this to around
Added: arm/trunk/src/interface/connections/clientEntry.py =================================================================== --- arm/trunk/src/interface/connections/clientEntry.py (rev 0) +++ arm/trunk/src/interface/connections/clientEntry.py 2011-03-21 16:39:27 UTC (rev 24402) @@ -0,0 +1,215 @@ +""" +Connection panel entries for client circuits. This includes a header entry +followed by an entry for each hop in the circuit. For instance: + +89.188.20.246:42667 --> 217.172.182.26 (de) General / Built 8.6m (CLIENT) +| 85.8.28.4 (se) 98FBC3B2B93897A78CDD797EF549E6B62C9A8523 1 / Guard +| 91.121.204.76 (fr) 546387D93F8D40CFF8842BB9D3A8EC477CEDA984 2 / Middle ++- 217.172.182.26 (de) 5CFA9EA136C0EA0AC096E5CEA7EB674F1207CF86 3 / Exit +""" + +import curses + +from interface.connections import entries, connEntry +from util import torTools, uiTools + +# cached fingerprint -> (IP Address, ORPort) results +RELAY_INFO = {} + +def getRelayInfo(fingerprint): + """ + Provides the (IP Address, ORPort) tuple for the given relay. If the lookup + fails then this returns ("192.168.0.1", "0"). + + Arguments: + fingerprint - relay to look up + """ + + if not fingerprint in RELAY_INFO: + conn = torTools.getConn() + failureResult = ("192.168.0.1", "0") + + nsEntry = conn.getConsensusEntry(fingerprint) + if not nsEntry: return failureResult + + nsLineComp = nsEntry.split("\n")[0].split(" ") + if len(nsLineComp) < 8: return failureResult + + RELAY_INFO[fingerprint] = (nsLineComp[6], nsLineComp[7]) + + return RELAY_INFO[fingerprint] + +class ClientEntry(connEntry.ConnectionEntry): + def __init__(self, circuitID, status, purpose, path): + connEntry.ConnectionEntry.__init__(self, "127.0.0.1", "0", "127.0.0.1", "0") + + self.circuitID = circuitID + self.status = status + + # drops to lowercase except the first letter + if len(purpose) >= 2: + purpose = purpose[0].upper() + purpose[1:].lower() + + self.lines = [ClientHeaderLine(self.circuitID, purpose)] + + # Overwrites attributes of the initial line to make it more fitting as the + # header for our listing. + + self.lines[0].baseType = connEntry.Category.CLIENT + + self.update(status, path) + + def update(self, status, path): + """ + Our status and path can change over time if the circuit is still in the + process of being built. Updates these attributs of our relay. + + Arguments: + status - new status of the circuit + path - list of fingerprints for the series of relays involved in the + circuit + """ + + self.status = status + self.lines = [self.lines[0]] + + if status == "BUILT" and not self.lines[0].isBuilt: + exitIp, exitORPort = getRelayInfo(path[-1]) + self.lines[0].setExit(exitIp, exitORPort, path[-1]) + + for i in range(len(path)): + relayFingerprint = path[i] + relayIp, relayOrPort = getRelayInfo(relayFingerprint) + + if i == len(path) - 1: + if status == "BUILT": placementType = "Exit" + else: placementType = "Extending" + elif i == 0: placementType = "Guard" + else: placementType = "Middle" + + placementLabel = "%i / %s" % (i + 1, placementType) + + self.lines.append(ClientLine(relayIp, relayOrPort, relayFingerprint, placementLabel)) + + self.lines[-1].isLast = True + +class ClientHeaderLine(connEntry.ConnectionLine): + """ + Initial line of a client entry. This has the same basic format as connection + lines except that its etc field has circuit attributes. + """ + + def __init__(self, circuitID, purpose): + connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", "0.0.0.0", "0", False, False) + self.circuitID = circuitID + self.purpose = purpose + self.isBuilt = False + + def setExit(self, exitIpAddr, exitPort, exitFingerprint): + connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", exitIpAddr, exitPort, False, False) + self.isBuilt = True + self.foreign.fingerprintOverwrite = exitFingerprint + + def getType(self): + return connEntry.Category.CLIENT + + def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False): + if not self.isBuilt: return "Building..." + return connEntry.ConnectionLine.getDestinationLabel(self, maxLength, includeLocale, includeHostname) + + def getEtcContent(self, width, listingType): + """ + Attempts to provide all circuit related stats. Anything that can't be + shown completely (not enough room) is dropped. + """ + + etcAttr = ["Purpose: %s" % self.purpose, "Circuit ID: %i" % self.circuitID] + + for i in range(len(etcAttr), -1, -1): + etcLabel = ", ".join(etcAttr[:i]) + if len(etcLabel) <= width: return etcLabel + + return "" + + def getDetails(self, width): + if not self.isBuilt: + detailFormat = curses.A_BOLD | uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()]) + return [uiTools.DrawEntry("Building Circuit...", detailFormat)] + else: return connEntry.ConnectionLine.getDetails(self, width) + +class ClientLine(connEntry.ConnectionLine): + """ + An inidividual hop in a circuit. This overwrites the displayed listing, but + otherwise makes use of the ConnectionLine attributes (for the detail display, + caching, etc). + """ + + def __init__(self, fIpAddr, fPort, fFingerprint, placementLabel): + connEntry.ConnectionLine.__init__(self, "127.0.0.1", "0", fIpAddr, fPort) + self.foreign.fingerprintOverwrite = fFingerprint + self.placementLabel = placementLabel + self.includePort = False + + # determines the sort of left hand bracketing we use + self.isLast = False + + def getType(self): + return connEntry.Category.CLIENT + + def getListingEntry(self, width, currentTime, listingType): + """ + Provides the DrawEntry for this relay in the circuilt listing. Lines are + composed of the following components: + <bracket> <dst> <etc> <placement label> + + The dst and etc entries largely match their ConnectionEntry counterparts. + + Arguments: + width - maximum length of the line + currentTime - the current unix time (ignored) + listingType - primary attribute we're listing connections by + """ + + return entries.ConnectionPanelLine.getListingEntry(self, width, currentTime, listingType) + + def _getListingEntry(self, width, currentTime, listingType): + lineFormat = uiTools.getColor(connEntry.CATEGORY_COLOR[self.getType()]) + + # The required widths are the sum of the following: + # bracketing (3 characters) + # placementLabel (14 characters) + # gap between etc and placement label (5 characters) + + if self.isLast: bracket = (curses.ACS_LLCORNER, curses.ACS_HLINE, ord(' ')) + else: bracket = (curses.ACS_VLINE, ord(' '), ord(' ')) + baselineSpace = len(bracket) + 14 + 5 + + dst, etc = "", "" + if listingType == entries.ListingType.IP_ADDRESS: + # TODO: include hostname when that's available + # dst width is derived as: + # src (21) + dst (26) + divider (7) + right gap (2) - bracket (3) = 53 char + dst = "%-53s" % self.getDestinationLabel(53, includeLocale = True) + etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) + elif listingType == entries.ListingType.HOSTNAME: + # min space for the hostname is 40 characters + etc = self.getEtcContent(width - baselineSpace - 40, listingType) + dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) + dst = dstLayout % self.foreign.getHostname(self.foreign.getIpAddr()) + elif listingType == entries.ListingType.FINGERPRINT: + # dst width is derived as: + # src (9) + dst (40) + divider (7) + right gap (2) - bracket (3) = 55 char + dst = "%-55s" % self.foreign.getFingerprint() + etc = self.getEtcContent(width - baselineSpace - len(dst), listingType) + else: + # min space for the nickname is 50 characters + etc = self.getEtcContent(width - baselineSpace - 50, listingType) + dstLayout = "%%-%is" % (width - baselineSpace - len(etc)) + dst = dstLayout % self.foreign.getNickname() + + drawEntry = uiTools.DrawEntry("%-14s" % self.placementLabel, lineFormat) + drawEntry = uiTools.DrawEntry(" " * (width - baselineSpace - len(dst) - len(etc) + 5), lineFormat, drawEntry) + drawEntry = uiTools.DrawEntry(dst + etc, lineFormat, drawEntry) + drawEntry = uiTools.DrawEntry(bracket, curses.A_NORMAL, drawEntry, lockFormat = True) + return drawEntry +
Modified: arm/trunk/src/interface/connections/connEntry.py =================================================================== --- arm/trunk/src/interface/connections/connEntry.py 2011-03-21 15:14:17 UTC (rev 24401) +++ arm/trunk/src/interface/connections/connEntry.py 2011-03-21 16:39:27 UTC (rev 24402) @@ -32,7 +32,7 @@ CONFIG = {"features.connection.showColumn.fingerprint": True, "features.connection.showColumn.nickname": True, "features.connection.showColumn.destination": True, - "features.connection.showColumn.expanedIp": True} + "features.connection.showColumn.expandedIp": True}
def loadConfig(config): config.update(CONFIG) @@ -51,6 +51,9 @@ # if true, we treat the port as an ORPort when searching for matching # fingerprints (otherwise the ORPort is assumed to be unknown) self.isORPort = False + + # if set then this overwrites fingerprint lookups + self.fingerprintOverwrite = None
def getIpAddr(self): """ @@ -88,14 +91,16 @@
return default
- def getLocale(self): + def getLocale(self, default=None): """ - Provides the two letter country code for the IP address' locale. This - proivdes None if it can't be determined. + Provides the two letter country code for the IP address' locale. + + Arguments: + default - return value if no locale information is available """
conn = torTools.getConn() - return conn.getInfo("ip-to-country/%s" % self.ipAddr) + return conn.getInfo("ip-to-country/%s" % self.ipAddr, default)
def getFingerprint(self): """ @@ -103,6 +108,9 @@ determined. """
+ if self.fingerprintOverwrite: + return self.fingerprintOverwrite + conn = torTools.getConn() orPort = self.port if self.isORPort else None myFingerprint = conn.getRelayFingerprint(self.ipAddr, orPort) @@ -157,7 +165,7 @@ return self.lines[0].startTime elif attr == entries.SortAttr.COUNTRY: if connections.isIpAddressPrivate(self.lines[0].foreign.getIpAddr()): return "" - else: return self.lines[0].foreign.getLocale() + else: return self.lines[0].foreign.getLocale("") else: return entries.ConnectionPanelEntry.getSortValue(self, attr, listingType)
@@ -166,7 +174,7 @@ Display component of the ConnectionEntry. """
- def __init__(self, lIpAddr, lPort, fIpAddr, fPort): + def __init__(self, lIpAddr, lPort, fIpAddr, fPort, includePort=True, includeExpandedIpAddr=True): entries.ConnectionPanelLine.__init__(self)
self.local = Endpoint(lIpAddr, lPort) @@ -206,14 +214,19 @@
self.cachedType = None
+ # includes the port or expanded ip address field when displaying listing + # information if true + self.includePort = includePort + self.includeExpandedIpAddr = includeExpandedIpAddr + # cached immutable values used for sorting self.sortIpAddr = connections.ipToInt(self.foreign.getIpAddr()) self.sortPort = int(self.foreign.getPort())
def getListingEntry(self, width, currentTime, listingType): """ - Provides the DrawEntry for this connection's listing. The line is made up - of six components: + Provides the DrawEntry for this connection's listing. Lines are composed + of the following components: <src> --> <dst> <etc> <uptime> (<type>)
ListingType.IP_ADDRESS: @@ -355,7 +368,7 @@ # a built 1-hop connection since those are most likely a directory # mirror).
- for status, _, path in myCircuits: + for _, status, _, path in myCircuits: if path[0] == destFingerprint and (status != "BUILT" or len(path) > 1): self.cachedType = Category.CLIENT # matched a probable guard connection
@@ -366,7 +379,7 @@ if self._possibleDirectory: # Checks if we match a built, single hop circuit.
- for status, _, path in myCircuits: + for _, status, _, path in myCircuits: if path[0] == destFingerprint and status == "BUILT" and len(path) == 1: self.cachedType = Category.DIRECTORY
@@ -379,6 +392,78 @@
return self.cachedType
+ def getEtcContent(self, width, listingType): + """ + Provides the optional content for the connection. + + Arguments: + width - maximum length of the line + listingType - primary attribute we're listing connections by + """ + + dstAddress = self.getDestinationLabel(26, includeLocale = True) + etc, usedSpace = "", 0 + if listingType == entries.ListingType.IP_ADDRESS: + if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: + # show fingerprint (column width: 42 characters) + etc += "%-40s " % self.foreign.getFingerprint() + usedSpace += 42 + + if width > usedSpace + 10 and CONFIG["features.connection.showColumn.nickname"]: + # show nickname (column width: remainder) + nicknameSpace = width - usedSpace + nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) + etc += ("%%-%is " % nicknameSpace) % nicknameLabel + usedSpace += nicknameSpace + 2 + elif listingType == entries.ListingType.HOSTNAME: + if width > usedSpace + 28 and CONFIG["features.connection.showColumn.destination"]: + # show destination ip/port/locale (column width: 28 characters) + etc += "%-26s " % dstAddress + usedSpace += 28 + + if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: + # show fingerprint (column width: 42 characters) + etc += "%-40s " % self.foreign.getFingerprint() + usedSpace += 42 + + if width > usedSpace + 17 and CONFIG["features.connection.showColumn.nickname"]: + # show nickname (column width: min 17 characters, uses half of the remainder) + nicknameSpace = 15 + (width - (usedSpace + 17)) / 2 + nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) + etc += ("%%-%is " % nicknameSpace) % nicknameLabel + usedSpace += (nicknameSpace + 2) + elif listingType == entries.ListingType.FINGERPRINT: + if width > usedSpace + 17: + # show nickname (column width: min 17 characters, consumes any remaining space) + nicknameSpace = width - usedSpace - 2 + + # if there's room then also show a column with the destination + # ip/port/locale (column width: 28 characters) + isIpLocaleIncluded = width > usedSpace + 45 + isIpLocaleIncluded &= CONFIG["features.connection.showColumn.destination"] + if isIpLocaleIncluded: nicknameSpace -= 28 + + if CONFIG["features.connection.showColumn.nickname"]: + nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) + etc += ("%%-%is " % nicknameSpace) % nicknameLabel + usedSpace += nicknameSpace + 2 + + if isIpLocaleIncluded: + etc += "%-26s " % dstAddress + usedSpace += 28 + else: + if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: + # show fingerprint (column width: 42 characters) + etc += "%-40s " % self.foreign.getFingerprint() + usedSpace += 42 + + if width > usedSpace + 28 and CONFIG["features.connection.showColumn.destination"]: + # show destination ip/port/locale (column width: 28 characters) + etc += "%-26s " % dstAddress + usedSpace += 28 + + return etc + def _getListingContent(self, width, listingType): """ Provides the source, destination, and extra info for our listing. @@ -390,7 +475,7 @@
conn = torTools.getConn() myType = self.getType() - dstAddress = self._getDestinationLabel(26, includeLocale = True) + dstAddress = self.getDestinationLabel(26, includeLocale = True)
# The required widths are the sum of the following: # - room for LABEL_FORMAT and LABEL_MIN_PADDING (11 characters) @@ -398,70 +483,55 @@ # - that extra field plus any previous
usedSpace = len(LABEL_FORMAT % tuple([""] * 4)) + LABEL_MIN_PADDING + localPort = ":%s" % self.local.getPort() if self.includePort else ""
src, dst, etc = "", "", "" if listingType == entries.ListingType.IP_ADDRESS: myExternalIpAddr = conn.getInfo("address", self.local.getIpAddr()) addrDiffer = myExternalIpAddr != self.local.getIpAddr()
- srcAddress = "%s:%s" % (myExternalIpAddr, self.local.getPort()) + srcAddress = myExternalIpAddr + localPort src = "%-21s" % srcAddress # ip:port = max of 21 characters dst = "%-26s" % dstAddress # ip:port (xx) = max of 26 characters
usedSpace += len(src) + len(dst) # base data requires 47 characters
- if width > usedSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: - # show fingerprint (column width: 42 characters) - etc += "%-40s " % self.foreign.getFingerprint() - usedSpace += 42 + # Showing the fingerprint (which has the width of 42) has priority over + # an expanded address field. Hence check if we either have space for + # both or wouldn't be showing the fingerprint reguardless.
- if addrDiffer and width > usedSpace + 28 and CONFIG["features.connection.showColumn.expanedIp"]: + isExpandedAddrVisible = width > usedSpace + 28 + if isExpandedAddrVisible and CONFIG["features.connection.showColumn.fingerprint"]: + isExpandedAddrVisible = width < usedSpace + 42 or width > usedSpace + 70 + + if addrDiffer and isExpandedAddrVisible and self.includeExpandedIpAddr and CONFIG["features.connection.showColumn.expandedIp"]: # include the internal address in the src (extra 28 characters) - internalAddress = "%s:%s" % (self.local.getIpAddr(), self.local.getPort()) + internalAddress = self.local.getIpAddr() + localPort src = "%-21s --> %s" % (internalAddress, src) usedSpace += 28
- if width > usedSpace + 10 and CONFIG["features.connection.showColumn.nickname"]: - # show nickname (column width: remainder) - nicknameSpace = width - usedSpace - nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) - etc += ("%%-%is " % nicknameSpace) % nicknameLabel - usedSpace += nicknameSpace + 2 + etc = self.getEtcContent(width - usedSpace, listingType) + usedSpace += len(etc) elif listingType == entries.ListingType.HOSTNAME: # 15 characters for source, and a min of 40 reserved for the destination - src = "localhost:%-5s" % self.local.getPort() + src = "localhost%-6s" % localPort usedSpace += len(src) minHostnameSpace = 40
- if width > usedSpace + minHostnameSpace + 28 and CONFIG["features.connection.showColumn.destination"]: - # show destination ip/port/locale (column width: 28 characters) - etc += "%-26s " % dstAddress - usedSpace += 28 + etc = self.getEtcContent(width - usedSpace - minHostnameSpace, listingType) + usedSpace += len(etc)
- if width > usedSpace + minHostnameSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: - # show fingerprint (column width: 42 characters) - etc += "%-40s " % self.foreign.getFingerprint() - usedSpace += 42 - - if width > usedSpace + minHostnameSpace + 17 and CONFIG["features.connection.showColumn.nickname"]: - # show nickname (column width: min 17 characters, uses half of the remainder) - nicknameSpace = 15 + (width - (usedSpace + minHostnameSpace + 17)) / 2 - nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) - etc += ("%%-%is " % nicknameSpace) % nicknameLabel - usedSpace += (nicknameSpace + 2) - hostnameSpace = width - usedSpace usedSpace = width # prevents padding at the end if self.isPrivate(): dst = ("%%-%is" % hostnameSpace) % "<scrubbed>" else: hostname = self.foreign.getHostname(self.foreign.getIpAddr()) - port = self.foreign.getPort() + portLabel = ":%-5s" % self.foreign.getPort() if self.includePort else ""
# truncates long hostnames and sets dst to <hostname>:<port> hostname = uiTools.cropStr(hostname, hostnameSpace, 0) - dst = "%s:%-5s" % (hostname, port) - dst = ("%%-%is" % hostnameSpace) % dst + dst = ("%%-%is" % hostnameSpace) % (hostname + portLabel) elif listingType == entries.ListingType.FINGERPRINT: src = "localhost" if myType == Category.CONTROL: dst = "localhost" @@ -470,25 +540,8 @@
usedSpace += len(src) + len(dst) # base data requires 49 characters
- if width > usedSpace + 17: - # show nickname (column width: min 17 characters, consumes any remaining space) - nicknameSpace = width - usedSpace - - # if there's room then also show a column with the destination - # ip/port/locale (column width: 28 characters) - isIpLocaleIncluded = width > usedSpace + 45 - isIpLocaleIncluded &= CONFIG["features.connection.showColumn.destination"] - if isIpLocaleIncluded: nicknameSpace -= 28 - - if CONFIG["features.connection.showColumn.nickname"]: - nicknameSpace = width - usedSpace - 28 if isIpLocaleIncluded else width - usedSpace - nicknameLabel = uiTools.cropStr(self.foreign.getNickname(), nicknameSpace, 0) - etc += ("%%-%is " % nicknameSpace) % nicknameLabel - usedSpace += nicknameSpace + 2 - - if isIpLocaleIncluded: - etc += "%-26s " % dstAddress - usedSpace += 28 + etc = self.getEtcContent(width - usedSpace, listingType) + usedSpace += len(etc) else: # base data requires 50 min characters src = self.local.getNickname() @@ -496,16 +549,9 @@ else: dst = self.foreign.getNickname() minBaseSpace = 50
- if width > usedSpace + minBaseSpace + 42 and CONFIG["features.connection.showColumn.fingerprint"]: - # show fingerprint (column width: 42 characters) - etc += "%-40s " % self.foreign.getFingerprint() - usedSpace += 42 + etc = self.getEtcContent(width - usedSpace - minBaseSpace, listingType) + usedSpace += len(etc)
- if width > usedSpace + minBaseSpace + 28 and CONFIG["features.connection.showColumn.destination"]: - # show destination ip/port/locale (column width: 28 characters) - etc += "%-26s " % dstAddress - usedSpace += 28 - baseSpace = width - usedSpace usedSpace = width # prevents padding at the end
@@ -529,8 +575,8 @@ """
lines = [""] * 7 - lines[0] = "address: %s" % self._getDestinationLabel(width - 11) - lines[1] = "locale: %s" % ("??" if self.isPrivate() else self.foreign.getLocale()) + lines[0] = "address: %s" % self.getDestinationLabel(width - 11) + lines[1] = "locale: %s" % ("??" if self.isPrivate() else self.foreign.getLocale("??"))
# Remaining data concerns the consensus results, with three possible cases: # - if there's a single match then display its details @@ -627,7 +673,7 @@
return lines
- def _getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False): + def getDestinationLabel(self, maxLength, includeLocale=False, includeHostname=False): """ Provides a short description of the destination. This is made up of two components, the base <ip addr>:<port> and an extra piece of information in @@ -646,10 +692,9 @@ """
# destination of the connection - if self.isPrivate(): - dstAddress = "<scrubbed>:%s" % self.foreign.getPort() - else: - dstAddress = "%s:%s" % (self.foreign.getIpAddr(), self.foreign.getPort()) + ipLabel = "<scrubbed>" if self.isPrivate() else self.foreign.getIpAddr() + portLabel = ":%s" % self.foreign.getPort() if self.includePort else "" + dstAddress = ipLabel + portLabel
# Only append the extra info if there's at least a couple characters of # space (this is what's needed for the country codes). @@ -673,7 +718,7 @@ extraInfo = []
if includeLocale: - foreignLocale = self.foreign.getLocale() + foreignLocale = self.foreign.getLocale("??") extraInfo.append(foreignLocale) spaceAvailable -= len(foreignLocale) + 2
Modified: arm/trunk/src/interface/connections/connPanel.py =================================================================== --- arm/trunk/src/interface/connections/connPanel.py 2011-03-21 15:14:17 UTC (rev 24401) +++ arm/trunk/src/interface/connections/connPanel.py 2011-03-21 16:39:27 UTC (rev 24402) @@ -6,8 +6,8 @@ import curses import threading
-from interface.connections import entries, connEntry -from util import connections, enum, panel, uiTools +from interface.connections import entries, connEntry, clientEntry +from util import connections, enum, panel, torTools, uiTools
DEFAULT_CONFIG = {"features.connection.listingType": 0, "features.connection.refreshRate": 10} @@ -48,8 +48,8 @@ self._listingType = Listing.values()[self._config["features.connection.listingType"]] self._scroller = uiTools.Scroller(True) self._title = "Connections:" # title line of the panel - self._connections = [] # last fetched connections - self._connectionLines = [] # individual lines in the connection listing + self._entries = [] # last fetched display entries + self._entryLines = [] # individual lines rendered from the entries listing self._showDetails = False # presents the details panel if true
self._lastUpdate = -1 # time the content was last revised @@ -95,11 +95,11 @@
self.valsLock.acquire() if ordering: self._sortOrdering = ordering - self._connections.sort(key=lambda i: (i.getSortValues(self._sortOrdering, self._listingType))) + self._entries.sort(key=lambda i: (i.getSortValues(self._sortOrdering, self._listingType)))
- self._connectionLines = [] - for entry in self._connections: - self._connectionLines += entry.getLines() + self._entryLines = [] + for entry in self._entries: + self._entryLines += entry.getLines() self.valsLock.release()
def setListingType(self, listingType): @@ -125,7 +125,7 @@ if uiTools.isScrollKey(key): pageHeight = self.getPreferredSize()[0] - 1 if self._showDetails: pageHeight -= (DETAILS_HEIGHT + 1) - isChanged = self._scroller.handleKey(key, self._connectionLines, pageHeight) + isChanged = self._scroller.handleKey(key, self._entryLines, pageHeight) if isChanged: self.redraw(True) elif uiTools.isSelectionKey(key): self._showDetails = not self._showDetails @@ -157,10 +157,10 @@
# extra line when showing the detail panel is for the bottom border detailPanelOffset = DETAILS_HEIGHT + 1 if self._showDetails else 0 - isScrollbarVisible = len(self._connectionLines) > height - detailPanelOffset - 1 + isScrollbarVisible = len(self._entryLines) > height - detailPanelOffset - 1
- scrollLoc = self._scroller.getScrollLoc(self._connectionLines, height - detailPanelOffset - 1) - cursorSelection = self._scroller.getCursorSelection(self._connectionLines) + scrollLoc = self._scroller.getScrollLoc(self._entryLines, height - detailPanelOffset - 1) + cursorSelection = self._scroller.getCursorSelection(self._entryLines)
# draws the detail panel if currently displaying it if self._showDetails: @@ -177,14 +177,14 @@ title = "Connection Details:" if self._showDetails else self._title self.addstr(0, 0, title, curses.A_STANDOUT)
- scrollOffset = 0 + scrollOffset = 1 if isScrollbarVisible: scrollOffset = 3 - self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._connectionLines), 1 + detailPanelOffset) + self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelOffset - 1, len(self._entryLines), 1 + detailPanelOffset)
currentTime = self._pauseTime if self._pauseTime else time.time() - for lineNum in range(scrollLoc, len(self._connectionLines)): - entryLine = self._connectionLines[lineNum] + for lineNum in range(scrollLoc, len(self._entryLines)): + entryLine = self._entryLines[lineNum]
# hilighting if this is the selected line extraFormat = curses.A_STANDOUT if entryLine == cursorSelection else curses.A_NORMAL @@ -216,41 +216,68 @@
if self._lastResourceFetch != currentResolutionCount: self.valsLock.acquire() - currentConnections = connResolver.getConnections()
- # Replacement listing of connections. We first populate it with any of - # our old entries in currentConnections, then add new ConnectionEntries - # for whatever remains. - newConnections = [] + newEntries = [] # the new results we'll display
- # preserves any ConnectionEntries they already exist - for entry in self._connections: - if isinstance(entry, connEntry.ConnectionEntry): - connLine = entry.getLines()[0] + # Fetches new connections and client circuits... + # newConnections [(local ip, local port, foreign ip, foreign port)...] + # newCircuits {circuitID => (status, purpose, path)...} + + newConnections = connResolver.getConnections() + newCircuits = {} + + for circuitID, status, purpose, path in torTools.getConn().getCircuits(): + # Skips established single-hop circuits (these are for directory + # fetches, not client circuits) + if not (status == "BUILT" and len(path) == 1): + newCircuits[circuitID] = (status, purpose, path) + + # Populates newEntries with any of our old entries that still exist. + # This is both for performance and to keep from resetting the uptime + # attributes. Note that ClientEntries are a ConnectionEntry subclass so + # we need to check for them first. + + for oldEntry in self._entries: + if isinstance(oldEntry, clientEntry.ClientEntry): + newEntry = newCircuits.get(oldEntry.circuitID) + + if newEntry: + oldEntry.update(newEntry[0], newEntry[2]) + newEntries.append(oldEntry) + del newCircuits[oldEntry.circuitID] + elif isinstance(oldEntry, connEntry.ConnectionEntry): + connLine = oldEntry.getLines()[0] connAttr = (connLine.local.getIpAddr(), connLine.local.getPort(), connLine.foreign.getIpAddr(), connLine.foreign.getPort())
- if connAttr in currentConnections: - newConnections.append(entry) - currentConnections.remove(connAttr) + if connAttr in newConnections: + newEntries.append(oldEntry) + newConnections.remove(connAttr)
- # reset any display attributes for the entries we're keeping - for entry in newConnections: - entry.resetDisplay() + # Reset any display attributes for the entries we're keeping + for entry in newEntries: entry.resetDisplay()
- # add new entries for any additions - for lIp, lPort, fIp, fPort in currentConnections: - newConnections.append(connEntry.ConnectionEntry(lIp, lPort, fIp, fPort)) + # Adds any new connection and circuit entries. + for lIp, lPort, fIp, fPort in newConnections: + newConnEntry = connEntry.ConnectionEntry(lIp, lPort, fIp, fPort) + if newConnEntry.getLines()[0].getType() != connEntry.Category.CLIENT: + newEntries.append(newConnEntry)
+ for circuitID in newCircuits: + status, purpose, path = newCircuits[circuitID] + newEntries.append(clientEntry.ClientEntry(circuitID, status, purpose, path)) + # Counts the relays in each of the categories. This also flushes the # type cache for all of the connections (in case its changed since last # fetched).
categoryTypes = connEntry.Category.values() typeCounts = dict((type, 0) for type in categoryTypes) - for entry in newConnections: + for entry in newEntries: if isinstance(entry, connEntry.ConnectionEntry): typeCounts[entry.getLines()[0].getType()] += 1 + elif isinstance(entry, clientEntry.ClientEntry): + typeCounts[connEntry.Category.CLIENT] += 1
# makes labels for all the categories with connections (ie, # "21 outbound", "1 control", etc) @@ -263,11 +290,11 @@ if countLabels: self._title = "Connections (%s):" % ", ".join(countLabels) else: self._title = "Connections:"
- self._connections = newConnections + self._entries = newEntries
- self._connectionLines = [] - for entry in self._connections: - self._connectionLines += entry.getLines() + self._entryLines = [] + for entry in self._entries: + self._entryLines += entry.getLines()
self.setSortOrder() self._lastResourceFetch = currentResolutionCount
Modified: arm/trunk/src/util/torTools.py =================================================================== --- arm/trunk/src/util/torTools.py 2011-03-21 15:14:17 UTC (rev 24401) +++ arm/trunk/src/util/torTools.py 2011-03-21 16:39:27 UTC (rev 24402) @@ -606,7 +606,7 @@ def getCircuits(self, default = []): """ This provides a list with tuples of the form: - (status, purpose, (fingerprint1, fingerprint2...)) + (circuitID, status, purpose, (fingerprint1, fingerprint2...))
Arguments: default - value provided back if unable to query the circuit-status @@ -1394,15 +1394,9 @@ for line in orconnResults.split("\n"): self._fingerprintsAttachedCache.append(line[1:line.find("=")])
- # circuit-status has entries of the form: - # 7 BUILT $33173252B70A50FE3928C7453077936D71E45C52=shiven,... - circStatusResults = self.getInfo("circuit-status") - if circStatusResults: - for line in circStatusResults.split("\n"): - clientEntries = line.split(" ")[2].split(",") - - for entry in clientEntries: - self._fingerprintsAttachedCache.append(entry[1:entry.find("=")]) + # circuit-status results (we only make connections to the first hop) + for _, _, _, path in self.getCircuits(): + self._fingerprintsAttachedCache.append(path[0])
# narrow to only relays we have a connection to attachedMatches = [] @@ -1608,7 +1602,7 @@ if len(lineComp) < 4: continue
path = tuple([hopEntry[1:41] for hopEntry in lineComp[2].split(",")]) - result.append((lineComp[1], lineComp[3], path)) + result.append((int(lineComp[0]), lineComp[1], lineComp[3][8:], path))
# cache value if result != None: self._cachedParam[key] = result
Modified: arm/trunk/src/util/uiTools.py =================================================================== --- arm/trunk/src/util/uiTools.py 2011-03-21 15:14:17 UTC (rev 24401) +++ arm/trunk/src/util/uiTools.py 2011-03-21 16:39:27 UTC (rev 24402) @@ -415,10 +415,23 @@ chained together to compose lines with multiple types of formatting. """
- def __init__(self, text, format=curses.A_NORMAL, nextEntry=None): + def __init__(self, text, format=curses.A_NORMAL, nextEntry=None, lockFormat=False): + """ + Constructor for prepared draw entries. + + Arguments: + text - content to be drawn, this can either be a string or list of + integer character codes + format - properties to apply when drawing + nextEntry - entry to be drawn after this one + lockFormat - prevents extra formatting attributes from being applied + when rendered if true + """ + self.text = text self.format = format self.nextEntry = nextEntry + self.lockFormat = lockFormat
def getNext(self): """ @@ -449,9 +462,16 @@ extraFormat - additional formatting """
- drawFormat = self.format | extraFormat - drawPanel.addstr(y, x, self.text, drawFormat) + if self.lockFormat: drawFormat = self.format + else: drawFormat = self.format | extraFormat
+ if isinstance(self.text, str): + drawPanel.addstr(y, x, self.text, drawFormat) + else: + for i in range(len(self.text)): + drawChar = self.text[i] + drawPanel.addch(y, x + i, drawChar, drawFormat) + # if there's additional content to show then render it too if self.nextEntry: self.nextEntry.render(drawPanel, y, x + len(self.text), extraFormat)
tor-commits@lists.torproject.org