Attached is a minor update, which makes it look ok in IceWeasel -
the previous one looks different between IceWeasel and IceApe.

My development time is on other projects, so the switch to PNG
images hasn't been written.

The baseline has moved from SVN 411 to SVN 414.

Steve
diff --git a/DebTorrent/BT1/AptListener.py b/DebTorrent/BT1/AptListener.py
index a2c92ea..ed216a4 100644
--- a/DebTorrent/BT1/AptListener.py
+++ b/DebTorrent/BT1/AptListener.py
@@ -25,7 +25,7 @@ from cStringIO import StringIO
 from time import time, gmtime, strftime
 from DebTorrent.clock import clock
 from sha import sha
-from binascii import b2a_hex
+from binascii import b2a_hex, a2b_hex
 from makemetafile import TorrentCreator
 from DebTorrent.HTTPCache import HTTPCache
 import os, logging
@@ -230,11 +230,14 @@ class AptListener:
         self.connection_update(open_conns)
 
 
-    def get_infopage(self):
+    def get_infopage(self, show_piecemaps):
         """Format the info page to display for normal browsers.
         
         Formats the currently downloading torrents into a table in human-readable
         format to display in a browser window.
+
+        @type show_piecemaps: C{string}
+        @param show_piecemaps: "svg" to include SVG piecemaps.  "png" may be supported in future
         
         @rtype: (C{int}, C{string}, C{dictionary}, C{string})
         @return: the HTTP status code, status message, headers, and message body
@@ -324,6 +327,26 @@ class AptListener:
                     '<li><em>distributed copies:</em> the number of copies of the complete torrent seen in non-seeding peers</li>\n' \
                     '</ul>\n')
 
+            # Draw the piece maps
+            s.write('<h3>Piece maps</h3>\n')
+            if show_piecemaps == 'svg':
+                for x in data:
+                    ( name, hash, status, progress, peers, seeds, seedsmsg, dist,
+                      uprate, dnrate, upamt, dnamt, httpdnamt, size, t, msg ) = x
+                    s.write('<h4>%s (%s)</h4>\n' % (name, b2a_hex(hash)))
+                    s.write('<a  href="piecemap.svg?info_hash=%s"><object data="piecemap.svg?info_hash=%s" width="100%%" height="%d"></object></a>\n'
+                        % (b2a_hex(hash), b2a_hex(hash), self.svg_height_piecemap(peers)))
+                    #TODO use an accurate size for the image
+
+                s.write('<ul>\n' \
+                        '<li><em>blue:</em> You have this piece</li>\n' \
+                        '<li><em>purple:</em> A peer has this piece</li>\n' \
+                        '<li><em>white:</em> You/Peer does not have this piece, but does have a later piece</li>\n' \
+                        '<li><em>grey:</em> You/Peer does not have this piece, and does not have any later piece</li>\n' \
+                        '</ul>\n')
+            else:
+                s.write('<p><a href="index.html?piecemaps=svg">Click here to show</a></p>')
+
             s.write('</body>\n' \
                 '</html>\n')
             return (200, 'OK', {'Server': VERSION, 'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue())
@@ -332,6 +355,162 @@ class AptListener:
             return (500, 'Internal Server Error', {'Server': VERSION, 'Content-Type': 'text/html; charset=iso-8859-1'}, 'Server Error')
 
 
+    def svg_height_piecemap(self, number_of_peers):
+        """Returns the size (currently only height) of the piecemap image.
+
+        This has an inbuild race condition - the actual request to draw the piecemap may come in a separate request.
+
+        """
+        #TODO - move the SVG stuff to a separate class, make these class constants
+        markwidth=1
+        markheight=6
+        linepadding=1
+        lineheight=markheight + linepadding
+
+        # This works round the behaviour of Iceweasel, which stretches these images to be a minimum of 50 pixels hight
+        totalheight = lineheight * (number_of_peers+1) + linepadding
+        workaroundheight = totalheight
+        if (workaroundheight < 50):
+            workaroundheight = 50
+
+        return workaroundheight
+
+
+    def get_piecemap_for_torrent_svg(self, info_hash):
+        """Shows the piecemap (which peers have what) for a given torrent.
+
+        The HTTP request should include the appropriate torrent hash ( http://.../piecemap.svg?info_hash=... )
+
+        @type info_hash: C{string}
+        @param info_hash: the info_hash passed to the HTTP query
+
+        @rtype: (C{int}, C{string}, C{dictionary}, C{string})
+        @return: the HTTP status code, status message, headers, and message body
+
+        """
+
+        try:
+            if not self.config['show_infopage']:
+                return (404, 'Not Found', {'Server': VERSION, 'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
+
+            if info_hash == None:
+                return (400, 'Bad request', {'Server': VERSION, 'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, 'No hash specified in request')
+            hash = a2b_hex(info_hash)
+            piecelist_list = self.handler.get_piecemap(hash)
+            if piecelist_list == None:
+                return (404, 'Not Found', {'Server': VERSION, 'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, 'Unknown hash')
+
+            s = StringIO()
+            self.svg_draw_piecemap(s, piecelist_list, ["blue", "purple"])
+
+            return (200, 'OK', {'Server': VERSION, 'Content-Type': 'image/svg+xml; charset=utf-8'}, s.getvalue())
+        except:
+            logger.exception('Error returning info_page')
+            return (500, 'Internal Server Error', {'Server': VERSION, 'Content-Type': 'text/html; charset=iso-8859-1'}, 'Server Error')
+
+    def svg_draw_piecemap(self, s, piecelist_list, color_list):
+        """Draw (in SVG) a map of which pieces are present in the piecelist
+
+        @type s: C{StringIO}
+        @param s: Output stream for data that will be sent to the webbrowser.
+        @type piecelist_list: C{List} of C{Bitfield}
+        @param piecelist_list: The bitfields representing which pieces are present.
+        @type color_list: C{List} of C{String}
+        @param color_list: List of colors for drawing the pieces. color_list[i] is used for piecelist_list[i]; if there are more piecelists than colors then the last color is reused.
+
+        """
+
+        # Constants for the size of the drawing
+        # Vertically, each line is of total height lineheight, with padding at the bottom.
+        markwidth=1
+        markheight=6
+        linepadding=1
+        lineheight=markheight + linepadding
+        fittowindow = True
+
+        # Since some piecelists have more pieces than others, find the longest.
+        mostpieces = 0
+        for piecelist in piecelist_list:
+            if (len(piecelist) > mostpieces):
+                mostpieces = len(piecelist)
+
+        # The bitfields have a lot of whitespace. Compress the drawing by showing only the pieces held by at least one peer.
+        # piecelist[piecenumber] will be drawn at pixellist[piecenumber]
+        pixellist = [0] * mostpieces
+        y = 0
+        for x in xrange(0, mostpieces):
+            someonehas = False
+            pixellist[x] = y
+            for i in xrange(0, len(piecelist_list)):
+                if (piecelist_list[i][x]):
+                    someonehas = True
+            if someonehas:
+                y += 1
+
+        # And this is the width of the compressed drawing
+        imagewidth = y
+        # This works round the behaviour of Iceweasel, which stretches these images to be a minimum of 50 pixels hight
+        totalheight = lineheight * len(piecelist_list) + linepadding
+        workaroundheight = totalheight
+        if (workaroundheight < 50):
+            workaroundheight = 50
+
+        if (fittowindow):
+            s.write('<svg:svg xmlns:svg="http://www.w3.org/2000/svg"; width="100%%" height="%spx" viewBox="0 0 %s %s" preserveAspectRatio="none" shape-rendering="crispEdges">\n'
+                    % (workaroundheight, markwidth * imagewidth, workaroundheight))
+        else:
+            s.write('<svg:svg xmlns:svg="http://www.w3.org/2000/svg"; width="%spx" height="%spx" shape-rendering="crispEdges">\n'
+                    % (markwidth * imagewidth, workaroundheight))
+
+        # Draw the background in one go.
+        s.write('<svg:rect x="0" y="0" width="%spx" height="%spx" fill="black" />\n'
+                % (markwidth * imagewidth, totalheight))
+
+        color = "blue"; # Default which should be overridden by color_list[0]
+
+        for i in xrange(0, len(piecelist_list)):
+            piecelist = piecelist_list[i]
+            y = i * lineheight + linepadding # the y-coordinate that we're drawing at
+
+            # This makes each row white, on a black grid (the previous background showing through)
+            s.write('<svg:rect x="0" y="%spx" width="%spx" height="%spx" fill="white" />\n'
+                % (y, imagewidth * markwidth, markheight))
+
+            # Set default drawing color
+            if (i < len(color_list)):
+                color = color_list[i]
+            s.write('<svg:g fill="%s">\n' % (color))
+
+            # Clump the data together into runs of "we have this piece" and "we lack this piece"
+            runstart = 0
+            runtype = piecelist[0]
+            for piece in xrange (1, len(piecelist)):
+                if (piecelist[piece] != runtype):
+                    # Just past the end of the current run.  Draw it if it was a "we have" run.
+                    if (runtype):
+                        s.write('<svg:rect x="%spx" y="%spx" width="%spx" height="%spx" title="%s-%s" />\n'
+                            % (pixellist[runstart] * markwidth, y, (pixellist[piece]-pixellist[runstart]) * markwidth, markheight,
+                            runstart, piece-1))
+
+                    # Now start counting the new run.
+                    runstart = piece
+                    runtype = piecelist[piece]
+
+            # Reached the end of the data, draw the final run
+            if (runtype):
+                s.write('<svg:rect x="%spx" y="%spx" width="%spx" height="%spx" title="%s-%s" />\n'
+                    % (pixellist[runstart] * markwidth, y, (pixellist[piece]-pixellist[runstart]) * markwidth, markheight,
+                    runstart, piece-1))
+            else:
+                # Show how far the "no pieces" bit is
+                s.write('<svg:rect x="%spx" y="%spx" width="%spx" height="%spx" fill="grey" />\n'
+                    % (pixellist[runstart] * markwidth, y, (pixellist[piece]-pixellist[runstart]) * markwidth, markheight))
+
+            s.write("</svg:g>\n\n")
+
+        s.write('</svg:svg>')
+
+
     def get_meow(self):
         return (200, 'OK', {'Server': VERSION, 'Content-Type': 'text/html; charset=iso-8859-1'}, """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd";>\n<html><head><title>Meow</title>\n</head>\n<body style="color: rgb(255, 255, 255); background-color: rgb(0, 0, 0);">\n<div><big style="font-weight: bold;"><big><big><span style="font-family: arial,helvetica,sans-serif;">I&nbsp;IZ&nbsp;TAKIN&nbsp;BRAKE</span></big></big></big><br></div>\n<pre><b><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .-o=o-.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ,&nbsp; /=o=o=o=\ .--.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _|\|=o=O=o=O=|&nbsp;&nbsp;&nbsp; \<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __.'&nbsp; a`\=o=o=o=(`\&nbsp;&nbsp; /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; '.&nbsp;&nbsp; a 4/`|.-""'`\ \ ;'`)&nbsp;&nbsp; .---.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \&nbsp;&nbsp; .'&nbsp; /&nbsp;&nbsp; .--'&nbsp; |_.'&nbsp;&nbsp; / .-._)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `)&nbsp; _.'&nbsp;&nbsp; /&nbsp;&nbsp;&nbsp;&nbsp; /`-.__.' /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `'-.____;&nbsp;&nbsp;&nbsp;&nbsp; /'-.___.-'<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `\"""`</tt></b></pre>\n<div><big style="font-weight: bold;"><big><big><span style="font-family: arial,helvetica,sans-serif;">FRM&nbsp;GETIN&nbsp;UR&nbsp;PACKAGES</span></big></big></big><br></div>\n</body>\n</html>""")
 
