r415 - in /debtorrent/trunk: DebTorrent/BT1/AptListener.py DebTorrent/launchmanycore.py debian/changelog

camrdale at users.alioth.debian.org camrdale at users.alioth.debian.org
Thu Sep 17 03:37:08 UTC 2009


Author: camrdale
Date: Thu Sep 17 03:37:07 2009
New Revision: 415

URL: http://svn.debian.org/wsvn/debtorrent/?sc=1&rev=415
Log:
Add a graphical display of the pieces all peers have, thanks 
Steve Cotton (Closes: #515771)

Modified:
    debtorrent/trunk/DebTorrent/BT1/AptListener.py
    debtorrent/trunk/DebTorrent/launchmanycore.py
    debtorrent/trunk/debian/changelog

Modified: debtorrent/trunk/DebTorrent/BT1/AptListener.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/BT1/AptListener.py?rev=415&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/BT1/AptListener.py (original)
+++ debtorrent/trunk/DebTorrent/BT1/AptListener.py Thu Sep 17 03:37:07 2009
@@ -25,7 +25,7 @@
 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 @@
         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,12 +327,188 @@
                     '<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())
         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_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):
@@ -787,9 +966,12 @@
                     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':

Modified: debtorrent/trunk/DebTorrent/launchmanycore.py
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/DebTorrent/launchmanycore.py?rev=415&op=diff
==============================================================================
--- debtorrent/trunk/DebTorrent/launchmanycore.py (original)
+++ debtorrent/trunk/DebTorrent/launchmanycore.py Thu Sep 17 03:37:07 2009
@@ -35,6 +35,7 @@
 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 @@
 
         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.
         

Modified: debtorrent/trunk/debian/changelog
URL: http://svn.debian.org/wsvn/debtorrent/debtorrent/trunk/debian/changelog?rev=415&op=diff
==============================================================================
--- debtorrent/trunk/debian/changelog (original)
+++ debtorrent/trunk/debian/changelog Thu Sep 17 03:37:07 2009
@@ -1,11 +1,13 @@
-debtorrent (0.1.9.1) unstable; urgency=low
+debtorrent (0.2.0) unstable; urgency=low
 
   * Prevent symlink attack when upgrading from pre 0.1.6 (Closes: #500180)
   * Add a status verb to init.d scripts (Closes: #498606)
   * Fix exception when server doesn't return modified time (Closes: #515753)
   * Fix piuparts uninstallation failure
+  * Add a graphical display of the pieces all peers have, thanks 
+    Steve Cotton (Closes: #515771)
 
- -- Cameron Dale <camrdale at gmail.com>  Tue, 08 Sep 2009 23:16:21 -0700
+ -- Cameron Dale <camrdale at gmail.com>  Wed, 16 Sep 2009 20:02:25 -0700
 
 debtorrent (0.1.9) unstable; urgency=low
 




More information about the Debtorrent-commits mailing list