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.
         