@@ -787,9 +966,12 @@ class AptListener:
                     kw = unquote(s[:i])
                     paramslist.setdefault(kw, [])
                     paramslist[kw] += [unquote(s[i+1:])]
+                    logger.debug('paramslist['+str(kw)+'] =='+str(paramslist[kw]))
                     
             if path == '' or path == 'index.html':
-                return self.get_infopage()
+                return self.get_infopage(params('piecemaps'))
+            if path == 'piecemap.svg':
+                return self.get_piecemap_for_torrent_svg(params('info_hash'))
             if path == 'meow':
                 return self.get_meow()
             if path == 'favicon.ico':
diff --git a/DebTorrent/launchmanycore.py b/DebTorrent/launchmanycore.py
index a1bdf3b..c60b5c7 100644
--- a/DebTorrent/launchmanycore.py
+++ b/DebTorrent/launchmanycore.py
@@ -35,6 +35,7 @@ from cStringIO import StringIO
 import logging
 from DebTorrent.BT1.AptListener import AptListener
 from DebTorrent.HTTPHandler import HTTPHandler
+from binascii import b2a_hex, a2b_hex
 
 logger = logging.getLogger('DebTorrent.launchmanycore')
 
@@ -497,6 +498,38 @@ class LaunchMany:
 
         return data
 
+    def get_piecemap(self, id):
+        """Return the piecemap (which peers have what) for a given torrent.
+
+        @type id: C{string}
+        @param id: Info hash for the torrent, as provided by gather_stats.
+
+        Internal: The "id" argument should match the identifier that
+        gather_stats provides in data[][1].  As of revision 373 it provides the
+        long-lived torrent ID, not the hash.
+
+        @rtype: C{list}
+        @return: List of piecemaps.  The first will be for the local peer, then the other peers in undefined order.
+
+        """
+
+        hash = None
+        for search_hash in self.torrent_list:
+            if self.torrent_cache[search_hash]['metainfo'].get('identifier', search_hash) == id:
+                hash = search_hash
+
+        if hash == None:
+            logger.warning('Could not find hash for id %s\n' % b2a_hex(id))
+            return None
+
+        piecelist_list = []
+        piecelist_list.append(self.downloads[hash].d.storagewrapper.have)
+        if (self.downloads[hash].d.downloader != None):
+            for peer in self.downloads[hash].d.downloader.downloads:
+                piecelist_list.append(peer.have)
+
+        return piecelist_list
+
     def remove(self, hash):
         """Stop and remove a running torrent.
         

Reply via email to