diff -pruN 0.3.18-10/BitTornado/BT1/makemetafile.py 0.3.18-10ubuntu3/BitTornado/BT1/makemetafile.py
--- 0.3.18-10/BitTornado/BT1/makemetafile.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/BT1/makemetafile.py	2011-12-31 04:28:59.000000000 +0000
@@ -4,7 +4,10 @@
 
 from os.path import getsize, split, join, abspath, isdir
 from os import listdir
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from copy import copy
 from string import strip
 from BitTornado.bencode import bencode
diff -pruN 0.3.18-10/BitTornado/BT1/Rerequester.py 0.3.18-10ubuntu3/BitTornado/BT1/Rerequester.py
--- 0.3.18-10/BitTornado/BT1/Rerequester.py	2006-12-22 00:09:50.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/BT1/Rerequester.py	2011-12-31 04:28:59.000000000 +0000
@@ -12,7 +12,10 @@ from cStringIO import StringIO
 from traceback import print_exc
 from socket import error, gethostbyname
 from random import shuffle
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from time import time
 try:
     from os import getpid
diff -pruN 0.3.18-10/BitTornado/BT1/StorageWrapper.py 0.3.18-10ubuntu3/BitTornado/BT1/StorageWrapper.py
--- 0.3.18-10/BitTornado/BT1/StorageWrapper.py	2006-03-04 03:53:16.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/BT1/StorageWrapper.py	2011-12-31 04:28:59.000000000 +0000
@@ -2,7 +2,10 @@
 # see LICENSE.txt for license information
 
 from BitTornado.bitfield import Bitfield
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from BitTornado.clock import clock
 from traceback import print_exc
 from random import randrange
diff -pruN 0.3.18-10/BitTornado/BT1/track.py 0.3.18-10ubuntu3/BitTornado/BT1/track.py
--- 0.3.18-10/BitTornado/BT1/track.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/BT1/track.py	2011-12-31 04:28:59.000000000 +0000
@@ -23,7 +23,6 @@ from traceback import print_exc
 from time import time, gmtime, strftime, localtime
 from BitTornado.clock import clock
 from random import shuffle, seed, randrange
-from sha import sha
 from types import StringType, IntType, LongType, ListType, DictType
 from binascii import b2a_hex, a2b_hex, a2b_base64
 from string import lower
diff -pruN 0.3.18-10/BitTornado/BTcrypto.py 0.3.18-10ubuntu3/BitTornado/BTcrypto.py
--- 0.3.18-10/BitTornado/BTcrypto.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/BTcrypto.py	2011-12-31 04:28:59.000000000 +0000
@@ -10,7 +10,10 @@ try:
 except:
     seed()
     urandom = lambda x: ''.join([chr(randint(0,255)) for i in xrange(x)])
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 
 try:
     True
diff -pruN 0.3.18-10/BitTornado/download_bt1.py 0.3.18-10ubuntu3/BitTornado/download_bt1.py
--- 0.3.18-10/BitTornado/download_bt1.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/download_bt1.py	2011-12-31 04:28:59.000000000 +0000
@@ -24,7 +24,10 @@ from BT1.Statistics import Statistics
 from ConfigDir import ConfigDir
 from bencode import bencode, bdecode
 from natpunch import UPnP_test
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from os import path, makedirs, listdir
 from parseargs import parseargs, formatDefinitions, defaultargs
 from socket import error as socketerror
diff -pruN 0.3.18-10/BitTornado/__init__.py 0.3.18-10ubuntu3/BitTornado/__init__.py
--- 0.3.18-10/BitTornado/__init__.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/__init__.py	2011-12-31 04:28:59.000000000 +0000
@@ -5,7 +5,10 @@ version = version_short+' ('+product_nam
 report_email = 'http://www.debian.org/Bugs/Reporting'
 
 from types import StringType
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from time import time, clock
 try:
     from os import getpid
diff -pruN 0.3.18-10/BitTornado/parsedir.py 0.3.18-10ubuntu3/BitTornado/parsedir.py
--- 0.3.18-10/BitTornado/parsedir.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/BitTornado/parsedir.py	2011-12-31 04:28:59.000000000 +0000
@@ -3,7 +3,10 @@
 from bencode import bencode, bdecode
 from BT1.btformats import check_info
 from os.path import exists, isfile
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 import sys, os
 
 try:
diff -pruN 0.3.18-10/btdownloadcurses.py 0.3.18-10ubuntu3/btdownloadcurses.py
--- 0.3.18-10/btdownloadcurses.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/btdownloadcurses.py	2011-12-31 04:28:59.000000000 +0000
@@ -23,7 +23,10 @@ from BitTornado.natpunch import UPnP_tes
 from threading import Event
 from os.path import abspath
 from signal import signal, SIGWINCH
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from sys import argv, exit
 import sys
 from time import time, strftime
diff -pruN 0.3.18-10/btdownloadgui.py 0.3.18-10ubuntu3/btdownloadgui.py
--- 0.3.18-10/btdownloadgui.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/btdownloadgui.py	2011-12-31 04:28:59.000000000 +0000
@@ -37,7 +37,10 @@ from BitTornado.clock import clock
 from webbrowser import open_new
 from traceback import print_exc
 from StringIO import StringIO
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 import re
 import sys, os
 from BitTornado import version, createPeerID, report_email
diff -pruN 0.3.18-10/btdownloadheadless.py 0.3.18-10ubuntu3/btdownloadheadless.py
--- 0.3.18-10/btdownloadheadless.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/btdownloadheadless.py	2011-12-31 04:28:59.000000000 +0000
@@ -22,7 +22,10 @@ from threading import Event
 from os.path import abspath
 from sys import argv, stdout
 import sys
-from sha import sha
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from time import strftime
 from BitTornado.clock import clock
 from BitTornado import createPeerID, version
diff -pruN 0.3.18-10/btrename.py 0.3.18-10ubuntu3/btrename.py
--- 0.3.18-10/btrename.py	2006-12-23 18:20:56.000000000 +0000
+++ 0.3.18-10ubuntu3/btrename.py	2011-12-31 04:28:59.000000000 +0000
@@ -5,7 +5,10 @@
 
 from sys import *
 from os.path import *
-from sha import *
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from BitTornado.bencode import *
 
 NAME, EXT = splitext(basename(argv[0]))
diff -pruN 0.3.18-10/btshowmetainfo.py 0.3.18-10ubuntu3/btshowmetainfo.py
--- 0.3.18-10/btshowmetainfo.py	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/btshowmetainfo.py	2011-12-31 04:28:59.000000000 +0000
@@ -6,7 +6,10 @@
 
 from sys import *
 from os.path import *
-from sha import *
+try:
+    from hashlib import sha1 as sha
+except ImportError:
+    from sha import sha
 from BitTornado.bencode import *
 
 NAME, EXT = splitext(basename(argv[0]))
diff -pruN 0.3.18-10/debian/bittornado-gui.install 0.3.18-10ubuntu3/debian/bittornado-gui.install
--- 0.3.18-10/debian/bittornado-gui.install	2007-08-18 21:47:51.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/bittornado-gui.install	2011-08-10 23:19:20.000000000 +0000
@@ -1,2 +1,3 @@
-usr/bin/*gui*
+usr/bin/*gui*.bittornado
+usr/bin/btmaketorrentgui
 usr/share/pixmaps
diff -pruN 0.3.18-10/debian/bittornado.install 0.3.18-10ubuntu3/debian/bittornado.install
--- 0.3.18-10/debian/bittornado.install	2007-04-17 17:17:01.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/bittornado.install	2011-08-10 23:19:20.000000000 +0000
@@ -10,4 +10,4 @@ usr/bin/btrename.bittornado
 usr/bin/btsethttpseeds
 usr/bin/btshowmetainfo.bittornado
 usr/bin/bttrack.bittornado
-usr/share/python-support/bittornado
+usr/lib/python2*
diff -pruN 0.3.18-10/debian/bittornado.pyinstall 0.3.18-10ubuntu3/debian/bittornado.pyinstall
--- 0.3.18-10/debian/bittornado.pyinstall	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/bittornado.pyinstall	2011-08-10 23:19:20.000000000 +0000
@@ -0,0 +1,2 @@
+debian/tmp/usr/bin/btmakemetafile.py BitTornado
+debian/tmp/usr/bin/btcompletedir.py BitTornado
diff -pruN 0.3.18-10/debian/changelog 0.3.18-10ubuntu3/debian/changelog
--- 0.3.18-10/debian/changelog	2010-03-21 21:36:58.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/changelog	2011-12-31 02:00:57.000000000 +0000
@@ -1,3 +1,26 @@
+bittornado (0.3.18-10ubuntu3) precise; urgency=low
+
+  * Rebuild to drop python2.6 dependencies.
+
+ -- Matthias Klose <doko@ubuntu.com>  Sat, 31 Dec 2011 02:00:57 +0000
+
+bittornado (0.3.18-10ubuntu2) oneiric; urgency=low
+
+  * switch to dh_python2 (LP: #788514)
+    - install btmakemetafile.py and btcompletedir.py via pyinstall
+    - add build depend on python-all
+    - bump debhelper depend to 7 for dh_auto_install
+
+ -- Julian Taylor <jtaylor.debian@googlemail.com>  Wed, 10 Aug 2011 23:02:19 +0200
+
+bittornado (0.3.18-10ubuntu1) natty; urgency=low
+
+  * debian/patches/32_use_hashlib_for_sha.patch:
+    - Updated use of deprecated sha module to hashlib. (LP: #420387,
+    Closes: #593653) 
+
+ -- Ronny Cardona (Rcart) <rcart1019@gmail.com>  Mon, 24 Jan 2011 17:27:47 -0600
+
 bittornado (0.3.18-10) unstable; urgency=low
 
   * New patch from upstream's CVS to allow torrents that only have an
diff -pruN 0.3.18-10/debian/control 0.3.18-10ubuntu3/debian/control
--- 0.3.18-10/debian/control	2010-03-21 21:16:54.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/control	2011-08-10 23:19:20.000000000 +0000
@@ -1,9 +1,10 @@
 Source: bittornado
 Section: net
 Priority: optional
-Maintainer: Cameron Dale <camrdale@gmail.com>
-Build-Depends: debhelper (>= 5.0.37.2)
-Build-Depends-Indep: python, python-support (>= 0.5.4), docbook-to-man
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> 
+XSBC-Original-Maintainer: Cameron Dale <camrdale@gmail.com> 
+Build-Depends: debhelper (>= 7)
+Build-Depends-Indep: python-all (>= 2.6.6-3~), docbook-to-man
 Standards-Version: 3.8.4
 Vcs-Svn: svn://svn.debian.org/pkg-bittornado/bittornado/trunk
 Vcs-Browser: http://svn.debian.org/wsvn/pkg-bittornado/bittornado/trunk/
diff -pruN 0.3.18-10/debian/patches/32_use_hashlib_for_sha.patch 0.3.18-10ubuntu3/debian/patches/32_use_hashlib_for_sha.patch
--- 0.3.18-10/debian/patches/32_use_hashlib_for_sha.patch	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/patches/32_use_hashlib_for_sha.patch	2011-08-10 23:02:06.000000000 +0000
@@ -0,0 +1,210 @@
+From: Ronny Cardona (Rcart) <rcart1019@gmail.com>
+Description: Updated use of deprecated sha module to hashlib.
+Origin: http://bugs.debian.org/593653#17
+Bug-Debian: http://bugs.debian.org/593653
+Bug-Ubuntu: https://launchpad.net/bugs/420387
+
+Index: bittornado.fix-420387/BitTornado/BT1/makemetafile.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/BT1/makemetafile.py	2011-01-24 17:18:09.183076000 -0600
++++ bittornado.fix-420387/BitTornado/BT1/makemetafile.py	2011-01-24 17:18:55.483076002 -0600
+@@ -4,7 +4,10 @@
+ 
+ from os.path import getsize, split, join, abspath, isdir
+ from os import listdir
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from copy import copy
+ from string import strip
+ from BitTornado.bencode import bencode
+Index: bittornado.fix-420387/BitTornado/BT1/Rerequester.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/BT1/Rerequester.py	2011-01-24 17:18:09.083076000 -0600
++++ bittornado.fix-420387/BitTornado/BT1/Rerequester.py	2011-01-24 17:18:55.483076002 -0600
+@@ -12,7 +12,10 @@
+ from traceback import print_exc
+ from socket import error, gethostbyname
+ from random import shuffle
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from time import time
+ try:
+     from os import getpid
+Index: bittornado.fix-420387/BitTornado/BT1/StorageWrapper.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/BT1/StorageWrapper.py	2011-01-24 17:18:09.383076000 -0600
++++ bittornado.fix-420387/BitTornado/BT1/StorageWrapper.py	2011-01-24 17:18:55.487076002 -0600
+@@ -2,7 +2,10 @@
+ # see LICENSE.txt for license information
+ 
+ from BitTornado.bitfield import Bitfield
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from BitTornado.clock import clock
+ from traceback import print_exc
+ from random import randrange
+Index: bittornado.fix-420387/BitTornado/BT1/track.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/BT1/track.py	2011-01-24 17:18:09.271076000 -0600
++++ bittornado.fix-420387/BitTornado/BT1/track.py	2011-01-24 17:18:55.487076002 -0600
+@@ -23,7 +23,6 @@
+ from time import time, gmtime, strftime, localtime
+ from BitTornado.clock import clock
+ from random import shuffle, seed, randrange
+-from sha import sha
+ from types import StringType, IntType, LongType, ListType, DictType
+ from binascii import b2a_hex, a2b_hex, a2b_base64
+ from string import lower
+Index: bittornado.fix-420387/BitTornado/BTcrypto.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/BTcrypto.py	2011-01-24 17:18:09.491076000 -0600
++++ bittornado.fix-420387/BitTornado/BTcrypto.py	2011-01-24 17:18:55.491076002 -0600
+@@ -10,7 +10,10 @@
+ except:
+     seed()
+     urandom = lambda x: ''.join([chr(randint(0,255)) for i in xrange(x)])
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ 
+ try:
+     True
+Index: bittornado.fix-420387/BitTornado/download_bt1.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/download_bt1.py	2011-01-24 17:18:08.867076000 -0600
++++ bittornado.fix-420387/BitTornado/download_bt1.py	2011-01-24 17:18:55.491076002 -0600
+@@ -24,7 +24,10 @@
+ from ConfigDir import ConfigDir
+ from bencode import bencode, bdecode
+ from natpunch import UPnP_test
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from os import path, makedirs, listdir
+ from parseargs import parseargs, formatDefinitions, defaultargs
+ from socket import error as socketerror
+Index: bittornado.fix-420387/BitTornado/__init__.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/__init__.py	2011-01-24 17:18:08.771076000 -0600
++++ bittornado.fix-420387/BitTornado/__init__.py	2011-01-24 17:18:55.491076002 -0600
+@@ -5,7 +5,10 @@
+ report_email = 'http://www.debian.org/Bugs/Reporting'
+ 
+ from types import StringType
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from time import time, clock
+ try:
+     from os import getpid
+Index: bittornado.fix-420387/BitTornado/parsedir.py
+===================================================================
+--- bittornado.fix-420387.orig/BitTornado/parsedir.py	2011-01-24 17:18:08.979076000 -0600
++++ bittornado.fix-420387/BitTornado/parsedir.py	2011-01-24 17:18:55.495076002 -0600
+@@ -3,7 +3,10 @@
+ from bencode import bencode, bdecode
+ from BT1.btformats import check_info
+ from os.path import exists, isfile
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ import sys, os
+ 
+ try:
+Index: bittornado.fix-420387/btdownloadcurses.py
+===================================================================
+--- bittornado.fix-420387.orig/btdownloadcurses.py	2011-01-24 17:18:09.583076000 -0600
++++ bittornado.fix-420387/btdownloadcurses.py	2011-01-24 17:18:55.495076002 -0600
+@@ -23,7 +23,10 @@
+ from threading import Event
+ from os.path import abspath
+ from signal import signal, SIGWINCH
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from sys import argv, exit
+ import sys
+ from time import time, strftime
+Index: bittornado.fix-420387/btdownloadgui.py
+===================================================================
+--- bittornado.fix-420387.orig/btdownloadgui.py	2011-01-24 17:18:08.579076000 -0600
++++ bittornado.fix-420387/btdownloadgui.py	2011-01-24 17:18:55.503076002 -0600
+@@ -37,7 +37,10 @@
+ from webbrowser import open_new
+ from traceback import print_exc
+ from StringIO import StringIO
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ import re
+ import sys, os
+ from BitTornado import version, createPeerID, report_email
+Index: bittornado.fix-420387/btdownloadheadless.py
+===================================================================
+--- bittornado.fix-420387.orig/btdownloadheadless.py	2011-01-24 17:18:08.671076000 -0600
++++ bittornado.fix-420387/btdownloadheadless.py	2011-01-24 17:18:55.519076002 -0600
+@@ -22,7 +22,10 @@
+ from os.path import abspath
+ from sys import argv, stdout
+ import sys
+-from sha import sha
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from time import strftime
+ from BitTornado.clock import clock
+ from BitTornado import createPeerID, version
+Index: bittornado.fix-420387/btrename.py
+===================================================================
+--- bittornado.fix-420387.orig/btrename.py	2011-01-24 17:18:08.475076000 -0600
++++ bittornado.fix-420387/btrename.py	2011-01-24 17:18:55.523076002 -0600
+@@ -5,7 +5,10 @@
+ 
+ from sys import *
+ from os.path import *
+-from sha import *
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from BitTornado.bencode import *
+ 
+ NAME, EXT = splitext(basename(argv[0]))
+Index: bittornado.fix-420387/btshowmetainfo.py
+===================================================================
+--- bittornado.fix-420387.orig/btshowmetainfo.py	2011-01-24 17:18:08.415076000 -0600
++++ bittornado.fix-420387/btshowmetainfo.py	2011-01-24 17:18:55.523076002 -0600
+@@ -6,7 +6,10 @@
+ 
+ from sys import *
+ from os.path import *
+-from sha import *
++try:
++    from hashlib import sha1 as sha
++except ImportError:
++    from sha import sha
+ from BitTornado.bencode import *
+ 
+ NAME, EXT = splitext(basename(argv[0]))
diff -pruN 0.3.18-10/debian/patches/series 0.3.18-10ubuntu3/debian/patches/series
--- 0.3.18-10/debian/patches/series	2010-03-21 20:04:55.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/patches/series	2011-08-10 23:02:06.000000000 +0000
@@ -27,3 +27,4 @@
 29_fix_urandom_error.dpatch
 30_announce_list_only_torrents.dpatch
 31_fix_for_compact_reqd_off.dpatch
+32_use_hashlib_for_sha.patch
diff -pruN 0.3.18-10/debian/rules 0.3.18-10ubuntu3/debian/rules
--- 0.3.18-10/debian/rules	2010-03-21 20:57:39.000000000 +0000
+++ 0.3.18-10ubuntu3/debian/rules	2011-08-10 23:19:20.000000000 +0000
@@ -31,23 +31,14 @@ install: build-stamp
 	dh_installdirs
 
 	# Add here commands to install the package into debian/bittornado.
-	chmod +x ./setup.py
-	./setup.py install --prefix=$(CURDIR)/debian/tmp/usr --install-lib=$(CURDIR)/debian/tmp/usr/share/python-support/bittornado
+	dh_auto_install -- --root=$(CURDIR)/debian/tmp
 
 	install -d debian/tmp/usr/share/mimelnk/application
 
 	install -D -m644 debian/bittornado.desktop debian/bittornado-gui/usr/share/applications/bittornado.desktop
 	install -D -m644 debian/bittornado.xpm debian/tmp/usr/share/pixmaps/bittornado.xpm
 	install -d debian/tmp/usr/share/bittorrent
-	chmod 755 debian/tmp/usr/share/python-support/bittornado/BitTornado/launchmanycore.py
-	cp debian/tmp/usr/bin/btmakemetafile.py debian/tmp/usr/share/python-support/bittornado/BitTornado
-	cp debian/tmp/usr/bin/btcompletedir.py debian/tmp/usr/share/python-support/bittornado/BitTornado
-
-	# Remove the generated .pyc files
-	( cd debian/tmp/usr/share/python-support/bittornado/BitTornado && \
-	  rm -f *.pyc  && \
-	  cd BT1 && \
-	  rm -f *.pyc )
+
 
 # Build architecture-independent files here.
 binary-indep: build install
@@ -59,14 +50,15 @@ binary-indep: build install
 	dh_installmime
 	dh_installman
 	
-	# Remove the .py from the end of each of these and add .bittornado
-	# so that the alternatives system can work
+	# create versions with .bittornado suffix so that the alternatives
+	# system can work. the .py extensions are not installed.
+	# btmakemetafile.py and btcompletedir.py is needed for pyinstall
 	( cd debian/tmp/usr/bin && \
 	  for i in btdownloadgui btdownloadheadless bttrack btmakemetafile \
 	  	   btlaunchmany btcompletedir btcompletedirgui \
 		   btdownloadcurses btlaunchmanycurses btreannounce btrename \
 		   btshowmetainfo; \
-	  do mv $$i.py $$i.bittornado || exit 1 ; done )
+	  do cp $$i.py $$i.bittornado || exit 1 ; done )
 	
 	# Remove the .py from the end of each of these, the following are not
 	# included in bittorrent, so they are not renamed to having the
@@ -80,7 +72,7 @@ binary-indep: build install
 	dh_link
 	dh_compress
 	dh_fixperms
-	dh_pysupport
+	dh_python2
 	dh_installdeb
 	dh_shlibdeps
 	dh_gencontrol
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/makemetafile.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/makemetafile.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/makemetafile.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/makemetafile.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,264 @@
+# Written by Bram Cohen
+# multitracker extensions by John Hoffman
+# see LICENSE.txt for license information
+
+from os.path import getsize, split, join, abspath, isdir
+from os import listdir
+from sha import sha
+from copy import copy
+from string import strip
+from BitTornado.bencode import bencode
+from btformats import check_info
+from threading import Event
+from time import time
+from traceback import print_exc
+try:
+    from sys import getfilesystemencoding
+    ENCODING = getfilesystemencoding()
+except:
+    from sys import getdefaultencoding
+    ENCODING = getdefaultencoding()
+
+defaults = [
+    ('announce_list', '',
+        'a list of announce URLs - explained below'),
+    ('httpseeds', '',
+        'a list of http seed URLs - explained below'),
+    ('piece_size_pow2', 0,
+        "which power of 2 to set the piece size to (0 = automatic)"),
+    ('comment', '',
+        "optional human-readable comment to put in .torrent"),
+    ('filesystem_encoding', '',
+        "optional specification for filesystem encoding " +
+        "(set automatically in recent Python versions)"),
+    ('target', '',
+        "optional target file for the torrent")
+    ]
+
+default_piece_len_exp = 18
+
+ignore = ['core', 'CVS']
+
+def print_announcelist_details():
+    print ('    announce_list = optional list of redundant/backup tracker URLs, in the format:')
+    print ('           url[,url...][|url[,url...]...]')
+    print ('                where URLs separated by commas are all tried first')
+    print ('                before the next group of URLs separated by the pipe is checked.')
+    print ("                If none is given, it is assumed you don't want one in the metafile.")
+    print ('                If announce_list is given, clients which support it')
+    print ('                will ignore the <announce> value.')
+    print ('           Examples:')
+    print ('                http://tracker1.com|http://tracker2.com|http://tracker3.com')
+    print ('                     (tries trackers 1-3 in order)')
+    print ('                http://tracker1.com,http://tracker2.com,http://tracker3.com')
+    print ('                     (tries trackers 1-3 in a randomly selected order)')
+    print ('                http://tracker1.com|http://backup1.com,http://backup2.com')
+    print ('                     (tries tracker 1 first, then tries between the 2 backups randomly)')
+    print ('')
+    print ('    httpseeds = optional list of http-seed URLs, in the format:')
+    print ('            url[|url...]')
+    
+def make_meta_file(file, url, params = {}, flag = Event(),
+                   progress = lambda x: None, progress_percent = 1):
+    if params.has_key('piece_size_pow2'):
+        piece_len_exp = params['piece_size_pow2']
+    else:
+        piece_len_exp = default_piece_len_exp
+    if params.has_key('target') and params['target'] != '':
+        f = params['target']
+    else:
+        a, b = split(file)
+        if b == '':
+            f = a + '.torrent'
+        else:
+            f = join(a, b + '.torrent')
+            
+    if piece_len_exp == 0:  # automatic
+        size = calcsize(file)
+        if   size > 8L*1024*1024*1024:   # > 8 gig =
+            piece_len_exp = 21          #   2 meg pieces
+        elif size > 2*1024*1024*1024:   # > 2 gig =
+            piece_len_exp = 20          #   1 meg pieces
+        elif size > 512*1024*1024:      # > 512M =
+            piece_len_exp = 19          #   512K pieces
+        elif size > 64*1024*1024:       # > 64M =
+            piece_len_exp = 18          #   256K pieces
+        elif size > 16*1024*1024:       # > 16M =
+            piece_len_exp = 17          #   128K pieces
+        elif size > 4*1024*1024:        # > 4M =
+            piece_len_exp = 16          #   64K pieces
+        else:                           # < 4M =
+            piece_len_exp = 15          #   32K pieces
+    piece_length = 2 ** piece_len_exp
+
+    encoding = None
+    if params.has_key('filesystem_encoding'):
+        encoding = params['filesystem_encoding']
+    if not encoding:
+        encoding = ENCODING
+    if not encoding:
+        encoding = 'ascii'
+    
+    info = makeinfo(file, piece_length, encoding, flag, progress, progress_percent)
+    if flag.isSet():
+        return
+    check_info(info)
+    h = open(f, 'wb')
+    data = {'info': info, 'announce': strip(url), 'creation date': long(time())}
+    
+    if params.has_key('comment') and params['comment']:
+        data['comment'] = params['comment']
+        
+    if params.has_key('real_announce_list'):    # shortcut for progs calling in from outside
+        data['announce-list'] = params['real_announce_list']
+    elif params.has_key('announce_list') and params['announce_list']:
+        l = []
+        for tier in params['announce_list'].split('|'):
+            l.append(tier.split(','))
+        data['announce-list'] = l
+        
+    if params.has_key('real_httpseeds'):    # shortcut for progs calling in from outside
+        data['httpseeds'] = params['real_httpseeds']
+    elif params.has_key('httpseeds') and params['httpseeds']:
+        data['httpseeds'] = params['httpseeds'].split('|')
+        
+    h.write(bencode(data))
+    h.close()
+
+def calcsize(file):
+    if not isdir(file):
+        return getsize(file)
+    total = 0L
+    for s in subfiles(abspath(file)):
+        total += getsize(s[1])
+    return total
+
+
+def uniconvertl(l, e):
+    r = []
+    try:
+        for s in l:
+            r.append(uniconvert(s, e))
+    except UnicodeError:
+        raise UnicodeError('bad filename: '+join(*l))
+    return r
+
+def uniconvert(s, e):
+    try:
+        if s.__class__.__name__ != 'unicode':
+            s = unicode(s,e)
+    except UnicodeError:
+        raise UnicodeError('bad filename: '+s)
+    return s.encode('utf-8')
+
+def makeinfo(file, piece_length, encoding, flag, progress, progress_percent=1):
+    file = abspath(file)
+    if isdir(file):
+        subs = subfiles(file)
+        subs.sort()
+        pieces = []
+        sh = sha()
+        done = 0L
+        fs = []
+        totalsize = 0.0
+        totalhashed = 0L
+        for p, f in subs:
+            totalsize += getsize(f)
+
+        for p, f in subs:
+            pos = 0L
+            size = getsize(f)
+            fs.append({'length': size, 'path': uniconvertl(p, encoding)})
+            h = open(f, 'rb')
+            while pos < size:
+                a = min(size - pos, piece_length - done)
+                sh.update(h.read(a))
+                if flag.isSet():
+                    return
+                done += a
+                pos += a
+                totalhashed += a
+                
+                if done == piece_length:
+                    pieces.append(sh.digest())
+                    done = 0
+                    sh = sha()
+                if progress_percent:
+                    progress(totalhashed / totalsize)
+                else:
+                    progress(a)
+            h.close()
+        if done > 0:
+            pieces.append(sh.digest())
+        return {'pieces': ''.join(pieces),
+            'piece length': piece_length, 'files': fs, 
+            'name': uniconvert(split(file)[1], encoding) }
+    else:
+        size = getsize(file)
+        pieces = []
+        p = 0L
+        h = open(file, 'rb')
+        while p < size:
+            x = h.read(min(piece_length, size - p))
+            if flag.isSet():
+                return
+            pieces.append(sha(x).digest())
+            p += piece_length
+            if p > size:
+                p = size
+            if progress_percent:
+                progress(float(p) / size)
+            else:
+                progress(min(piece_length, size - p))
+        h.close()
+        return {'pieces': ''.join(pieces), 
+            'piece length': piece_length, 'length': size, 
+            'name': uniconvert(split(file)[1], encoding) }
+
+def subfiles(d):
+    r = []
+    stack = [([], d)]
+    while len(stack) > 0:
+        p, n = stack.pop()
+        if isdir(n):
+            for s in listdir(n):
+                if s not in ignore and s[:1] != '.':
+                    stack.append((copy(p) + [s], join(n, s)))
+        else:
+            r.append((p, n))
+    return r
+
+
+def completedir(dir, url, params = {}, flag = Event(),
+                vc = lambda x: None, fc = lambda x: None):
+    files = listdir(dir)
+    files.sort()
+    ext = '.torrent'
+    if params.has_key('target'):
+        target = params['target']
+    else:
+        target = ''
+
+    togen = []
+    for f in files:
+        if f[-len(ext):] != ext and (f + ext) not in files:
+            togen.append(join(dir, f))
+        
+    total = 0
+    for i in togen:
+        total += calcsize(i)
+
+    subtotal = [0]
+    def callback(x, subtotal = subtotal, total = total, vc = vc):
+        subtotal[0] += x
+        vc(float(subtotal[0]) / total)
+    for i in togen:
+        fc(i)
+        try:
+            t = split(i)[-1]
+            if t not in ignore and t[0] != '.':
+                if target != '':
+                    params['target'] = join(target,t+ext)
+                make_meta_file(i, url, params, flag, progress = callback, progress_percent = 0)
+        except ValueError:
+            print_exc()
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/Rerequester.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/Rerequester.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/Rerequester.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/Rerequester.py	2006-12-22 00:09:50.000000000 +0000
@@ -0,0 +1,462 @@
+# Written by Bram Cohen
+# modified for multitracker operation by John Hoffman
+# see LICENSE.txt for license information
+
+from BitTornado.zurllib import urlopen, quote
+from urlparse import urlparse, urlunparse
+from socket import gethostbyname
+from btformats import check_peers
+from BitTornado.bencode import bdecode
+from threading import Thread, Lock
+from cStringIO import StringIO
+from traceback import print_exc
+from socket import error, gethostbyname
+from random import shuffle
+from sha import sha
+from time import time
+try:
+    from os import getpid
+except ImportError:
+    def getpid():
+        return 1
+    
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
+keys = {}
+basekeydata = str(getpid()) + repr(time()) + 'tracker'
+
+def add_key(tracker):
+    key = ''
+    for i in sha(basekeydata+tracker).digest()[-6:]:
+        key += mapbase64[ord(i) & 0x3F]
+    keys[tracker] = key
+
+def get_key(tracker):
+    try:
+        return "&key="+keys[tracker]
+    except:
+        add_key(tracker)
+        return "&key="+keys[tracker]
+
+class fakeflag:
+    def __init__(self, state=False):
+        self.state = state
+    def wait(self):
+        pass
+    def isSet(self):
+        return self.state
+
+class Rerequester:
+    def __init__( self, port, myid, infohash, trackerlist, config,
+                  sched, externalsched, errorfunc, excfunc, connect,
+                  howmany, amount_left, up, down, upratefunc, downratefunc,
+                  doneflag, unpauseflag = fakeflag(True),
+                  seededfunc = None, force_rapid_update = False ):
+
+        self.sched = sched
+        self.externalsched = externalsched
+        self.errorfunc = errorfunc
+        self.excfunc = excfunc
+        self.connect = connect
+        self.howmany = howmany
+        self.amount_left = amount_left
+        self.up = up
+        self.down = down
+        self.upratefunc = upratefunc
+        self.downratefunc = downratefunc
+        self.doneflag = doneflag
+        self.unpauseflag = unpauseflag
+        self.seededfunc = seededfunc
+        self.force_rapid_update = force_rapid_update
+
+        self.ip = config.get('ip','')
+        self.minpeers = config['min_peers']
+        self.maxpeers = config['max_initiate']
+        self.interval = config['rerequest_interval']
+        self.timeout = config['http_timeout']
+
+        newtrackerlist = []        
+        for tier in trackerlist:
+            if len(tier)>1:
+                shuffle(tier)
+            newtrackerlist += [tier]
+        self.trackerlist = newtrackerlist
+
+        self.lastsuccessful = ''
+        self.rejectedmessage = 'rejected by tracker - '
+
+        self.url = ('info_hash=%s&peer_id=%s' %
+            (quote(infohash), quote(myid)))
+        if not config.get('crypto_allowed'):
+            self.url += "&port="
+        else:
+            self.url += "&supportcrypto=1"
+            if not config.get('crypto_only'):
+                    self.url += "&port="
+            else:
+                self.url += "&requirecrypto=1"            
+                if not config.get('crypto_stealth'):
+                    self.url += "&port="
+                else:
+                    self.url += "&port=0&cryptoport="
+        self.url += str(port)
+
+        seed_id = config.get('dedicated_seed_id')
+        if seed_id:
+            self.url += '&seed_id='+quote(seed_id)
+        if self.seededfunc:
+            self.url += '&check_seeded=1'
+
+        self.last = None
+        self.trackerid = None
+        self.announce_interval = 30 * 60
+        self.last_failed = True
+        self.never_succeeded = True
+        self.errorcodes = {}
+        self.lock = SuccessLock()
+        self.special = None
+        self.stopped = False
+
+    def start(self):
+        self.sched(self.c, self.interval/2)
+        self.d(0)
+
+    def c(self):
+        if self.stopped:
+            return
+        if not self.unpauseflag.isSet() and (
+            self.howmany() < self.minpeers or self.force_rapid_update ):
+            self.announce(3, self._c)
+        else:
+            self._c()
+
+    def _c(self):
+        self.sched(self.c, self.interval)
+
+    def d(self, event = 3):
+        if self.stopped:
+            return
+        if not self.unpauseflag.isSet():
+            self._d()
+            return
+        self.announce(event, self._d)
+
+    def _d(self):
+        if self.never_succeeded:
+            self.sched(self.d, 60)  # retry in 60 seconds
+        elif self.force_rapid_update:
+            return
+        else:
+            self.sched(self.d, self.announce_interval)
+
+
+    def hit(self, event = 3):
+        if not self.unpauseflag.isSet() and (
+            self.howmany() < self.minpeers or self.force_rapid_update ):
+            self.announce(event)
+
+    def announce(self, event = 3, callback = lambda: None, specialurl = None):
+
+        if specialurl is not None:
+            s = self.url+'&uploaded=0&downloaded=0&left=1'   # don't add to statistics
+            if self.howmany() >= self.maxpeers:
+                s += '&numwant=0'
+            else:
+                s += '&no_peer_id=1&compact=1'
+            self.last_failed = True         # force true, so will display an error
+            self.special = specialurl
+            self.rerequest(s, callback)
+            return
+        
+        else:
+            s = ('%s&uploaded=%s&downloaded=%s&left=%s' %
+                (self.url, str(self.up()), str(self.down()), 
+                str(self.amount_left())))
+        if self.last is not None:
+            s += '&last=' + quote(str(self.last))
+        if self.trackerid is not None:
+            s += '&trackerid=' + quote(str(self.trackerid))
+        if self.howmany() >= self.maxpeers:
+            s += '&numwant=0'
+        else:
+            s += '&no_peer_id=1&compact=1'
+        if event != 3:
+            s += '&event=' + ['started', 'completed', 'stopped'][event]
+        if event == 2:
+            self.stopped = True
+        self.rerequest(s, callback)
+
+
+    def snoop(self, peers, callback = lambda: None):  # tracker call support
+        self.rerequest(self.url
+            +'&event=stopped&port=0&uploaded=0&downloaded=0&left=1&tracker=1&numwant='
+            +str(peers), callback)
+
+
+    def rerequest(self, s, callback):
+        if not self.lock.isfinished():  # still waiting for prior cycle to complete??
+            def retry(self = self, s = s, callback = callback):
+                self.rerequest(s, callback)
+            self.sched(retry,5)         # retry in 5 seconds
+            return
+        self.lock.reset()
+        rq = Thread(target = self._rerequest, args = [s, callback])
+        rq.setDaemon(False)
+        rq.start()
+
+    def _rerequest(self, s, callback):
+        try:
+            def fail (self = self, callback = callback):
+                self._fail(callback)
+            if self.ip:
+                try:
+                    s += '&ip=' + gethostbyname(self.ip)
+                except:
+                    self.errorcodes['troublecode'] = 'unable to resolve: '+self.ip
+                    self.externalsched(fail)
+            self.errorcodes = {}
+            if self.special is None:
+                for t in range(len(self.trackerlist)):
+                    for tr in range(len(self.trackerlist[t])):
+                        tracker  = self.trackerlist[t][tr]
+                        if self.rerequest_single(tracker, s, callback):
+                            if not self.last_failed and tr != 0:
+                                del self.trackerlist[t][tr]
+                                self.trackerlist[t] = [tracker] + self.trackerlist[t]
+                            return
+            else:
+                tracker = self.special
+                self.special = None
+                if self.rerequest_single(tracker, s, callback):
+                    return
+            # no success from any tracker
+            self.externalsched(fail)
+        except:
+            self.exception(callback)
+
+
+    def _fail(self, callback):
+        if ( (self.upratefunc() < 100 and self.downratefunc() < 100)
+             or not self.amount_left() ):
+            for f in ['rejected', 'bad_data', 'troublecode']:
+                if self.errorcodes.has_key(f):
+                    r = self.errorcodes[f]
+                    break
+            else:
+                r = 'Problem connecting to tracker - unspecified error'
+            self.errorfunc(r)
+
+        self.last_failed = True
+        self.lock.give_up()
+        self.externalsched(callback)
+
+
+    def rerequest_single(self, t, s, callback):
+        l = self.lock.set()
+        rq = Thread(target = self._rerequest_single, args = [t, s+get_key(t), l, callback])
+        rq.setDaemon(False)
+        rq.start()
+        self.lock.wait()
+        if self.lock.success:
+            self.lastsuccessful = t
+            self.last_failed = False
+            self.never_succeeded = False
+            return True
+        if not self.last_failed and self.lastsuccessful == t:
+            # if the last tracker hit was successful, and you've just tried the tracker
+            # you'd contacted before, don't go any further, just fail silently.
+            self.last_failed = True
+            self.externalsched(callback)
+            self.lock.give_up()
+            return True
+        return False    # returns true if it wants rerequest() to exit
+
+
+    def _rerequest_single(self, t, s, l, callback):
+        try:        
+            closer = [None]
+            def timedout(self = self, l = l, closer = closer):
+                if self.lock.trip(l):
+                    self.errorcodes['troublecode'] = 'Problem connecting to tracker - timeout exceeded'
+                    self.lock.unwait(l)
+                try:
+                    closer[0]()
+                except:
+                    pass
+                    
+            self.externalsched(timedout, self.timeout)
+
+            err = None
+            try:
+                url,q = t.split('?',1)
+                q += '&'+s
+            except:
+                url = t
+                q = s
+            try:
+                h = urlopen(url+'?'+q)
+                closer[0] = h.close
+                data = h.read()
+            except (IOError, error), e:
+                err = 'Problem connecting to tracker - ' + str(e)
+            except:
+                err = 'Problem connecting to tracker'
+            try:
+                h.close()
+            except:
+                pass
+            if err:        
+                if self.lock.trip(l):
+                    self.errorcodes['troublecode'] = err
+                    self.lock.unwait(l)
+                return
+
+            if data == '':
+                if self.lock.trip(l):
+                    self.errorcodes['troublecode'] = 'no data from tracker'
+                    self.lock.unwait(l)
+                return
+            
+            try:
+                r = bdecode(data, sloppy=1)
+                check_peers(r)
+            except ValueError, e:
+                if self.lock.trip(l):
+                    self.errorcodes['bad_data'] = 'bad data from tracker - ' + str(e)
+                    self.lock.unwait(l)
+                return
+            
+            if r.has_key('failure reason'):
+                if self.lock.trip(l):
+                    self.errorcodes['rejected'] = self.rejectedmessage + r['failure reason']
+                    self.lock.unwait(l)
+                return
+                
+            if self.lock.trip(l, True):     # success!
+                self.lock.unwait(l)
+            else:
+                callback = lambda: None     # attempt timed out, don't do a callback
+
+            # even if the attempt timed out, go ahead and process data
+            def add(self = self, r = r, callback = callback):
+                self.postrequest(r, callback)
+            self.externalsched(add)
+        except:
+            self.exception(callback)
+
+
+    def postrequest(self, r, callback):
+        if r.has_key('warning message'):
+                self.errorfunc('warning from tracker - ' + r['warning message'])
+        self.announce_interval = r.get('interval', self.announce_interval)
+        self.interval = r.get('min interval', self.interval)
+        self.trackerid = r.get('tracker id', self.trackerid)
+        self.last = r.get('last')
+#        ps = len(r['peers']) + self.howmany()
+        p = r['peers']
+        peers = []
+        if type(p) == type(''):
+            lenpeers = len(p)/6
+        else:
+            lenpeers = len(p)
+        cflags = r.get('crypto_flags')
+        if type(cflags) != type('') or len(cflags) != lenpeers:
+            cflags = None
+        if cflags is None:
+            cflags = [None for i in xrange(lenpeers)]
+        else:
+            cflags = [ord(x) for x in cflags]
+        if type(p) == type(''):
+            for x in xrange(0, len(p), 6):
+                ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
+                port = (ord(p[x+4]) << 8) | ord(p[x+5])
+                peers.append(((ip, port), 0, cflags[int(x/6)]))
+        else:
+            for i in xrange(len(p)):
+                x = p[i]
+                peers.append(((x['ip'].strip(), x['port']),
+                              x.get('peer id',0), cflags[i]))
+        ps = len(peers) + self.howmany()
+        if ps < self.maxpeers:
+            if self.doneflag.isSet():
+                if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:
+                    self.last = None
+            else:
+                if r.get('num peers', 1000) > ps * 1.2:
+                    self.last = None
+        if self.seededfunc and r.get('seeded'):
+            self.seededfunc()
+        elif peers:
+            shuffle(peers)
+            self.connect(peers)
+        callback()
+
+    def exception(self, callback):
+        data = StringIO()
+        print_exc(file = data)
+        def r(s = data.getvalue(), callback = callback):
+            if self.excfunc:
+                self.excfunc(s)
+            else:
+                print s
+            callback()
+        self.externalsched(r)
+
+
+class SuccessLock:
+    def __init__(self):
+        self.lock = Lock()
+        self.pause = Lock()
+        self.code = 0L
+        self.success = False
+        self.finished = True
+
+    def reset(self):
+        self.success = False
+        self.finished = False
+
+    def set(self):
+        self.lock.acquire()
+        if not self.pause.locked():
+            self.pause.acquire()
+        self.first = True
+        self.code += 1L
+        self.lock.release()
+        return self.code
+
+    def trip(self, code, s = False):
+        self.lock.acquire()
+        try:
+            if code == self.code and not self.finished:
+                r = self.first
+                self.first = False
+                if s:
+                    self.finished = True
+                    self.success = True
+                return r
+        finally:
+            self.lock.release()
+
+    def give_up(self):
+        self.lock.acquire()
+        self.success = False
+        self.finished = True
+        self.lock.release()
+
+    def wait(self):
+        self.pause.acquire()
+
+    def unwait(self, code):
+        if code == self.code and self.pause.locked():
+            self.pause.release()
+
+    def isfinished(self):
+        self.lock.acquire()
+        x = self.finished
+        self.lock.release()
+        return x    
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/StorageWrapper.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/StorageWrapper.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/StorageWrapper.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/StorageWrapper.py	2006-03-04 03:53:16.000000000 +0000
@@ -0,0 +1,1045 @@
+# Written by Bram Cohen
+# see LICENSE.txt for license information
+
+from BitTornado.bitfield import Bitfield
+from sha import sha
+from BitTornado.clock import clock
+from traceback import print_exc
+from random import randrange
+try:
+    True
+except:
+    True = 1
+    False = 0
+try:
+    from bisect import insort
+except:
+    def insort(l, item):
+        l.append(item)
+        l.sort()
+
+DEBUG = False
+
+STATS_INTERVAL = 0.2
+
+def dummy_status(fractionDone = None, activity = None):
+    pass
+
+class Olist:
+    def __init__(self, l = []):
+        self.d = {}
+        for i in l:
+            self.d[i] = 1
+    def __len__(self):
+        return len(self.d)
+    def includes(self, i):
+        return self.d.has_key(i)
+    def add(self, i):
+        self.d[i] = 1
+    def extend(self, l):
+        for i in l:
+            self.d[i] = 1
+    def pop(self, n=0):
+        # assert self.d
+        k = self.d.keys()
+        if n == 0:
+            i = min(k)
+        elif n == -1:
+            i = max(k)
+        else:
+            k.sort()
+            i = k[n]
+        del self.d[i]
+        return i
+    def remove(self, i):
+        if self.d.has_key(i):
+            del self.d[i]
+
+class fakeflag:
+    def __init__(self, state=False):
+        self.state = state
+    def wait(self):
+        pass
+    def isSet(self):
+        return self.state
+
+
+class StorageWrapper:
+    def __init__(self, storage, request_size, hashes, 
+            piece_size, finished, failed, 
+            statusfunc = dummy_status, flag = fakeflag(), check_hashes = True,
+            data_flunked = lambda x: None, backfunc = None,
+            config = {}, unpauseflag = fakeflag(True) ):
+        self.storage = storage
+        self.request_size = long(request_size)
+        self.hashes = hashes
+        self.piece_size = long(piece_size)
+        self.piece_length = long(piece_size)
+        self.finished = finished
+        self.failed = failed
+        self.statusfunc = statusfunc
+        self.flag = flag
+        self.check_hashes = check_hashes
+        self.data_flunked = data_flunked
+        self.backfunc = backfunc
+        self.config = config
+        self.unpauseflag = unpauseflag
+        
+        self.alloc_type = config.get('alloc_type','normal')
+        self.double_check = config.get('double_check', 0)
+        self.triple_check = config.get('triple_check', 0)
+        if self.triple_check:
+            self.double_check = True
+        self.bgalloc_enabled = False
+        self.bgalloc_active = False
+        self.total_length = storage.get_total_length()
+        self.amount_left = self.total_length
+        if self.total_length <= self.piece_size * (len(hashes) - 1):
+            raise ValueError, 'bad data in responsefile - total too small'
+        if self.total_length > self.piece_size * len(hashes):
+            raise ValueError, 'bad data in responsefile - total too big'
+        self.numactive = [0] * len(hashes)
+        self.inactive_requests = [1] * len(hashes)
+        self.amount_inactive = self.total_length
+        self.amount_obtained = 0
+        self.amount_desired = self.total_length
+        self.have = Bitfield(len(hashes))
+        self.have_cloaked_data = None
+        self.blocked = [False] * len(hashes)
+        self.blocked_holes = []
+        self.blocked_movein = Olist()
+        self.blocked_moveout = Olist()
+        self.waschecked = [False] * len(hashes)
+        self.places = {}
+        self.holes = []
+        self.stat_active = {}
+        self.stat_new = {}
+        self.dirty = {}
+        self.stat_numflunked = 0
+        self.stat_numdownloaded = 0
+        self.stat_numfound = 0
+        self.download_history = {}
+        self.failed_pieces = {}
+        self.out_of_place = 0
+        self.write_buf_max = config['write_buffer_size']*1048576L
+        self.write_buf_size = 0L
+        self.write_buf = {}   # structure:  piece: [(start, data), ...]
+        self.write_buf_list = []
+
+        self.initialize_tasks = [
+            ['checking existing data', 0, self.init_hashcheck, self.hashcheckfunc],
+            ['moving data', 1, self.init_movedata, self.movedatafunc],
+            ['allocating disk space', 1, self.init_alloc, self.allocfunc] ]
+
+        self.backfunc(self._bgalloc,0.1)
+        self.backfunc(self._bgsync,max(self.config['auto_flush']*60,60))
+
+    def _bgsync(self):
+        if self.config['auto_flush']:
+            self.sync()
+        self.backfunc(self._bgsync,max(self.config['auto_flush']*60,60))
+
+
+    def old_style_init(self):
+        while self.initialize_tasks:
+            msg, done, init, next = self.initialize_tasks.pop(0)
+            if init():
+                self.statusfunc(activity = msg, fractionDone = done)
+                t = clock() + STATS_INTERVAL
+                x = 0
+                while x is not None:
+                    if t < clock():
+                        t = clock() + STATS_INTERVAL
+                        self.statusfunc(fractionDone = x)
+                    self.unpauseflag.wait()
+                    if self.flag.isSet():
+                        return False
+                    x = next()
+
+        self.statusfunc(fractionDone = 0)
+        return True
+
+
+    def initialize(self, donefunc, statusfunc = None):
+        self.initialize_done = donefunc
+        if statusfunc is None:
+            statusfunc = self.statusfunc
+        self.initialize_status = statusfunc
+        self.initialize_next = None
+            
+        self.backfunc(self._initialize)
+
+    def _initialize(self):
+        if not self.unpauseflag.isSet():
+            self.backfunc(self._initialize, 1)
+            return
+        
+        if self.initialize_next:
+            x = self.initialize_next()
+            if x is None:
+                self.initialize_next = None
+            else:
+                self.initialize_status(fractionDone = x)
+        else:
+            if not self.initialize_tasks:
+                self.initialize_done()
+                return
+            msg, done, init, next = self.initialize_tasks.pop(0)
+            if init():
+                self.initialize_status(activity = msg, fractionDone = done)
+                self.initialize_next = next
+
+        self.backfunc(self._initialize)
+
+
+    def init_hashcheck(self):
+        if self.flag.isSet():
+            return False
+        self.check_list = []
+        if len(self.hashes) == 0 or self.amount_left == 0:
+            self.check_total = 0
+            self.finished()
+            return False
+
+        self.check_targets = {}
+        got = {}
+        for p,v in self.places.items():
+            assert not got.has_key(v)
+            got[v] = 1
+        for i in xrange(len(self.hashes)):
+            if self.places.has_key(i):  # restored from pickled
+                self.check_targets[self.hashes[i]] = []
+                if self.places[i] == i:
+                    continue
+                else:
+                    assert not got.has_key(i)
+                    self.out_of_place += 1
+            if got.has_key(i):
+                continue
+            if self._waspre(i):
+                if self.blocked[i]:
+                    self.places[i] = i
+                else:
+                    self.check_list.append(i)
+                continue
+            if not self.check_hashes:
+                self.failed('told file complete on start-up, but data is missing')
+                return False
+            self.holes.append(i)
+            if self.blocked[i] or self.check_targets.has_key(self.hashes[i]):
+                self.check_targets[self.hashes[i]] = [] # in case of a hash collision, discard
+            else:
+                self.check_targets[self.hashes[i]] = [i]
+        self.check_total = len(self.check_list)
+        self.check_numchecked = 0.0
+        self.lastlen = self._piecelen(len(self.hashes) - 1)
+        self.numchecked = 0.0
+        return self.check_total > 0
+
+    def _markgot(self, piece, pos):
+        if DEBUG:
+            print str(piece)+' at '+str(pos)
+        self.places[piece] = pos
+        self.have[piece] = True
+        len = self._piecelen(piece)
+        self.amount_obtained += len
+        self.amount_left -= len
+        self.amount_inactive -= len
+        self.inactive_requests[piece] = None
+        self.waschecked[piece] = self.check_hashes
+        self.stat_numfound += 1
+
+    def hashcheckfunc(self):
+        if self.flag.isSet():
+            return None
+        if not self.check_list:
+            return None
+        
+        i = self.check_list.pop(0)
+        if not self.check_hashes:
+            self._markgot(i, i)
+        else:
+            d1 = self.read_raw(i,0,self.lastlen)
+            if d1 is None:
+                return None
+            sh = sha(d1[:])
+            d1.release()
+            sp = sh.digest()
+            d2 = self.read_raw(i,self.lastlen,self._piecelen(i)-self.lastlen)
+            if d2 is None:
+                return None
+            sh.update(d2[:])
+            d2.release()
+            s = sh.digest()
+            if s == self.hashes[i]:
+                self._markgot(i, i)
+            elif ( self.check_targets.get(s)
+                   and self._piecelen(i) == self._piecelen(self.check_targets[s][-1]) ):
+                self._markgot(self.check_targets[s].pop(), i)
+                self.out_of_place += 1
+            elif ( not self.have[-1] and sp == self.hashes[-1]
+                   and (i == len(self.hashes) - 1
+                        or not self._waspre(len(self.hashes) - 1)) ):
+                self._markgot(len(self.hashes) - 1, i)
+                self.out_of_place += 1
+            else:
+                self.places[i] = i
+        self.numchecked += 1
+        if self.amount_left == 0:
+            self.finished()
+        return (self.numchecked / self.check_total)
+
+
+    def init_movedata(self):
+        if self.flag.isSet():
+            return False
+        if self.alloc_type != 'sparse':
+            return False
+        self.storage.top_off()  # sets file lengths to their final size
+        self.movelist = []
+        if self.out_of_place == 0:
+            for i in self.holes:
+                self.places[i] = i
+            self.holes = []
+            return False
+        self.tomove = float(self.out_of_place)
+        for i in xrange(len(self.hashes)):
+            if not self.places.has_key(i):
+                self.places[i] = i
+            elif self.places[i] != i:
+                self.movelist.append(i)
+        self.holes = []
+        return True
+
+    def movedatafunc(self):
+        if self.flag.isSet():
+            return None
+        if not self.movelist:
+            return None
+        i = self.movelist.pop(0)
+        old = self.read_raw(self.places[i], 0, self._piecelen(i))
+        if old is None:
+            return None
+        if not self.write_raw(i, 0, old):
+            return None
+        if self.double_check and self.have[i]:
+            if self.triple_check:
+                old.release()
+                old = self.read_raw( i, 0, self._piecelen(i),
+                                            flush_first = True )
+                if old is None:
+                    return None
+            if sha(old[:]).digest() != self.hashes[i]:
+                self.failed('download corrupted; please restart and resume')
+                return None
+        old.release()
+
+        self.places[i] = i
+        self.tomove -= 1
+        return (self.tomove / self.out_of_place)
+
+        
+    def init_alloc(self):
+        if self.flag.isSet():
+            return False
+        if not self.holes:
+            return False
+        self.numholes = float(len(self.holes))
+        self.alloc_buf = chr(0xFF) * self.piece_size
+        if self.alloc_type == 'pre-allocate':
+            self.bgalloc_enabled = True
+            return True
+        if self.alloc_type == 'background':
+            self.bgalloc_enabled = True
+        if self.blocked_moveout:
+            return True
+        return False
+
+
+    def _allocfunc(self):
+        while self.holes:
+            n = self.holes.pop(0)
+            if self.blocked[n]: # assume not self.blocked[index]
+                if not self.blocked_movein:
+                    self.blocked_holes.append(n)
+                    continue
+                if not self.places.has_key(n):
+                    b = self.blocked_movein.pop(0)
+                    oldpos = self._move_piece(b, n)
+                    self.places[oldpos] = oldpos
+                    return None
+            if self.places.has_key(n):
+                oldpos = self._move_piece(n, n)
+                self.places[oldpos] = oldpos
+                return None
+            return n
+        return None
+
+    def allocfunc(self):
+        if self.flag.isSet():
+            return None
+        
+        if self.blocked_moveout:
+            self.bgalloc_active = True
+            n = self._allocfunc()
+            if n is not None:
+                if self.blocked_moveout.includes(n):
+                    self.blocked_moveout.remove(n)
+                    b = n
+                else:
+                    b = self.blocked_moveout.pop(0)
+                oldpos = self._move_piece(b,n)
+                self.places[oldpos] = oldpos
+            return len(self.holes) / self.numholes
+
+        if self.holes and self.bgalloc_enabled:
+            self.bgalloc_active = True
+            n = self._allocfunc()
+            if n is not None:
+                self.write_raw(n, 0, self.alloc_buf[:self._piecelen(n)])
+                self.places[n] = n
+            return len(self.holes) / self.numholes
+
+        self.bgalloc_active = False
+        return None
+
+    def bgalloc(self):
+        if self.bgalloc_enabled:
+            if not self.holes and not self.blocked_moveout and self.backfunc:
+                self.backfunc(self.storage.flush)
+                # force a flush whenever the "finish allocation" button is hit
+        self.bgalloc_enabled = True
+        return False
+
+    def _bgalloc(self):
+        self.allocfunc()
+        if self.config.get('alloc_rate',0) < 0.1:
+            self.config['alloc_rate'] = 0.1
+        self.backfunc( self._bgalloc,
+              float(self.piece_size)/(self.config['alloc_rate']*1048576) )
+
+
+    def _waspre(self, piece):
+        return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece))
+
+    def _piecelen(self, piece):
+        if piece < len(self.hashes) - 1:
+            return self.piece_size
+        else:
+            return self.total_length - (piece * self.piece_size)
+
+    def get_amount_left(self):
+        return self.amount_left
+
+    def do_I_have_anything(self):
+        return self.amount_left < self.total_length
+
+    def _make_inactive(self, index):
+        length = self._piecelen(index)
+        l = []
+        x = 0
+        while x + self.request_size < length:
+            l.append((x, self.request_size))
+            x += self.request_size
+        l.append((x, length - x))
+        self.inactive_requests[index] = l
+
+    def is_endgame(self):
+        return not self.amount_inactive
+
+    def am_I_complete(self):
+        return self.amount_obtained == self.amount_desired
+
+    def reset_endgame(self, requestlist):
+        for index, begin, length in requestlist:
+            self.request_lost(index, begin, length)
+
+    def get_have_list(self):
+        return self.have.tostring()
+
+    def get_have_list_cloaked(self):
+        if self.have_cloaked_data is None:
+            newhave = Bitfield(copyfrom = self.have)
+            unhaves = []
+            n = min(randrange(2,5),len(self.hashes))    # between 2-4 unless torrent is small
+            while len(unhaves) < n:
+                unhave = randrange(min(32,len(self.hashes)))    # all in first 4 bytes
+                if not unhave in unhaves:
+                    unhaves.append(unhave)
+                    newhave[unhave] = False
+            self.have_cloaked_data = (newhave.tostring(), unhaves)
+        return self.have_cloaked_data
+
+    def do_I_have(self, index):
+        return self.have[index]
+
+    def do_I_have_requests(self, index):
+        return not not self.inactive_requests[index]
+
+    def is_unstarted(self, index):
+        return ( not self.have[index] and not self.numactive[index]
+                 and not self.dirty.has_key(index) )
+
+    def get_hash(self, index):
+        return self.hashes[index]
+
+    def get_stats(self):
+        return self.amount_obtained, self.amount_desired
+
+    def new_request(self, index):
+        # returns (begin, length)
+        if self.inactive_requests[index] == 1:
+            self._make_inactive(index)
+        self.numactive[index] += 1
+        self.stat_active[index] = 1
+        if not self.dirty.has_key(index):
+            self.stat_new[index] = 1
+        rs = self.inactive_requests[index]
+#        r = min(rs)
+#        rs.remove(r)
+        r = rs.pop(0)
+        self.amount_inactive -= r[1]
+        return r
+
+
+    def write_raw(self, index, begin, data):
+        try:
+            self.storage.write(self.piece_size * index + begin, data)
+            return True
+        except IOError, e:
+            self.failed('IO Error: ' + str(e))
+            return False
+
+
+    def _write_to_buffer(self, piece, start, data):
+        if not self.write_buf_max:
+            return self.write_raw(self.places[piece], start, data)
+        self.write_buf_size += len(data)
+        while self.write_buf_size > self.write_buf_max:
+            old = self.write_buf_list.pop(0)
+            if not self._flush_buffer(old, True):
+                return False
+        if self.write_buf.has_key(piece):
+            self.write_buf_list.remove(piece)
+        else:
+            self.write_buf[piece] = []
+        self.write_buf_list.append(piece)
+        self.write_buf[piece].append((start,data))
+        return True
+
+    def _flush_buffer(self, piece, popped = False):
+        if not self.write_buf.has_key(piece):
+            return True
+        if not popped:
+            self.write_buf_list.remove(piece)
+        l = self.write_buf[piece]
+        del self.write_buf[piece]
+        l.sort()
+        for start, data in l:
+            self.write_buf_size -= len(data)
+            if not self.write_raw(self.places[piece], start, data):
+                return False
+        return True
+
+    def sync(self):
+        spots = {}
+        for p in self.write_buf_list:
+            spots[self.places[p]] = p
+        l = spots.keys()
+        l.sort()
+        for i in l:
+            try:
+                self._flush_buffer(spots[i])
+            except:
+                pass
+        try:
+            self.storage.sync()
+        except IOError, e:
+            self.failed('IO Error: ' + str(e))
+        except OSError, e:
+            self.failed('OS Error: ' + str(e))
+
+
+    def _move_piece(self, index, newpos):
+        oldpos = self.places[index]
+        if DEBUG:
+            print 'moving '+str(index)+' from '+str(oldpos)+' to '+str(newpos)
+        assert oldpos != index
+        assert oldpos != newpos
+        assert index == newpos or not self.places.has_key(newpos)
+        old = self.read_raw(oldpos, 0, self._piecelen(index))
+        if old is None:
+            return -1
+        if not self.write_raw(newpos, 0, old):
+            return -1
+        self.places[index] = newpos
+        if self.have[index] and (
+                self.triple_check or (self.double_check and index == newpos) ):
+            if self.triple_check:
+                old.release()
+                old = self.read_raw(newpos, 0, self._piecelen(index),
+                                    flush_first = True)
+                if old is None:
+                    return -1
+            if sha(old[:]).digest() != self.hashes[index]:
+                self.failed('download corrupted; please restart and resume')
+                return -1
+        old.release()
+
+        if self.blocked[index]:
+            self.blocked_moveout.remove(index)
+            if self.blocked[newpos]:
+                self.blocked_movein.remove(index)
+            else:
+                self.blocked_movein.add(index)
+        else:
+            self.blocked_movein.remove(index)
+            if self.blocked[newpos]:
+                self.blocked_moveout.add(index)
+            else:
+                self.blocked_moveout.remove(index)
+                    
+        return oldpos
+            
+    def _clear_space(self, index):
+        h = self.holes.pop(0)
+        n = h
+        if self.blocked[n]: # assume not self.blocked[index]
+            if not self.blocked_movein:
+                self.blocked_holes.append(n)
+                return True    # repeat
+            if not self.places.has_key(n):
+                b = self.blocked_movein.pop(0)
+                oldpos = self._move_piece(b, n)
+                if oldpos < 0:
+                    return False
+                n = oldpos
+        if self.places.has_key(n):
+            oldpos = self._move_piece(n, n)
+            if oldpos < 0:
+                return False
+            n = oldpos
+        if index == n or index in self.holes:
+            if n == h:
+                self.write_raw(n, 0, self.alloc_buf[:self._piecelen(n)])
+            self.places[index] = n
+            if self.blocked[n]:
+                # because n may be a spot cleared 10 lines above, it's possible
+                # for it to be blocked.  While that spot could be left cleared
+                # and a new spot allocated, this condition might occur several
+                # times in a row, resulting in a significant amount of disk I/O,
+                # delaying the operation of the engine.  Rather than do this,
+                # queue the piece to be moved out again, which will be performed
+                # by the background allocator, with which data movement is
+                # automatically limited.
+                self.blocked_moveout.add(index)
+            return False
+        for p, v in self.places.items():
+            if v == index:
+                break
+        else:
+            self.failed('download corrupted; please restart and resume')
+            return False
+        self._move_piece(p, n)
+        self.places[index] = index
+        return False
+
+
+    def piece_came_in(self, index, begin, piece, source = None):
+        assert not self.have[index]
+        
+        if not self.places.has_key(index):
+            while self._clear_space(index):
+                pass
+            if DEBUG:
+                print 'new place for '+str(index)+' at '+str(self.places[index])
+        if self.flag.isSet():
+            return
+
+        if self.failed_pieces.has_key(index):
+            old = self.read_raw(self.places[index], begin, len(piece))
+            if old is None:
+                return True
+            if old[:].tostring() != piece:
+                try:
+                    self.failed_pieces[index][self.download_history[index][begin]] = 1
+                except:
+                    self.failed_pieces[index][None] = 1
+            old.release()
+        self.download_history.setdefault(index,{})[begin] = source
+        
+        if not self._write_to_buffer(index, begin, piece):
+            return True
+        
+        self.amount_obtained += len(piece)
+        self.dirty.setdefault(index,[]).append((begin, len(piece)))
+        self.numactive[index] -= 1
+        assert self.numactive[index] >= 0
+        if not self.numactive[index]:
+            del self.stat_active[index]
+        if self.stat_new.has_key(index):
+            del self.stat_new[index]
+
+        if self.inactive_requests[index] or self.numactive[index]:
+            return True
+        
+        del self.dirty[index]
+        if not self._flush_buffer(index):
+            return True
+        length = self._piecelen(index)
+        data = self.read_raw(self.places[index], 0, length,
+                                 flush_first = self.triple_check)
+        if data is None:
+            return True
+        hash = sha(data[:]).digest()
+        data.release()
+        if hash != self.hashes[index]:
+
+            self.amount_obtained -= length
+            self.data_flunked(length, index)
+            self.inactive_requests[index] = 1
+            self.amount_inactive += length
+            self.stat_numflunked += 1
+
+            self.failed_pieces[index] = {}
+            allsenders = {}
+            for d in self.download_history[index].values():
+                allsenders[d] = 1
+            if len(allsenders) == 1:
+                culprit = allsenders.keys()[0]
+                if culprit is not None:
+                    culprit.failed(index, bump = True)
+                del self.failed_pieces[index] # found the culprit already
+            
+            return False
+
+        self.have[index] = True
+        self.inactive_requests[index] = None
+        self.waschecked[index] = True
+        self.amount_left -= length
+        self.stat_numdownloaded += 1
+
+        for d in self.download_history[index].values():
+            if d is not None:
+                d.good(index)
+        del self.download_history[index]
+        if self.failed_pieces.has_key(index):
+            for d in self.failed_pieces[index].keys():
+                if d is not None:
+                    d.failed(index)
+            del self.failed_pieces[index]
+
+        if self.amount_left == 0:
+            self.finished()
+        return True
+
+
+    def request_lost(self, index, begin, length):
+        assert not (begin, length) in self.inactive_requests[index]
+        insort(self.inactive_requests[index], (begin, length))
+        self.amount_inactive += length
+        self.numactive[index] -= 1
+        if not self.numactive[index]:
+            del self.stat_active[index]
+            if self.stat_new.has_key(index):
+                del self.stat_new[index]
+
+
+    def get_piece(self, index, begin, length):
+        if not self.have[index]:
+            return None
+        data = None
+        if not self.waschecked[index]:
+            data = self.read_raw(self.places[index], 0, self._piecelen(index))
+            if data is None:
+                return None
+            if sha(data[:]).digest() != self.hashes[index]:
+                self.failed('told file complete on start-up, but piece failed hash check')
+                return None
+            self.waschecked[index] = True
+            if length == -1 and begin == 0:
+                return data     # optimization
+        if length == -1:
+            if begin > self._piecelen(index):
+                return None
+            length = self._piecelen(index)-begin
+            if begin == 0:
+                return self.read_raw(self.places[index], 0, length)
+        elif begin + length > self._piecelen(index):
+            return None
+        if data is not None:
+            s = data[begin:begin+length]
+            data.release()
+            return s
+        data = self.read_raw(self.places[index], begin, length)
+        if data is None:
+            return None
+        s = data.getarray()
+        data.release()
+        return s
+
+    def read_raw(self, piece, begin, length, flush_first = False):
+        try:
+            return self.storage.read(self.piece_size * piece + begin,
+                                                     length, flush_first)
+        except IOError, e:
+            self.failed('IO Error: ' + str(e))
+            return None
+
+
+    def set_file_readonly(self, n):
+        try:
+            self.storage.set_readonly(n)
+        except IOError, e:
+            self.failed('IO Error: ' + str(e))
+        except OSError, e:
+            self.failed('OS Error: ' + str(e))
+
+
+    def has_data(self, index):
+        return index not in self.holes and index not in self.blocked_holes
+
+    def doublecheck_data(self, pieces_to_check):
+        if not self.double_check:
+            return
+        sources = []
+        for p,v in self.places.items():
+            if pieces_to_check.has_key(v):
+                sources.append(p)
+        assert len(sources) == len(pieces_to_check)
+        sources.sort()
+        for index in sources:
+            if self.have[index]:
+                piece = self.read_raw(self.places[index],0,self._piecelen(index),
+                                       flush_first = True )
+                if piece is None:
+                    return False
+                if sha(piece[:]).digest() != self.hashes[index]:
+                    self.failed('download corrupted; please restart and resume')
+                    return False
+                piece.release()
+        return True
+
+
+    def reblock(self, new_blocked):
+        # assume downloads have already been canceled and chunks made inactive
+        for i in xrange(len(new_blocked)):
+            if new_blocked[i] and not self.blocked[i]:
+                length = self._piecelen(i)
+                self.amount_desired -= length
+                if self.have[i]:
+                    self.amount_obtained -= length
+                    continue
+                if self.inactive_requests[i] == 1:
+                    self.amount_inactive -= length
+                    continue
+                inactive = 0
+                for nb, nl in self.inactive_requests[i]:
+                    inactive += nl
+                self.amount_inactive -= inactive
+                self.amount_obtained -= length - inactive
+                
+            if self.blocked[i] and not new_blocked[i]:
+                length = self._piecelen(i)
+                self.amount_desired += length
+                if self.have[i]:
+                    self.amount_obtained += length
+                    continue
+                if self.inactive_requests[i] == 1:
+                    self.amount_inactive += length
+                    continue
+                inactive = 0
+                for nb, nl in self.inactive_requests[i]:
+                    inactive += nl
+                self.amount_inactive += inactive
+                self.amount_obtained += length - inactive
+
+        self.blocked = new_blocked
+
+        self.blocked_movein = Olist()
+        self.blocked_moveout = Olist()
+        for p,v in self.places.items():
+            if p != v:
+                if self.blocked[p] and not self.blocked[v]:
+                    self.blocked_movein.add(p)
+                elif self.blocked[v] and not self.blocked[p]:
+                    self.blocked_moveout.add(p)
+
+        self.holes.extend(self.blocked_holes)    # reset holes list
+        self.holes.sort()
+        self.blocked_holes = []
+
+
+    '''
+    Pickled data format:
+
+    d['pieces'] = either a string containing a bitfield of complete pieces,
+                    or the numeric value "1" signifying a seed.  If it is
+                    a seed, d['places'] and d['partials'] should be empty
+                    and needn't even exist.
+    d['partials'] = [ piece, [ offset, length... ]... ]
+                    a list of partial data that had been previously
+                    downloaded, plus the given offsets.  Adjacent partials
+                    are merged so as to save space, and so that if the
+                    request size changes then new requests can be
+                    calculated more efficiently.
+    d['places'] = [ piece, place, {,piece, place ...} ]
+                    the piece index, and the place it's stored.
+                    If d['pieces'] specifies a complete piece or d['partials']
+                    specifies a set of partials for a piece which has no
+                    entry in d['places'], it can be assumed that
+                    place[index] = index.  A place specified with no
+                    corresponding data in d['pieces'] or d['partials']
+                    indicates allocated space with no valid data, and is
+                    reserved so it doesn't need to be hash-checked.
+    '''
+    def pickle(self):
+        if self.have.complete():
+            return {'pieces': 1}
+        pieces = Bitfield(len(self.hashes))
+        places = []
+        partials = []
+        for p in xrange(len(self.hashes)):
+            if self.blocked[p] or not self.places.has_key(p):
+                continue
+            h = self.have[p]
+            pieces[p] = h
+            pp = self.dirty.get(p)
+            if not h and not pp:  # no data
+                places.extend([self.places[p],self.places[p]])
+            elif self.places[p] != p:
+                places.extend([p, self.places[p]])
+            if h or not pp:
+                continue
+            pp.sort()
+            r = []
+            while len(pp) > 1:
+                if pp[0][0]+pp[0][1] == pp[1][0]:
+                    pp[0] = list(pp[0])
+                    pp[0][1] += pp[1][1]
+                    del pp[1]
+                else:
+                    r.extend(pp[0])
+                    del pp[0]
+            r.extend(pp[0])
+            partials.extend([p,r])
+        return {'pieces': pieces.tostring(), 'places': places, 'partials': partials}
+
+
+    def unpickle(self, data, valid_places):
+        got = {}
+        places = {}
+        dirty = {}
+        download_history = {}
+        stat_active = {}
+        stat_numfound = self.stat_numfound
+        amount_obtained = self.amount_obtained
+        amount_inactive = self.amount_inactive
+        amount_left = self.amount_left
+        inactive_requests = [x for x in self.inactive_requests]
+        restored_partials = []
+
+        try:
+            if data['pieces'] == 1:     # a seed
+                assert not data.get('places',None)
+                assert not data.get('partials',None)
+                have = Bitfield(len(self.hashes))
+                for i in xrange(len(self.hashes)):
+                    have[i] = True
+                assert have.complete()
+                _places = []
+                _partials = []
+            else:
+                have = Bitfield(len(self.hashes), data['pieces'])
+                _places = data['places']
+                assert len(_places) % 2 == 0
+                _places = [_places[x:x+2] for x in xrange(0,len(_places),2)]
+                _partials = data['partials']
+                assert len(_partials) % 2 == 0
+                _partials = [_partials[x:x+2] for x in xrange(0,len(_partials),2)]
+                
+            for index, place in _places:
+                if place not in valid_places:
+                    continue
+                assert not got.has_key(index)
+                assert not got.has_key(place)
+                places[index] = place
+                got[index] = 1
+                got[place] = 1
+
+            for index in xrange(len(self.hashes)):
+                if have[index]:
+                    if not places.has_key(index):
+                        if index not in valid_places:
+                            have[index] = False
+                            continue
+                        assert not got.has_key(index)
+                        places[index] = index
+                        got[index] = 1
+                    length = self._piecelen(index)
+                    amount_obtained += length
+                    stat_numfound += 1
+                    amount_inactive -= length
+                    amount_left -= length
+                    inactive_requests[index] = None
+
+            for index, plist in _partials:
+                assert not dirty.has_key(index)
+                assert not have[index]
+                if not places.has_key(index):
+                    if index not in valid_places:
+                        continue
+                    assert not got.has_key(index)
+                    places[index] = index
+                    got[index] = 1
+                assert len(plist) % 2 == 0
+                plist = [plist[x:x+2] for x in xrange(0,len(plist),2)]
+                dirty[index] = plist
+                stat_active[index] = 1
+                download_history[index] = {}
+                # invert given partials
+                length = self._piecelen(index)
+                l = []
+                if plist[0][0] > 0:
+                    l.append((0,plist[0][0]))
+                for i in xrange(len(plist)-1):
+                    end = plist[i][0]+plist[i][1]
+                    assert not end > plist[i+1][0]
+                    l.append((end,plist[i+1][0]-end))
+                end = plist[-1][0]+plist[-1][1]
+                assert not end > length
+                if end < length:
+                    l.append((end,length-end))
+                # split them to request_size
+                ll = []
+                amount_obtained += length
+                amount_inactive -= length
+                for nb, nl in l:
+                    while nl > 0:
+                        r = min(nl,self.request_size)
+                        ll.append((nb,r))
+                        amount_inactive += r
+                        amount_obtained -= r
+                        nb += self.request_size
+                        nl -= self.request_size
+                inactive_requests[index] = ll
+                restored_partials.append(index)
+
+            assert amount_obtained + amount_inactive == self.amount_desired
+        except:
+#            print_exc()
+            return []   # invalid data, discard everything
+
+        self.have = have
+        self.places = places
+        self.dirty = dirty
+        self.download_history = download_history
+        self.stat_active = stat_active
+        self.stat_numfound = stat_numfound
+        self.amount_obtained = amount_obtained
+        self.amount_inactive = amount_inactive
+        self.amount_left = amount_left
+        self.inactive_requests = inactive_requests
+                
+        return restored_partials
+    
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/track.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/track.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/track.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BT1/track.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,1146 @@
+# Written by Bram Cohen
+# see LICENSE.txt for license information
+
+from BitTornado.parseargs import parseargs, formatDefinitions
+from BitTornado.RawServer import RawServer, autodetect_ipv6, autodetect_socket_style
+from BitTornado.HTTPHandler import HTTPHandler, months, weekdays
+from BitTornado.parsedir import parsedir
+from NatCheck import NatCheck, CHECK_PEER_ID_ENCRYPTED
+from BitTornado.BTcrypto import CRYPTO_OK
+from T2T import T2TList
+from BitTornado.subnetparse import IP_List, ipv6_to_ipv4, to_ipv4, is_valid_ip, is_ipv4
+from BitTornado.iprangeparse import IP_List as IP_Range_List
+from BitTornado.torrentlistparse import parsetorrentlist
+from threading import Event, Thread
+from BitTornado.bencode import bencode, bdecode, Bencached
+from BitTornado.zurllib import urlopen, quote, unquote
+from Filter import Filter
+from urlparse import urlparse
+from os import rename, getpid
+from os.path import exists, isfile
+from cStringIO import StringIO
+from traceback import print_exc
+from time import time, gmtime, strftime, localtime
+from BitTornado.clock import clock
+from random import shuffle, seed, randrange
+from sha import sha
+from types import StringType, IntType, LongType, ListType, DictType
+from binascii import b2a_hex, a2b_hex, a2b_base64
+from string import lower
+import sys, os
+import signal
+import re
+import BitTornado.__init__
+from BitTornado.__init__ import version, createPeerID
+try:
+    True
+except:
+    True = 1
+    False = 0
+    bool = lambda x: not not x
+
+defaults = [
+    ('port', 80, "Port to listen on."),
+    ('dfile', None, 'file to store recent downloader info in'),
+    ('bind', '', 'comma-separated list of ips/hostnames to bind to locally'),
+#    ('ipv6_enabled', autodetect_ipv6(),
+    ('ipv6_enabled', 0,
+         'allow the client to connect to peers via IPv6'),
+    ('ipv6_binds_v4', autodetect_socket_style(),
+        'set if an IPv6 server socket will also field IPv4 connections'),
+    ('socket_timeout', 15, 'timeout for closing connections'),
+    ('save_dfile_interval', 5 * 60, 'seconds between saving dfile'),
+    ('timeout_downloaders_interval', 45 * 60, 'seconds between expiring downloaders'),
+    ('reannounce_interval', 30 * 60, 'seconds downloaders should wait between reannouncements'),
+    ('response_size', 50, 'number of peers to send in an info message'),
+    ('timeout_check_interval', 5,
+        'time to wait between checking if any connections have timed out'),
+    ('nat_check', 3,
+        "how many times to check if a downloader is behind a NAT (0 = don't check)"),
+    ('log_nat_checks', 0,
+        "whether to add entries to the log for nat-check results"),
+    ('min_time_between_log_flushes', 3.0,
+        'minimum time it must have been since the last flush to do another one'),
+    ('min_time_between_cache_refreshes', 600.0,
+        'minimum time in seconds before a cache is considered stale and is flushed'),
+    ('allowed_dir', '', 'only allow downloads for .torrents in this dir'),
+    ('allowed_list', '', 'only allow downloads for hashes in this list (hex format, one per line)'),
+    ('allowed_controls', 0, 'allow special keys in torrents in the allowed_dir to affect tracker access'),
+    ('multitracker_enabled', 0, 'whether to enable multitracker operation'),
+    ('multitracker_allowed', 'autodetect', 'whether to allow incoming tracker announces (can be none, autodetect or all)'),
+    ('multitracker_reannounce_interval', 2 * 60, 'seconds between outgoing tracker announces'),
+    ('multitracker_maxpeers', 20, 'number of peers to get in a tracker announce'),
+    ('aggregate_forward', '', 'format: <url>[,<password>] - if set, forwards all non-multitracker to this url with this optional password'),
+    ('aggregator', '0', 'whether to act as a data aggregator rather than a tracker.  If enabled, may be 1, or <password>; ' +
+             'if password is set, then an incoming password is required for access'),
+    ('hupmonitor', 0, 'whether to reopen the log file upon receipt of HUP signal'),
+    ('http_timeout', 60, 
+        'number of seconds to wait before assuming that an http connection has timed out'),
+    ('parse_dir_interval', 60, 'seconds between reloading of allowed_dir or allowed_file ' +
+             'and allowed_ips and banned_ips lists'),
+    ('show_infopage', 1, "whether to display an info page when the tracker's root dir is loaded"),
+    ('infopage_redirect', '', 'a URL to redirect the info page to'),
+    ('show_names', 1, 'whether to display names from allowed dir'),
+    ('favicon', '', 'file containing x-icon data to return when browser requests favicon.ico'),
+    ('allowed_ips', '', 'only allow connections from IPs specified in the given file; '+
+             'file contains subnet data in the format: aa.bb.cc.dd/len'),
+    ('banned_ips', '', "don't allow connections from IPs specified in the given file; "+
+             'file contains IP range data in the format: xxx:xxx:ip1-ip2'),
+    ('only_local_override_ip', 2, "ignore the ip GET parameter from machines which aren't on local network IPs " +
+             "(0 = never, 1 = always, 2 = ignore if NAT checking is not enabled)"),
+    ('logfile', '', 'file to write the tracker logs, use - for stdout (default)'),
+    ('allow_get', 0, 'use with allowed_dir; adds a /file?hash={hash} url that allows users to download the torrent file'),
+    ('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'),
+    ('scrape_allowed', 'full', 'scrape access allowed (can be none, specific or full)'),
+    ('dedicated_seed_id', '', 'allows tracker to monitor dedicated seed(s) and flag torrents as seeded'),
+    ('compact_reqd', 1, "only allow peers that accept a compact response"),
+  ]
+
+def statefiletemplate(x):
+    if type(x) != DictType:
+        raise ValueError
+    for cname, cinfo in x.items():
+        if cname == 'peers':
+            for y in cinfo.values():      # The 'peers' key is a dictionary of SHA hashes (torrent ids)
+                if type(y) != DictType:   # ... for the active torrents, and each is a dictionary
+                    raise ValueError
+                for id, info in y.items(): # ... of client ids interested in that torrent
+                    if (len(id) != 20):
+                        raise ValueError
+                    if type(info) != DictType:  # ... each of which is also a dictionary
+                        raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
+                    if type(info.get('ip', '')) != StringType:
+                        raise ValueError
+                    port = info.get('port')
+                    if type(port) not in (IntType,LongType) or port < 0:
+                        raise ValueError
+                    left = info.get('left')
+                    if type(left) not in (IntType,LongType) or left < 0:
+                        raise ValueError
+                    if type(info.get('supportcrypto')) not in (IntType,LongType):
+                        raise ValueError
+                    if type(info.get('requirecrypto')) not in (IntType,LongType):
+                        raise ValueError
+        elif cname == 'completed':
+            if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids)
+                raise ValueError          # ... for keeping track of the total completions per torrent
+            for y in cinfo.values():      # ... each torrent has an integer value
+                if type(y) not in (IntType,LongType):
+                    raise ValueError      # ... for the number of reported completions for that torrent
+        elif cname == 'allowed':
+            if (type(cinfo) != DictType): # a list of info_hashes and included data
+                raise ValueError
+            if x.has_key('allowed_dir_files'):
+                adlist = [z[1] for z in x['allowed_dir_files'].values()]
+                for y in cinfo.keys():        # and each should have a corresponding key here
+                    if not y in adlist:
+                        raise ValueError
+        elif cname == 'allowed_dir_files':
+            if (type(cinfo) != DictType): # a list of files, their attributes and info hashes
+                raise ValueError
+            dirkeys = {}
+            for y in cinfo.values():      # each entry should have a corresponding info_hash
+                if not y[1]:
+                    continue
+                if not x['allowed'].has_key(y[1]):
+                    raise ValueError
+                if dirkeys.has_key(y[1]): # and each should have a unique info_hash
+                    raise ValueError
+                dirkeys[y[1]] = 1
+            
+
+alas = 'your file may exist elsewhere in the universe\nbut alas, not here\n'
+
+local_IPs = IP_List()
+local_IPs.set_intranet_addresses()
+
+
+def isotime(secs = None):
+    if secs == None:
+        secs = time()
+    return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs))
+
+http_via_filter = re.compile(' for ([0-9.]+)\Z')
+
+def _get_forwarded_ip(headers):
+    header = headers.get('x-forwarded-for')
+    if header:
+        try:
+            x,y = header.split(',')
+        except:
+            return header
+        if is_valid_ip(x) and not local_IPs.includes(x):
+            return x
+        return y
+    header = headers.get('client-ip')
+    if header:
+        return header
+    header = headers.get('via')
+    if header:
+        x = http_via_filter.search(header)
+        try:
+            return x.group(1)
+        except:
+            pass
+    header = headers.get('from')
+    #if header:
+    #    return header
+    #return None
+    return header
+
+def get_forwarded_ip(headers):
+    x = _get_forwarded_ip(headers)
+    if x is None or not is_valid_ip(x) or local_IPs.includes(x):
+        return None
+    return x
+
+def compact_peer_info(ip, port):
+    try:
+        s = ( ''.join([chr(int(i)) for i in ip.split('.')])
+              + chr((port & 0xFF00) >> 8) + chr(port & 0xFF) )
+        if len(s) != 6:
+            raise ValueError
+    except:
+        s = ''  # not a valid IP, must be a domain name
+    return s
+
+class Tracker:
+    def __init__(self, config, rawserver):
+        self.config = config
+        self.response_size = config['response_size']
+        self.dfile = config['dfile']
+        self.natcheck = config['nat_check']
+        favicon = config['favicon']
+        self.parse_dir_interval = config['parse_dir_interval']
+        self.favicon = None
+        if favicon:
+            try:
+                h = open(favicon,'r')
+                self.favicon = h.read()
+                h.close()
+            except:
+                print "**warning** specified favicon file -- %s -- does not exist." % favicon
+        self.rawserver = rawserver
+        self.cached = {}    # format: infohash: [[time1, l1, s1], [time2, l2, s2], ...]
+        self.cached_t = {}  # format: infohash: [time, cache]
+        self.times = {}
+        self.state = {}
+        self.seedcount = {}
+
+        self.allowed_IPs = None
+        self.banned_IPs = None
+        if config['allowed_ips'] or config['banned_ips']:
+            self.allowed_ip_mtime = 0
+            self.banned_ip_mtime = 0
+            self.read_ip_lists()
+                
+        self.only_local_override_ip = config['only_local_override_ip']
+        if self.only_local_override_ip == 2:
+            self.only_local_override_ip = not config['nat_check']
+
+        if CHECK_PEER_ID_ENCRYPTED and not CRYPTO_OK:
+            print ('**warning** crypto library not installed,' +
+                   ' cannot completely verify encrypted peers')
+
+        if exists(self.dfile):
+            try:
+                h = open(self.dfile, 'rb')
+                ds = h.read()
+                h.close()
+                tempstate = bdecode(ds)
+                if not tempstate.has_key('peers'):
+                    tempstate = {'peers': tempstate}
+                statefiletemplate(tempstate)
+                self.state = tempstate
+            except:
+                print '**warning** statefile '+self.dfile+' corrupt; resetting'
+        self.downloads = self.state.setdefault('peers', {})
+        self.completed = self.state.setdefault('completed', {})
+
+        self.becache = {}
+        ''' format: infohash: [[l0, s0], [l1, s1], ...]
+                l0,s0 = compact, not requirecrypto=1
+                l1,s1 = compact, only supportcrypto=1
+                l2,s2 = [compact, crypto_flag], all peers
+            if --compact_reqd 0:
+                l3,s3 = [ip,port,id]
+                l4,l4 = [ip,port] nopeerid
+        '''
+        if config['compact_reqd']:
+            self.cache_default_len = 3
+        else:
+            self.cache_default_len = 5
+        for infohash, ds in self.downloads.items():
+            self.seedcount[infohash] = 0
+            for x,y in ds.items():
+                ip = y['ip']
+                if ( (self.allowed_IPs and not self.allowed_IPs.includes(ip))
+                     or (self.banned_IPs and self.banned_IPs.includes(ip)) ):
+                    del ds[x]
+                    continue
+                if not y['left']:
+                    self.seedcount[infohash] += 1
+                if y.get('nat',-1):
+                    continue
+                gip = y.get('given_ip')
+                if is_valid_ip(gip) and (
+                    not self.only_local_override_ip or local_IPs.includes(ip) ):
+                    ip = gip
+                self.natcheckOK(infohash,x,ip,y['port'],y)
+            
+        for x in self.downloads.keys():
+            self.times[x] = {}
+            for y in self.downloads[x].keys():
+                self.times[x][y] = 0
+
+        self.trackerid = createPeerID('-T-')
+        seed(self.trackerid)
+                
+        self.reannounce_interval = config['reannounce_interval']
+        self.save_dfile_interval = config['save_dfile_interval']
+        self.show_names = config['show_names']
+        rawserver.add_task(self.save_state, self.save_dfile_interval)
+        self.prevtime = clock()
+        self.timeout_downloaders_interval = config['timeout_downloaders_interval']
+        rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
+        self.logfile = None
+        self.log = None
+        if (config['logfile']) and (config['logfile'] != '-'):
+            try:
+                self.logfile = config['logfile']
+                self.log = open(self.logfile,'a')
+                sys.stdout = self.log
+                print "# Log Started: ", isotime()
+            except:
+                print "**warning** could not redirect stdout to log file: ", sys.exc_info()[0]
+
+        if config['hupmonitor']:
+            def huphandler(signum, frame, self = self):
+                try:
+                    self.log.close ()
+                    self.log = open(self.logfile,'a')
+                    sys.stdout = self.log
+                    print "# Log reopened: ", isotime()
+                except:
+                    print "**warning** could not reopen logfile"
+             
+            signal.signal(signal.SIGHUP, huphandler)            
+                
+        self.allow_get = config['allow_get']
+        
+        self.t2tlist = T2TList(config['multitracker_enabled'], self.trackerid,
+                               config['multitracker_reannounce_interval'],
+                               config['multitracker_maxpeers'], config['http_timeout'],
+                               self.rawserver)
+
+        if config['allowed_list']:
+            if config['allowed_dir']:
+                print '**warning** allowed_dir and allowed_list options cannot be used together'
+                print '**warning** disregarding allowed_dir'
+                config['allowed_dir'] = ''
+            self.allowed = self.state.setdefault('allowed_list',{})
+            self.allowed_list_mtime = 0
+            self.parse_allowed()
+            self.remove_from_state('allowed','allowed_dir_files')
+            if config['multitracker_allowed'] == 'autodetect':
+                config['multitracker_allowed'] = 'none'
+            config['allowed_controls'] = 0
+
+        elif config['allowed_dir']:
+            self.allowed = self.state.setdefault('allowed',{})
+            self.allowed_dir_files = self.state.setdefault('allowed_dir_files',{})
+            self.allowed_dir_blocked = {}
+            self.parse_allowed()
+            self.remove_from_state('allowed_list')
+
+        else:
+            self.allowed = None
+            self.remove_from_state('allowed','allowed_dir_files', 'allowed_list')
+            if config['multitracker_allowed'] == 'autodetect':
+                config['multitracker_allowed'] = 'none'
+            config['allowed_controls'] = 0
+                
+        self.uq_broken = unquote('+') != ' '
+        self.keep_dead = config['keep_dead']
+        self.Filter = Filter(rawserver.add_task)
+        
+        aggregator = config['aggregator']
+        if aggregator == '0':
+            self.is_aggregator = False
+            self.aggregator_key = None
+        else:
+            self.is_aggregator = True
+            if aggregator == '1':
+                self.aggregator_key = None
+            else:
+                self.aggregator_key = aggregator
+            self.natcheck = False
+                
+        send = config['aggregate_forward']
+        if not send:
+            self.aggregate_forward = None
+        else:
+            try:
+                self.aggregate_forward, self.aggregate_password = send.split(',')
+            except:
+                self.aggregate_forward = send
+                self.aggregate_password = None
+
+        self.dedicated_seed_id = config['dedicated_seed_id']
+        self.is_seeded = {}
+
+        self.cachetime = 0
+        self.cachetimeupdate()
+
+    def cachetimeupdate(self):
+        self.cachetime += 1     # raw clock, but more efficient for cache
+        self.rawserver.add_task(self.cachetimeupdate,1)
+
+    def aggregate_senddata(self, query):
+        url = self.aggregate_forward+'?'+query
+        if self.aggregate_password is not None:
+            url += '&password='+self.aggregate_password
+        rq = Thread(target = self._aggregate_senddata, args = [url])
+        rq.setDaemon(False)
+        rq.start()
+
+    def _aggregate_senddata(self, url):     # just send, don't attempt to error check,
+        try:                                # discard any returned data
+            h = urlopen(url)
+            h.read()
+            h.close()
+        except:
+            return
+
+
+    def get_infopage(self):
+        try:
+            if not self.config['show_infopage']:
+                return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
+            red = self.config['infopage_redirect']
+            if red:
+                return (302, 'Found', {'Content-Type': 'text/html', 'Location': red},
+                        '<A HREF="'+red+'">Click Here</A>')
+            
+            s = StringIO()
+            s.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \
+                '<html><head><title>BitTorrent download info</title>\n')
+            if self.favicon is not None:
+                s.write('<link rel="shortcut icon" href="/favicon.ico">\n')
+            s.write('</head>\n<body>\n' \
+                '<h3>BitTorrent download info</h3>\n'\
+                '<ul>\n'
+                '<li><strong>tracker version:</strong> %s</li>\n' \
+                '<li><strong>server time:</strong> %s</li>\n' \
+                '</ul>\n' % (version, isotime()))
+            if self.config['allowed_dir']:
+                if self.show_names:
+                    names = [ (self.allowed[hash]['name'],hash)
+                              for hash in self.allowed.keys() ]
+                else:
+                    names = [ (None,hash)
+                              for hash in self.allowed.keys() ]
+            else:
+                names = [ (None,hash) for hash in self.downloads.keys() ]
+            if not names:
+                s.write('<p>not tracking any files yet...</p>\n')
+            else:
+                names.sort()
+                tn = 0
+                tc = 0
+                td = 0
+                tt = 0  # Total transferred
+                ts = 0  # Total size
+                nf = 0  # Number of files displayed
+                if self.config['allowed_dir'] and self.show_names:
+                    s.write('<table summary="files" border="1">\n' \
+                        '<tr><th>info hash</th><th>torrent name</th><th align="right">size</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th><th align="right">transferred</th></tr>\n')
+                else:
+                    s.write('<table summary="files">\n' \
+                        '<tr><th>info hash</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th></tr>\n')
+                for name,hash in names:
+                    l = self.downloads[hash]
+                    n = self.completed.get(hash, 0)
+                    tn = tn + n
+                    c = self.seedcount[hash]
+                    tc = tc + c
+                    d = len(l) - c
+                    td = td + d
+                    if self.config['allowed_dir'] and self.show_names:
+                        if self.allowed.has_key(hash):
+                            nf = nf + 1
+                            sz = self.allowed[hash]['length']  # size
+                            ts = ts + sz
+                            szt = sz * n   # Transferred for this torrent
+                            tt = tt + szt
+                            if self.allow_get == 1:
+                                linkname = '<a href="/file?info_hash=' + quote(hash) + '">' + name + '</a>'
+                            else:
+                                linkname = name
+                            s.write('<tr><td><code>%s</code></td><td>%s</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' \
+                                % (b2a_hex(hash), linkname, size_format(sz), c, d, n, size_format(szt)))
+                    else:
+                        s.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \
+                            % (b2a_hex(hash), c, d, n))
+                if self.config['allowed_dir'] and self.show_names:
+                    s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n'
+                            % (nf, size_format(ts), tc, td, tn, size_format(tt)))
+                else:
+                    s.write('<tr><td align="right">%i files</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td></tr>\n'
+                            % (nf, tc, td, tn))
+                s.write('</table>\n' \
+                    '<ul>\n' \
+                    '<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \
+                    '<li><em>complete:</em> number of connected clients with the complete file</li>\n' \
+                    '<li><em>downloading:</em> number of connected clients still downloading</li>\n' \
+                    '<li><em>downloaded:</em> reported complete downloads</li>\n' \
+                    '<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \
+                    '</ul>\n')
+
+            s.write('</body>\n' \
+                '</html>\n')
+            return (200, 'OK', {'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue())
+        except:
+            print_exc()
+            return (500, 'Internal Server Error', {'Content-Type': 'text/html; charset=iso-8859-1'}, 'Server Error')
+
+
+    def scrapedata(self, hash, return_name = True):
+        l = self.downloads[hash]
+        n = self.completed.get(hash, 0)
+        c = self.seedcount[hash]
+        d = len(l) - c
+        f = {'complete': c, 'incomplete': d, 'downloaded': n}
+        if return_name and self.show_names and self.config['allowed_dir']:
+            f['name'] = self.allowed[hash]['name']
+        return (f)
+
+    def get_scrape(self, paramslist):
+        fs = {}
+        if paramslist.has_key('info_hash'):
+            if self.config['scrape_allowed'] not in ['specific', 'full']:
+                return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason':
+                    'specific scrape function is not available with this tracker.'}))
+            for hash in paramslist['info_hash']:
+                if self.allowed is not None:
+                    if self.allowed.has_key(hash):
+                        fs[hash] = self.scrapedata(hash)
+                else:
+                    if self.downloads.has_key(hash):
+                        fs[hash] = self.scrapedata(hash)
+        else:
+            if self.config['scrape_allowed'] != 'full':
+                return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason':
+                    'full scrape function is not available with this tracker.'}))
+            if self.allowed is not None:
+                keys = self.allowed.keys()
+            else:
+                keys = self.downloads.keys()
+            for hash in keys:
+                fs[hash] = self.scrapedata(hash)
+
+        return (200, 'OK', {'Content-Type': 'text/plain'}, bencode({'files': fs}))
+
+
+    def get_file(self, hash):
+         if not self.allow_get:
+             return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                 'get function is not available with this tracker.')
+         if not self.allowed.has_key(hash):
+             return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
+         fname = self.allowed[hash]['file']
+         fpath = self.allowed[hash]['path']
+         return (200, 'OK', {'Content-Type': 'application/x-bittorrent',
+             'Content-Disposition': 'attachment; filename=' + fname},
+             open(fpath, 'rb').read())
+
+
+    def check_allowed(self, infohash, paramslist):
+        if ( self.aggregator_key is not None
+                and not ( paramslist.has_key('password')
+                        and paramslist['password'][0] == self.aggregator_key ) ):
+            return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                bencode({'failure reason':
+                'Requested download is not authorized for use with this tracker.'}))
+
+        if self.allowed is not None:
+            if not self.allowed.has_key(infohash):
+                return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason':
+                    'Requested download is not authorized for use with this tracker.'}))
+            if self.config['allowed_controls']:
+                if self.allowed[infohash].has_key('failure reason'):
+                    return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                        bencode({'failure reason': self.allowed[infohash]['failure reason']}))
+
+        if paramslist.has_key('tracker'):
+            if ( self.config['multitracker_allowed'] == 'none' or       # turned off
+                          paramslist['peer_id'][0] == self.trackerid ): # oops! contacted myself
+                return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason': 'disallowed'}))
+            
+            if ( self.config['multitracker_allowed'] == 'autodetect'
+                        and not self.allowed[infohash].has_key('announce-list') ):
+                return (200, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason':
+                    'Requested download is not authorized for multitracker use.'}))
+
+        return None
+
+    def cache_default(self):
+        return [({},{}) for i in xrange(self.cache_default_len)]
+
+    def add_data(self, infohash, event, ip, paramslist):
+        peers = self.downloads.setdefault(infohash, {})
+        ts = self.times.setdefault(infohash, {})
+        self.completed.setdefault(infohash, 0)
+        self.seedcount.setdefault(infohash, 0)
+
+        def params(key, default = None, l = paramslist):
+            if l.has_key(key):
+                return l[key][0]
+            return default
+        
+        myid = params('peer_id','')
+        if len(myid) != 20:
+            raise ValueError, 'id not of length 20'
+        if event not in ['started', 'completed', 'stopped', 'snooped', None]:
+            raise ValueError, 'invalid event'
+        port = params('cryptoport')
+        if port is None:
+            port = params('port','')
+        port = long(port)
+        if port < 0 or port > 65535:
+            raise ValueError, 'invalid port'
+        left = long(params('left',''))
+        if left < 0:
+            raise ValueError, 'invalid amount left'
+        uploaded = long(params('uploaded',''))
+        downloaded = long(params('downloaded',''))
+        if params('supportcrypto'):
+            supportcrypto = 1
+            try:
+                s = int(params['requirecrypto'])
+                chr(s)
+            except:
+                s = 0
+            requirecrypto = s
+        else:
+            supportcrypto = 0
+            requirecrypto = 0
+
+        peer = peers.get(myid)
+        islocal = local_IPs.includes(ip)
+        mykey = params('key')
+        if peer:
+            auth = peer.get('key',-1) == mykey or peer.get('ip') == ip
+
+        gip = params('ip')
+        if is_valid_ip(gip) and (islocal or not self.only_local_override_ip):
+            ip1 = gip
+        else:
+            ip1 = ip
+
+        if params('numwant') is not None:
+            rsize = min(int(params('numwant')),self.response_size)
+        else:
+            rsize = self.response_size
+
+        if event == 'stopped':
+            if peer:
+                if auth:
+                    self.delete_peer(infohash,myid)
+        
+        elif not peer:
+            ts[myid] = clock()
+            peer = { 'ip': ip, 'port': port, 'left': left,
+                     'supportcrypto': supportcrypto,
+                     'requirecrypto': requirecrypto }
+            if mykey:
+                peer['key'] = mykey
+            if gip:
+                peer['given ip'] = gip
+            if port:
+                if not self.natcheck or islocal:
+                    peer['nat'] = 0
+                    self.natcheckOK(infohash,myid,ip1,port,peer)
+                else:
+                    NatCheck(self.connectback_result,infohash,myid,ip1,port,
+                             self.rawserver,encrypted=requirecrypto)
+            else:
+                peer['nat'] = 2**30
+            if event == 'completed':
+                self.completed[infohash] += 1
+            if not left:
+                self.seedcount[infohash] += 1
+                
+            peers[myid] = peer
+
+        else:
+            if not auth:
+                return rsize    # return w/o changing stats
+
+            ts[myid] = clock()
+            if not left and peer['left']:
+                self.completed[infohash] += 1
+                self.seedcount[infohash] += 1
+                if not peer.get('nat', -1):
+                    for bc in self.becache[infohash]:
+                        x = bc[0].get(myid)
+                        if x:
+                            bc[1][myid] = x
+                            del bc[0][myid]
+            elif left and not peer['left']:
+                self.completed[infohash] -= 1
+                self.seedcount[infohash] -= 1
+                if not peer.get('nat', -1):
+                    for bc in self.becache[infohash]:
+                        x = bc[1].get(myid)
+                        if x:
+                            bc[0][myid] = x
+                            del bc[1][myid]
+            peer['left'] = left
+
+            if port:
+                recheck = False
+                if ip != peer['ip']:
+                    peer['ip'] = ip
+                    recheck = True
+                if gip != peer.get('given ip'):
+                    if gip:
+                        peer['given ip'] = gip
+                    elif peer.has_key('given ip'):
+                        del peer['given ip']
+                    recheck = True
+
+                natted = peer.get('nat', -1)
+                if recheck:
+                    if natted == 0:
+                        l = self.becache[infohash]
+                        y = not peer['left']
+                        for x in l:
+                            if x[y].has_key(myid):
+                                del x[y][myid]
+                    if natted >= 0:
+                        del peer['nat'] # restart NAT testing
+                if natted and natted < self.natcheck:
+                    recheck = True
+
+                if recheck:
+                    if not self.natcheck or islocal:
+                        peer['nat'] = 0
+                        self.natcheckOK(infohash,myid,ip1,port,peer)
+                    else:
+                        NatCheck(self.connectback_result,infohash,myid,ip1,port,
+                                 self.rawserver,encrypted=requirecrypto)
+
+        return rsize
+
+
+    def peerlist(self, infohash, stopped, tracker, is_seed,
+                 return_type, rsize, supportcrypto):
+        data = {}    # return data
+        seeds = self.seedcount[infohash]
+        data['complete'] = seeds
+        data['incomplete'] = len(self.downloads[infohash]) - seeds
+        
+        if ( self.config['allowed_controls']
+                and self.allowed[infohash].has_key('warning message') ):
+            data['warning message'] = self.allowed[infohash]['warning message']
+
+        if tracker:
+            data['interval'] = self.config['multitracker_reannounce_interval']
+            if not rsize:
+                return data
+            cache = self.cached_t.setdefault(infohash, None)
+            if ( not cache or len(cache[1]) < rsize
+                 or cache[0] + self.config['min_time_between_cache_refreshes'] < clock() ):
+                bc = self.becache.setdefault(infohash,self.cache_default())
+                cache = [ clock(), bc[0][0].values() + bc[0][1].values() ]
+                self.cached_t[infohash] = cache
+                shuffle(cache[1])
+                cache = cache[1]
+
+            data['peers'] = cache[-rsize:]
+            del cache[-rsize:]
+            return data
+
+        data['interval'] = self.reannounce_interval
+        if stopped or not rsize:     # save some bandwidth
+            data['peers'] = []
+            return data
+
+        bc = self.becache.setdefault(infohash,self.cache_default())
+        len_l = len(bc[2][0])
+        len_s = len(bc[2][1])
+        if not (len_l+len_s):   # caches are empty!
+            data['peers'] = []
+            return data
+        l_get_size = int(float(rsize)*(len_l)/(len_l+len_s))
+        if self.config['compact_reqd']:
+            cache = self.cached.setdefault(infohash,[None,None,None])[return_type]
+        else:
+            cache = self.cached.setdefault(infohash,[None,None,None,None,None])[return_type]
+        if cache and ( not cache[1]
+                       or (is_seed and len(cache[1]) < rsize)
+                       or len(cache[1]) < l_get_size
+                       or cache[0]+self.config['min_time_between_cache_refreshes'] < self.cachetime ):
+            cache = None
+        if not cache:
+            peers = self.downloads[infohash]
+            if self.config['compact_reqd']:
+                vv = ([],[],[])
+            else:
+                vv = ([],[],[],[],[])
+            for key, ip, port in self.t2tlist.harvest(infohash):   # empty if disabled
+                if not peers.has_key(key):
+                    cp = compact_peer_info(ip, port)
+                    vv[0].append(cp)
+                    vv[2].append((cp,'\x00'))
+                    if not self.config['compact_reqd']:
+                        vv[3].append({'ip': ip, 'port': port, 'peer id': key})
+                        vv[4].append({'ip': ip, 'port': port})
+            cache = [ self.cachetime,
+                      bc[return_type][0].values()+vv[return_type],
+                      bc[return_type][1].values() ]
+            shuffle(cache[1])
+            shuffle(cache[2])
+            self.cached[infohash][return_type] = cache
+            for rr in xrange(len(self.cached[infohash])):
+                if rr != return_type:
+                    try:
+                        self.cached[infohash][rr][1].extend(vv[rr])
+                    except:
+                        pass
+        if len(cache[1]) < l_get_size:
+            peerdata = cache[1]
+            if not is_seed:
+                peerdata.extend(cache[2])
+            cache[1] = []
+            cache[2] = []
+        else:
+            if not is_seed:
+                peerdata = cache[2][l_get_size-rsize:]
+                del cache[2][l_get_size-rsize:]
+                rsize -= len(peerdata)
+            else:
+                peerdata = []
+            if rsize:
+                peerdata.extend(cache[1][-rsize:])
+                del cache[1][-rsize:]
+        if return_type == 0:
+            data['peers'] = ''.join(peerdata)
+        elif return_type == 1:
+            data['crypto_flags'] = "0x01"*len(peerdata)
+            data['peers'] = ''.join(peerdata)
+        elif return_type == 2:
+            data['crypto_flags'] = ''.join([p[1] for p in peerdata])
+            data['peers'] = ''.join([p[0] for p in peerdata])
+        else:
+            data['peers'] = peerdata
+        return data
+
+
+    def get(self, connection, path, headers):
+        real_ip = connection.get_ip()
+        ip = real_ip
+        if is_ipv4(ip):
+            ipv4 = True
+        else:
+            try:
+                ip = ipv6_to_ipv4(ip)
+                ipv4 = True
+            except ValueError:
+                ipv4 = False
+
+        if ( (self.allowed_IPs and not self.allowed_IPs.includes(ip))
+             or (self.banned_IPs and self.banned_IPs.includes(ip)) ):
+            return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                bencode({'failure reason':
+                'your IP is not allowed on this tracker'}))
+
+        nip = get_forwarded_ip(headers)
+        if nip and not self.only_local_override_ip:
+            ip = nip
+            try:
+                ip = to_ipv4(ip)
+                ipv4 = True
+            except ValueError:
+                ipv4 = False
+
+        paramslist = {}
+        def params(key, default = None, l = paramslist):
+            if l.has_key(key):
+                return l[key][0]
+            return default
+
+        try:
+            (scheme, netloc, path, pars, query, fragment) = urlparse(path)
+            if self.uq_broken == 1:
+                path = path.replace('+',' ')
+                query = query.replace('+',' ')
+            path = unquote(path)[1:]
+            for s in query.split('&'):
+                if s:
+                    i = s.index('=')
+                    kw = unquote(s[:i])
+                    paramslist.setdefault(kw, [])
+                    paramslist[kw] += [unquote(s[i+1:])]
+                    
+            if path == '' or path == 'index.html':
+                return self.get_infopage()
+            if (path == 'file'):
+                return self.get_file(params('info_hash'))
+            if path == 'favicon.ico' and self.favicon is not None:
+                return (200, 'OK', {'Content-Type' : 'image/x-icon'}, self.favicon)
+
+            # automated access from here on
+
+            if path in ('scrape', 'scrape.php', 'tracker.php/scrape'):
+                return self.get_scrape(paramslist)
+            
+            if not path in ('announce', 'announce.php', 'tracker.php/announce'):
+                return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
+
+            # main tracker function
+
+            filtered = self.Filter.check(real_ip, paramslist, headers)
+            if filtered:
+                return (400, 'Not Authorized', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'failure reason': filtered}))
+            
+            infohash = params('info_hash')
+            if not infohash:
+                raise ValueError, 'no info hash'
+
+            notallowed = self.check_allowed(infohash, paramslist)
+            if notallowed:
+                return notallowed
+
+            event = params('event')
+
+            rsize = self.add_data(infohash, event, ip, paramslist)
+
+        except ValueError, e:
+            return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 
+                'you sent me garbage - ' + str(e))
+
+        if self.aggregate_forward and not paramslist.has_key('tracker'):
+            self.aggregate_senddata(query)
+
+        if self.is_aggregator:      # don't return peer data here
+            return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},
+                    bencode({'response': 'OK'}))
+
+        if params('compact') and ipv4:
+            if params('requirecrypto'):
+                return_type = 1
+            elif params('supportcrypto'):
+                return_type = 2
+            else:
+                return_type = 0
+        elif self.config['compact_reqd'] and ipv4:
+            return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 
+                'your client is outdated, please upgrade')
+        elif params('no_peer_id'):
+            return_type = 4
+        else:
+            return_type = 3
+            
+        data = self.peerlist(infohash, event=='stopped',
+                             params('tracker'), not params('left'),
+                             return_type, rsize, params('supportcrypto'))
+
+        if paramslist.has_key('scrape'):    # deprecated
+            data['scrape'] = self.scrapedata(infohash, False)
+
+        if self.dedicated_seed_id:
+            if params('seed_id') == self.dedicated_seed_id and params('left') == 0:
+                self.is_seeded[infohash] = True
+            if params('check_seeded') and self.is_seeded.get(infohash):
+                data['seeded'] = 1
+            
+        return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data))
+
+
+    def natcheckOK(self, infohash, peerid, ip, port, peer):
+        seed = not peer['left']
+        bc = self.becache.setdefault(infohash,self.cache_default())
+        cp = compact_peer_info(ip, port)
+        reqc = peer['requirecrypto']
+        bc[2][seed][peerid] = (cp,chr(reqc))
+        if peer['supportcrypto']:
+            bc[1][seed][peerid] = cp
+        if not reqc:
+            bc[0][seed][peerid] = cp
+            if not self.config['compact_reqd']:
+                bc[3][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port,
+                                                         'peer id': peerid}))
+                bc[4][seed][peerid] = Bencached(bencode({'ip': ip, 'port': port}))
+
+
+    def natchecklog(self, peerid, ip, port, result):
+        year, month, day, hour, minute, second, a, b, c = localtime(time())
+        print '%s - %s [%02d/%3s/%04d:%02d:%02d:%02d] "!natcheck-%s:%i" %i 0 - -' % (
+            ip, quote(peerid), day, months[month], year, hour, minute, second,
+            ip, port, result)
+
+    def connectback_result(self, result, downloadid, peerid, ip, port):
+        record = self.downloads.get(downloadid,{}).get(peerid)
+        if ( record is None 
+                 or (record['ip'] != ip and record.get('given ip') != ip)
+                 or record['port'] != port ):
+            if self.config['log_nat_checks']:
+                self.natchecklog(peerid, ip, port, 404)
+            return
+        if self.config['log_nat_checks']:
+            if result:
+                x = 200
+            else:
+                x = 503
+            self.natchecklog(peerid, ip, port, x)
+        if not record.has_key('nat'):
+            record['nat'] = int(not result)
+            if result:
+                self.natcheckOK(downloadid,peerid,ip,port,record)
+        elif result and record['nat']:
+            record['nat'] = 0
+            self.natcheckOK(downloadid,peerid,ip,port,record)
+        elif not result:
+            record['nat'] += 1
+
+
+    def remove_from_state(self, *l):
+        for s in l:
+            try:
+                del self.state[s]
+            except:
+                pass
+
+    def save_state(self):
+        self.rawserver.add_task(self.save_state, self.save_dfile_interval)
+        h = open(self.dfile, 'wb')
+        h.write(bencode(self.state))
+        h.close()
+
+
+    def parse_allowed(self):
+        self.rawserver.add_task(self.parse_allowed, self.parse_dir_interval)
+
+        if self.config['allowed_dir']:
+            r = parsedir( self.config['allowed_dir'], self.allowed,
+                          self.allowed_dir_files, self.allowed_dir_blocked,
+                          [".torrent"] )
+            ( self.allowed, self.allowed_dir_files, self.allowed_dir_blocked,
+                added, garbage2 ) = r
+            
+            self.state['allowed'] = self.allowed
+            self.state['allowed_dir_files'] = self.allowed_dir_files
+
+            self.t2tlist.parse(self.allowed)
+            
+        else:
+            f = self.config['allowed_list']
+            if self.allowed_list_mtime == os.path.getmtime(f):
+                return
+            try:
+                r = parsetorrentlist(f, self.allowed)
+                (self.allowed, added, garbage2) = r
+                self.state['allowed_list'] = self.allowed
+            except (IOError, OSError):
+                print '**warning** unable to read allowed torrent list'
+                return
+            self.allowed_list_mtime = os.path.getmtime(f)
+
+        for infohash in added.keys():
+            self.downloads.setdefault(infohash, {})
+            self.completed.setdefault(infohash, 0)
+            self.seedcount.setdefault(infohash, 0)
+
+
+    def read_ip_lists(self):
+        self.rawserver.add_task(self.read_ip_lists,self.parse_dir_interval)
+            
+        f = self.config['allowed_ips']
+        if f and self.allowed_ip_mtime != os.path.getmtime(f):
+            self.allowed_IPs = IP_List()
+            try:
+                self.allowed_IPs.read_fieldlist(f)
+                self.allowed_ip_mtime = os.path.getmtime(f)
+            except (IOError, OSError):
+                print '**warning** unable to read allowed_IP list'
+                
+        f = self.config['banned_ips']
+        if f and self.banned_ip_mtime != os.path.getmtime(f):
+            self.banned_IPs = IP_Range_List()
+            try:
+                self.banned_IPs.read_rangelist(f)
+                self.banned_ip_mtime = os.path.getmtime(f)
+            except (IOError, OSError):
+                print '**warning** unable to read banned_IP list'
+                
+
+    def delete_peer(self, infohash, peerid):
+        dls = self.downloads[infohash]
+        peer = dls[peerid]
+        if not peer['left']:
+            self.seedcount[infohash] -= 1
+        if not peer.get('nat',-1):
+            l = self.becache[infohash]
+            y = not peer['left']
+            for x in l:
+                if x[y].has_key(peerid):
+                    del x[y][peerid]
+        del self.times[infohash][peerid]
+        del dls[peerid]
+
+    def expire_downloaders(self):
+        for x in self.times.keys():
+            for myid, t in self.times[x].items():
+                if t < self.prevtime:
+                    self.delete_peer(x,myid)
+        self.prevtime = clock()
+        if (self.keep_dead != 1):
+            for key, value in self.downloads.items():
+                if len(value) == 0 and (
+                        self.allowed is None or not self.allowed.has_key(key) ):
+                    del self.times[key]
+                    del self.downloads[key]
+                    del self.seedcount[key]
+        self.rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
+
+
+def track(args):
+    if len(args) == 0:
+        print formatDefinitions(defaults, 80)
+        return
+    try:
+        config, files = parseargs(args, defaults, 0, 0)
+    except ValueError, e:
+        print 'error: ' + str(e)
+        print 'run with no arguments for parameter explanations'
+        return
+    r = RawServer(Event(), config['timeout_check_interval'],
+                  config['socket_timeout'], ipv6_enable = config['ipv6_enabled'])
+    t = Tracker(config, r)
+    r.bind(config['port'], config['bind'],
+           reuse = True, ipv6_socket_style = config['ipv6_binds_v4'])
+    r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))
+    t.save_state()
+    print '# Shutting down: ' + isotime()
+
+def size_format(s):
+    if (s < 1024):
+        r = str(s) + 'B'
+    elif (s < 1048576):
+        r = str(int(s/1024)) + 'KiB'
+    elif (s < 1073741824L):
+        r = str(int(s/1048576)) + 'MiB'
+    elif (s < 1099511627776L):
+        r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB'
+    else:
+        r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB'
+    return(r)
+
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BTcrypto.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BTcrypto.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/BTcrypto.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/BTcrypto.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,104 @@
+# Written by John Hoffman
+# based on code by Uoti Urpala
+# see LICENSE.txt for license information
+
+from __future__ import generators   # for python 2.2
+from random import randrange,randint,seed
+try:
+    from os import urandom
+    urandom(1)
+except:
+    seed()
+    urandom = lambda x: ''.join([chr(randint(0,255)) for i in xrange(x)])
+from sha import sha
+
+try:
+    True
+except:
+    True = 1
+    False = 0
+    
+try:
+    from Crypto.Cipher import ARC4
+    CRYPTO_OK = True
+except:
+    CRYPTO_OK = False
+
+KEY_LENGTH = 160
+DH_PRIME = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563
+PAD_MAX = 200 # less than protocol maximum, and later assumed to be < 256
+DH_BYTES = 96
+
+def bytetonum(x):
+    return long(x.encode('hex'), 16)
+
+def numtobyte(x):
+    x = hex(x).lstrip('0x').rstrip('Ll')
+    x = '0'*(192 - len(x)) + x
+    return x.decode('hex')
+
+class Crypto:
+    def __init__(self, initiator, disable_crypto = False):
+        self.initiator = initiator
+        self.disable_crypto = disable_crypto
+        if not disable_crypto and not CRYPTO_OK:
+            raise NotImplementedError, "attempt to run encryption w/ none installed"
+        self.privkey = bytetonum(urandom(KEY_LENGTH/8))
+        self.pubkey = numtobyte(pow(2, self.privkey, DH_PRIME))
+        self.keylength = DH_BYTES
+        self._VC_pattern = None
+
+    def received_key(self, k):
+        self.S = numtobyte(pow(bytetonum(k), self.privkey, DH_PRIME))
+        self.block3a = sha('req1'+self.S).digest()
+        self.block3bkey = sha('req3'+self.S).digest()
+        self.block3b = None
+
+    def _gen_block3b(self, SKEY):
+        a = sha('req2'+SKEY).digest()
+        return ''.join([ chr(ord(a[i])^ord(self.block3bkey[i]))
+                         for i in xrange(20) ])
+
+    def test_skey(self, s, SKEY):
+        block3b = self._gen_block3b(SKEY)
+        if block3b != s:
+            return False
+        self.block3b = block3b
+        if not self.disable_crypto:
+            self.set_skey(SKEY)
+        return True
+
+    def set_skey(self, SKEY):
+        if not self.block3b:
+            self.block3b = self._gen_block3b(SKEY)
+        crypta = ARC4.new(sha('keyA'+self.S+SKEY).digest())
+        cryptb = ARC4.new(sha('keyB'+self.S+SKEY).digest())
+        if self.initiator:
+            self.encrypt = crypta.encrypt
+            self.decrypt = cryptb.decrypt
+        else:
+            self.encrypt = cryptb.encrypt
+            self.decrypt = crypta.decrypt
+        self.encrypt('x'*1024)  # discard first 1024 bytes
+        self.decrypt('x'*1024)
+
+    def VC_pattern(self):
+        if not self._VC_pattern:
+            self._VC_pattern = self.decrypt('\x00'*8)
+        return self._VC_pattern
+
+
+    def read(self, s):
+        self._read(self.decrypt(s))
+
+    def write(self, s):
+        self._write(self.encrypt(s))
+
+    def setrawaccess(self, _read, _write):
+        self._read = _read
+        self._write = _write
+
+    def padding(self):
+        return urandom(randrange(PAD_MAX-16)+16)
+     
+        
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/download_bt1.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/download_bt1.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/download_bt1.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/download_bt1.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,868 @@
+# Written by Bram Cohen
+# see LICENSE.txt for license information
+
+from zurllib import urlopen
+from urlparse import urlparse
+from BT1.btformats import check_message
+from BT1.Choker import Choker
+from BT1.Storage import Storage
+from BT1.StorageWrapper import StorageWrapper
+from BT1.FileSelector import FileSelector
+from BT1.Uploader import Upload
+from BT1.Downloader import Downloader
+from BT1.HTTPDownloader import HTTPDownloader
+from BT1.Connecter import Connecter
+from RateLimiter import RateLimiter
+from BT1.Encrypter import Encoder
+from RawServer import RawServer, autodetect_ipv6, autodetect_socket_style
+from BT1.Rerequester import Rerequester
+from BT1.DownloaderFeedback import DownloaderFeedback
+from RateMeasure import RateMeasure
+from CurrentRateMeasure import Measure
+from BT1.PiecePicker import PiecePicker
+from BT1.Statistics import Statistics
+from ConfigDir import ConfigDir
+from bencode import bencode, bdecode
+from natpunch import UPnP_test
+from sha import sha
+from os import path, makedirs, listdir
+from parseargs import parseargs, formatDefinitions, defaultargs
+from socket import error as socketerror
+from random import seed
+from threading import Thread, Event
+from clock import clock
+from BTcrypto import CRYPTO_OK
+from __init__ import createPeerID
+
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+defaults = [
+    ('max_uploads', 7,
+        "the maximum number of uploads to allow at once."),
+    ('keepalive_interval', 120.0,
+        'number of seconds to pause between sending keepalives'),
+    ('download_slice_size', 2 ** 14,
+        "How many bytes to query for per request."),
+    ('upload_unit_size', 1460,
+        "when limiting upload rate, how many bytes to send at a time"),
+    ('request_backlog', 10,
+        "maximum number of requests to keep in a single pipe at once."),
+    ('max_message_length', 2 ** 23,
+        "maximum length prefix encoding you'll accept over the wire - larger values get the connection dropped."),
+    ('ip', '',
+        "ip to report you have to the tracker."),
+    ('minport', 10000, 'minimum port to listen on, counts up if unavailable'),
+    ('maxport', 60000, 'maximum port to listen on'),
+    ('random_port', 1, 'whether to choose randomly inside the port range ' +
+        'instead of counting up linearly'),
+    ('responsefile', '',
+        'file the server response was stored in, alternative to url'),
+    ('url', '',
+        'url to get file from, alternative to responsefile'),
+    ('crypto_allowed', int(CRYPTO_OK),
+        'whether to allow the client to accept encrypted connections'),
+    ('crypto_only', 0,
+        'whether to only create or allow encrypted connections'),
+    ('crypto_stealth', 0,
+        'whether to prevent all non-encrypted connection attempts; ' +
+        'will result in an effectively firewalled state on older trackers'),
+    ('selector_enabled', 1,
+        'whether to enable the file selector and fast resume function'),
+    ('expire_cache_data', 10,
+        'the number of days after which you wish to expire old cache data ' +
+        '(0 = disabled)'),
+    ('priority', '',
+        'a list of file priorities separated by commas, must be one per file, ' +
+        '0 = highest, 1 = normal, 2 = lowest, -1 = download disabled'),
+    ('saveas', '',
+        'local file name to save the file as, null indicates query user'),
+    ('timeout', 300.0,
+        'time to wait between closing sockets which nothing has been received on'),
+    ('timeout_check_interval', 60.0,
+        'time to wait between checking if any connections have timed out'),
+    ('max_slice_length', 2 ** 17,
+        "maximum length slice to send to peers, larger requests are ignored"),
+    ('max_rate_period', 20.0,
+        "maximum amount of time to guess the current rate estimate represents"),
+    ('bind', '', 
+        'comma-separated list of ips/hostnames to bind to locally'),
+#    ('ipv6_enabled', autodetect_ipv6(),
+    ('ipv6_enabled', 0,
+         'allow the client to connect to peers via IPv6'),
+    ('ipv6_binds_v4', autodetect_socket_style(),
+        "set if an IPv6 server socket won't also field IPv4 connections"),
+    ('upload_rate_fudge', 5.0, 
+        'time equivalent of writing to kernel-level TCP buffer, for rate adjustment'),
+    ('tcp_ack_fudge', 0.03,
+        'how much TCP ACK download overhead to add to upload rate calculations ' +
+        '(0 = disabled)'),
+    ('display_interval', .5,
+        'time between updates of displayed information'),
+    ('rerequest_interval', 5 * 60,
+        'time to wait between requesting more peers'),
+    ('min_peers', 20, 
+        'minimum number of peers to not do rerequesting'),
+    ('http_timeout', 60, 
+        'number of seconds to wait before assuming that an http connection has timed out'),
+    ('max_initiate', 40,
+        'number of peers at which to stop initiating new connections'),
+    ('check_hashes', 1,
+        'whether to check hashes on disk'),
+    ('max_upload_rate', 0,
+        'maximum kB/s to upload at (0 = no limit, -1 = automatic)'),
+    ('max_download_rate', 0,
+        'maximum kB/s to download at (0 = no limit)'),
+    ('alloc_type', 'normal',
+        'allocation type (may be normal, background, pre-allocate or sparse)'),
+    ('alloc_rate', 2.0,
+        'rate (in MiB/s) to allocate space at using background allocation'),
+    ('buffer_reads', 1,
+        'whether to buffer disk reads'),
+    ('write_buffer_size', 4,
+        'the maximum amount of space to use for buffering disk writes ' +
+        '(in megabytes, 0 = disabled)'),
+    ('breakup_seed_bitfield', 1,
+        'sends an incomplete bitfield and then fills with have messages, '
+        'in order to get around stupid ISP manipulation'),
+    ('snub_time', 30.0,
+        "seconds to wait for data to come in over a connection before assuming it's semi-permanently choked"),
+    ('spew', 0,
+        "whether to display diagnostic info to stdout"),
+    ('rarest_first_cutoff', 2,
+        "number of downloads at which to switch from random to rarest first"),
+    ('rarest_first_priority_cutoff', 5,
+        'the number of peers which need to have a piece before other partials take priority over rarest first'),
+    ('min_uploads', 4,
+        "the number of uploads to fill out to with extra optimistic unchokes"),
+    ('max_files_open', 50,
+        'the maximum number of files to keep open at a time, 0 means no limit'),
+    ('round_robin_period', 30,
+        "the number of seconds between the client's switching upload targets"),
+    ('super_seeder', 0,
+        "whether to use special upload-efficiency-maximizing routines (only for dedicated seeds)"),
+    ('security', 1,
+        "whether to enable extra security features intended to prevent abuse"),
+    ('max_connections', 0,
+        "the absolute maximum number of peers to connect with (0 = no limit)"),
+    ('auto_kick', 1,
+        "whether to allow the client to automatically kick/ban peers that send bad data"),
+    ('double_check', 1,
+        "whether to double-check data being written to the disk for errors (may increase CPU load)"),
+    ('triple_check', 0,
+        "whether to thoroughly check data being written to the disk (may slow disk access)"),
+    ('lock_files', 1,
+        "whether to lock files the client is working with"),
+    ('lock_while_reading', 0,
+        "whether to lock access to files being read"),
+    ('auto_flush', 0,
+        "minutes between automatic flushes to disk (0 = disabled)"),
+    ('dedicated_seed_id', '',
+        "code to send to tracker identifying as a dedicated seed"),
+    ]
+
+argslistheader = 'Arguments are:\n\n'
+
+
+def _failfunc(x):
+    print x
+
+# old-style downloader
+def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols,
+             pathFunc = None, presets = {}, exchandler = None,
+             failed = _failfunc, paramfunc = None):
+
+    try:
+        config = parse_params(params, presets)
+    except ValueError, e:
+        failed('error: ' + str(e) + '\nrun with no args for parameter explanations')
+        return
+    if not config:
+        errorfunc(get_usage())
+        return
+    
+    myid = createPeerID()
+    seed(myid)
+
+    rawserver = RawServer(doneflag, config['timeout_check_interval'],
+                          config['timeout'], ipv6_enable = config['ipv6_enabled'],
+                          failfunc = failed, errorfunc = exchandler)
+
+    upnp_type = 0
+    try:
+        listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
+                        config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
+                        upnp = upnp_type, randomizer = config['random_port'])
+    except socketerror, e:
+        failed("Couldn't listen - " + str(e))
+        return
+
+    response = get_response(config['responsefile'], config['url'], failed)
+    if not response:
+        return
+
+    infohash = sha(bencode(response['info'])).digest()
+
+    d = BT1Download(statusfunc, finfunc, errorfunc, exchandler, doneflag,
+                    config, response, infohash, myid, rawserver, listen_port)
+
+    if not d.saveAs(filefunc):
+        return
+
+    if pathFunc:
+        pathFunc(d.getFilename())
+
+    hashcheck = d.initFiles(old_style = True)
+    if not hashcheck:
+        return
+    if not hashcheck():
+        return
+    if not d.startEngine():
+        return
+    d.startRerequester()
+    d.autoStats()
+
+    statusfunc(activity = 'connecting to peers')
+
+    if paramfunc:
+        paramfunc({ 'max_upload_rate' : d.setUploadRate,  # change_max_upload_rate(<int KiB/sec>)
+                    'max_uploads': d.setConns, # change_max_uploads(<int max uploads>)
+                    'listen_port' : listen_port, # int
+                    'peer_id' : myid, # string
+                    'info_hash' : infohash, # string
+                    'start_connection' : d._startConnection, # start_connection((<string ip>, <int port>), <peer id>)
+                    })
+        
+    rawserver.listen_forever(d.getPortHandler())
+    
+    d.shutdown()
+
+
+def parse_params(params, presets = {}):
+    if len(params) == 0:
+        return None
+    config, args = parseargs(params, defaults, 0, 1, presets = presets)
+    if args:
+        if config['responsefile'] or config['url']:
+            raise ValueError,'must have responsefile or url as arg or parameter, not both'
+        if path.isfile(args[0]):
+            config['responsefile'] = args[0]
+        else:
+            try:
+                urlparse(args[0])
+            except:
+                raise ValueError, 'bad filename or url'
+            config['url'] = args[0]
+    elif (config['responsefile'] == '') == (config['url'] == ''):
+        raise ValueError, 'need responsefile or url, must have one, cannot have both'
+    return config
+
+
+def get_usage(defaults = defaults, cols = 100, presets = {}):
+    return (argslistheader + formatDefinitions(defaults, cols, presets))
+
+
+def get_response(file, url, errorfunc):
+    try:
+        if file:
+            h = open(file, 'rb')
+            try:
+                line = h.read(10)   # quick test to see if responsefile contains a dict
+                front,garbage = line.split(':',1)
+                assert front[0] == 'd'
+                int(front[1:])
+            except:
+                errorfunc(file+' is not a valid responsefile')
+                return None
+            try:
+                h.seek(0)
+            except:
+                try:
+                    h.close()
+                except:
+                    pass
+                h = open(file, 'rb')
+        else:
+            try:
+                h = urlopen(url)
+            except:
+                errorfunc(url+' bad url')
+                return None
+        response = h.read()
+    
+    except IOError, e:
+        errorfunc('problem getting response info - ' + str(e))
+        return None
+    try:    
+        h.close()
+    except:
+        pass
+    try:
+        try:
+            response = bdecode(response)
+        except:
+            errorfunc("warning: bad data in responsefile")
+            response = bdecode(response, sloppy=1)
+        check_message(response)
+    except ValueError, e:
+        errorfunc("got bad file info - " + str(e))
+        return None
+
+    return response
+
+
+class BT1Download:    
+    def __init__(self, statusfunc, finfunc, errorfunc, excfunc, doneflag,
+                 config, response, infohash, id, rawserver, port,
+                 appdataobj = None):
+        self.statusfunc = statusfunc
+        self.finfunc = finfunc
+        self.errorfunc = errorfunc
+        self.excfunc = excfunc
+        self.doneflag = doneflag
+        self.config = config
+        self.response = response
+        self.infohash = infohash
+        self.myid = id
+        self.rawserver = rawserver
+        self.port = port
+        
+        self.info = self.response['info']
+        self.pieces = [self.info['pieces'][x:x+20]
+                       for x in xrange(0, len(self.info['pieces']), 20)]
+        self.len_pieces = len(self.pieces)
+        self.argslistheader = argslistheader
+        self.unpauseflag = Event()
+        self.unpauseflag.set()
+        self.downloader = None
+        self.storagewrapper = None
+        self.fileselector = None
+        self.super_seeding_active = False
+        self.filedatflag = Event()
+        self.spewflag = Event()
+        self.superseedflag = Event()
+        self.whenpaused = None
+        self.finflag = Event()
+        self.rerequest = None
+        self.tcp_ack_fudge = config['tcp_ack_fudge']
+
+        self.selector_enabled = config['selector_enabled']
+        if appdataobj:
+            self.appdataobj = appdataobj
+        elif self.selector_enabled:
+            self.appdataobj = ConfigDir()
+            self.appdataobj.deleteOldCacheData( config['expire_cache_data'],
+                                                [self.infohash] )
+
+        self.excflag = self.rawserver.get_exception_flag()
+        self.failed = False
+        self.checking = False
+        self.started = False
+
+        self.picker = PiecePicker(self.len_pieces, config['rarest_first_cutoff'],
+                             config['rarest_first_priority_cutoff'])
+        self.choker = Choker(config, rawserver.add_task,
+                             self.picker, self.finflag.isSet)
+
+
+    def checkSaveLocation(self, loc):
+        if self.info.has_key('length'):
+            return path.exists(loc)
+        for x in self.info['files']:
+            if path.exists(path.join(loc, x['path'][0])):
+                return True
+        return False
+                
+
+    def saveAs(self, filefunc, pathfunc = None):
+        try:
+            def make(f, forcedir = False):
+                if not forcedir:
+                    f = path.split(f)[0]
+                if f != '' and not path.exists(f):
+                    makedirs(f)
+
+            if self.info.has_key('length'):
+                file_length = self.info['length']
+                file = filefunc(self.info['name'], file_length,
+                                self.config['saveas'], False)
+                if file is None:
+                    return None
+                make(file)
+                files = [(file, file_length)]
+            else:
+                file_length = 0L
+                for x in self.info['files']:
+                    file_length += x['length']
+                file = filefunc(self.info['name'], file_length,
+                                self.config['saveas'], True)
+                if file is None:
+                    return None
+
+                # if this path exists, and no files from the info dict exist, we assume it's a new download and 
+                # the user wants to create a new directory with the default name
+                existing = 0
+                if path.exists(file):
+                    if not path.isdir(file):
+                        self.errorfunc(file + 'is not a dir')
+                        return None
+                    if len(listdir(file)) > 0:  # if it's not empty
+                        for x in self.info['files']:
+                            if path.exists(path.join(file, x['path'][0])):
+                                existing = 1
+                        if not existing:
+                            file = path.join(file, self.info['name'])
+                            if path.exists(file) and not path.isdir(file):
+                                if file[-8:] == '.torrent':
+                                    file = file[:-8]
+                                if path.exists(file) and not path.isdir(file):
+                                    self.errorfunc("Can't create dir - " + self.info['name'])
+                                    return None
+                make(file, True)
+
+                # alert the UI to any possible change in path
+                if pathfunc != None:
+                    pathfunc(file)
+
+                files = []
+                for x in self.info['files']:
+                    n = file
+                    for i in x['path']:
+                        n = path.join(n, i)
+                    files.append((n, x['length']))
+                    make(n)
+        except OSError, e:
+            self.errorfunc("Couldn't allocate dir - " + str(e))
+            return None
+
+        self.filename = file
+        self.files = files
+        self.datalength = file_length
+
+        return file
+    
+
+    def getFilename(self):
+        return self.filename
+
+
+    def _finished(self):
+        self.finflag.set()
+        try:
+            self.storage.set_readonly()
+        except (IOError, OSError), e:
+            self.errorfunc('trouble setting readonly at end - ' + str(e))
+        if self.superseedflag.isSet():
+            self._set_super_seed()
+        self.choker.set_round_robin_period(
+            max( self.config['round_robin_period'],
+                 self.config['round_robin_period'] *
+                                     self.info['piece length'] / 200000 ) )
+        self.rerequest_complete()
+        self.finfunc()
+
+    def _data_flunked(self, amount, index):
+        self.ratemeasure_datarejected(amount)
+        if not self.doneflag.isSet():
+            self.errorfunc('piece %d failed hash check, re-downloading it' % index)
+
+    def _failed(self, reason):
+        self.failed = True
+        self.doneflag.set()
+        if reason is not None:
+            self.errorfunc(reason)
+        
+
+    def initFiles(self, old_style = False, statusfunc = None):
+        if self.doneflag.isSet():
+            return None
+        if not statusfunc:
+            statusfunc = self.statusfunc
+
+        disabled_files = None
+        if self.selector_enabled:
+            self.priority = self.config['priority']
+            if self.priority:
+                try:
+                    self.priority = self.priority.split(',')
+                    assert len(self.priority) == len(self.files)
+                    self.priority = [int(p) for p in self.priority]
+                    for p in self.priority:
+                        assert p >= -1
+                        assert p <= 2
+                except:
+                    self.errorfunc('bad priority list given, ignored')
+                    self.priority = None
+
+            data = self.appdataobj.getTorrentData(self.infohash)
+            try:
+                d = data['resume data']['priority']
+                assert len(d) == len(self.files)
+                disabled_files = [x == -1 for x in d]
+            except:
+                try:
+                    disabled_files = [x == -1 for x in self.priority]
+                except:
+                    pass
+
+        try:
+            try:
+                self.storage = Storage(self.files, self.info['piece length'],
+                                       self.doneflag, self.config, disabled_files)
+            except IOError, e:
+                self.errorfunc('trouble accessing files - ' + str(e))
+                return None
+            if self.doneflag.isSet():
+                return None
+
+            self.storagewrapper = StorageWrapper(self.storage, self.config['download_slice_size'],
+                self.pieces, self.info['piece length'], self._finished, self._failed,
+                statusfunc, self.doneflag, self.config['check_hashes'],
+                self._data_flunked, self.rawserver.add_task,
+                self.config, self.unpauseflag)
+            
+        except ValueError, e:
+            self._failed('bad data - ' + str(e))
+        except IOError, e:
+            self._failed('IOError - ' + str(e))
+        if self.doneflag.isSet():
+            return None
+
+        if self.selector_enabled:
+            self.fileselector = FileSelector(self.files, self.info['piece length'],
+                                             self.appdataobj.getPieceDir(self.infohash),
+                                             self.storage, self.storagewrapper,
+                                             self.rawserver.add_task,
+                                             self._failed)
+            if data:
+                data = data.get('resume data')
+                if data:
+                    self.fileselector.unpickle(data)
+                
+        self.checking = True
+        if old_style:
+            return self.storagewrapper.old_style_init()
+        return self.storagewrapper.initialize
+
+
+    def getCachedTorrentData(self):
+        return self.appdataobj.getTorrentData(self.infohash)
+
+
+    def _make_upload(self, connection, ratelimiter, totalup):
+        return Upload(connection, ratelimiter, totalup,
+                      self.choker, self.storagewrapper, self.picker,
+                      self.config)
+
+    def _kick_peer(self, connection):
+        def k(connection = connection):
+            connection.close()
+        self.rawserver.add_task(k,0)
+
+    def _ban_peer(self, ip):
+        self.encoder_ban(ip)
+
+    def _received_raw_data(self, x):
+        if self.tcp_ack_fudge:
+            x = int(x*self.tcp_ack_fudge)
+            self.ratelimiter.adjust_sent(x)
+
+    def _received_data(self, x):
+        self.downmeasure.update_rate(x)
+        self.ratemeasure.data_came_in(x)
+
+    def _received_http_data(self, x):
+        self.downmeasure.update_rate(x)
+        self.ratemeasure.data_came_in(x)
+        self.downloader.external_data_received(x)
+
+    def _cancelfunc(self, pieces):
+        self.downloader.cancel_piece_download(pieces)
+        self.httpdownloader.cancel_piece_download(pieces)
+    def _reqmorefunc(self, pieces):
+        self.downloader.requeue_piece_download(pieces)
+
+    def startEngine(self, ratelimiter = None, statusfunc = None):
+        if self.doneflag.isSet():
+            return False
+        if not statusfunc:
+            statusfunc = self.statusfunc
+
+        self.checking = False
+
+        if not CRYPTO_OK:
+            if self.config['crypto_allowed']:
+                self.errorfunc('warning - crypto library not installed')
+            self.config['crypto_allowed'] = 0
+            self.config['crypto_only'] = 0
+            self.config['crypto_stealth'] = 0
+
+        for i in xrange(self.len_pieces):
+            if self.storagewrapper.do_I_have(i):
+                self.picker.complete(i)
+        self.upmeasure = Measure(self.config['max_rate_period'],
+                            self.config['upload_rate_fudge'])
+        self.downmeasure = Measure(self.config['max_rate_period'])
+
+        if ratelimiter:
+            self.ratelimiter = ratelimiter
+        else:
+            self.ratelimiter = RateLimiter(self.rawserver.add_task,
+                                           self.config['upload_unit_size'],
+                                           self.setConns)
+            self.ratelimiter.set_upload_rate(self.config['max_upload_rate'])
+        
+        self.ratemeasure = RateMeasure()
+        self.ratemeasure_datarejected = self.ratemeasure.data_rejected
+
+        self.downloader = Downloader(self.storagewrapper, self.picker,
+            self.config['request_backlog'], self.config['max_rate_period'],
+            self.len_pieces, self.config['download_slice_size'],
+            self._received_data, self.config['snub_time'], self.config['auto_kick'],
+            self._kick_peer, self._ban_peer)
+        self.downloader.set_download_rate(self.config['max_download_rate'])
+        self.connecter = Connecter(self._make_upload, self.downloader, self.choker,
+                            self.len_pieces, self.upmeasure, self.config,
+                            self.ratelimiter, self.rawserver.add_task)
+        self.encoder = Encoder(self.connecter, self.rawserver,
+            self.myid, self.config['max_message_length'], self.rawserver.add_task,
+            self.config['keepalive_interval'], self.infohash,
+            self._received_raw_data, self.config)
+        self.encoder_ban = self.encoder.ban
+
+        self.httpdownloader = HTTPDownloader(self.storagewrapper, self.picker,
+            self.rawserver, self.finflag, self.errorfunc, self.downloader,
+            self.config['max_rate_period'], self.infohash, self._received_http_data,
+            self.connecter.got_piece)
+        if self.response.has_key('httpseeds') and not self.finflag.isSet():
+            for u in self.response['httpseeds']:
+                self.httpdownloader.make_download(u)
+
+        if self.selector_enabled:
+            self.fileselector.tie_in(self.picker, self._cancelfunc,
+                    self._reqmorefunc, self.rerequest_ondownloadmore)
+            if self.priority:
+                self.fileselector.set_priorities_now(self.priority)
+            self.appdataobj.deleteTorrentData(self.infohash)
+                                # erase old data once you've started modifying it
+
+        if self.config['super_seeder']:
+            self.set_super_seed()
+
+        self.started = True
+        return True
+
+
+    def rerequest_complete(self):
+        if self.rerequest:
+            self.rerequest.announce(1)
+
+    def rerequest_stopped(self):
+        if self.rerequest:
+            self.rerequest.announce(2)
+
+    def rerequest_lastfailed(self):
+        if self.rerequest:
+            return self.rerequest.last_failed
+        return False
+
+    def rerequest_ondownloadmore(self):
+        if self.rerequest:
+            self.rerequest.hit()
+
+    def startRerequester(self, seededfunc = None, force_rapid_update = False):
+        if self.response.has_key('announce-list'):
+            trackerlist = self.response['announce-list']
+        else:
+            trackerlist = [[self.response['announce']]]
+
+        self.rerequest = Rerequester(self.port, self.myid, self.infohash, 
+            trackerlist, self.config, 
+            self.rawserver.add_task, self.rawserver.add_task,
+            self.errorfunc, self.excfunc,
+            self.encoder.start_connections,
+            self.connecter.how_many_connections, 
+            self.storagewrapper.get_amount_left, 
+            self.upmeasure.get_total, self.downmeasure.get_total,
+            self.upmeasure.get_rate, self.downmeasure.get_rate,
+            self.doneflag, self.unpauseflag, seededfunc, force_rapid_update )
+
+        self.rerequest.start()
+
+
+    def _init_stats(self):
+        self.statistics = Statistics(self.upmeasure, self.downmeasure,
+                    self.connecter, self.httpdownloader, self.ratelimiter,
+                    self.rerequest_lastfailed, self.filedatflag)
+        if self.info.has_key('files'):
+            self.statistics.set_dirstats(self.files, self.info['piece length'])
+        if self.config['spew']:
+            self.spewflag.set()
+
+    def autoStats(self, displayfunc = None):
+        if not displayfunc:
+            displayfunc = self.statusfunc
+
+        self._init_stats()
+        DownloaderFeedback(self.choker, self.httpdownloader, self.rawserver.add_task,
+            self.upmeasure.get_rate, self.downmeasure.get_rate,
+            self.ratemeasure, self.storagewrapper.get_stats,
+            self.datalength, self.finflag, self.spewflag, self.statistics,
+            displayfunc, self.config['display_interval'])
+
+    def startStats(self):
+        self._init_stats()
+        d = DownloaderFeedback(self.choker, self.httpdownloader, self.rawserver.add_task,
+            self.upmeasure.get_rate, self.downmeasure.get_rate,
+            self.ratemeasure, self.storagewrapper.get_stats,
+            self.datalength, self.finflag, self.spewflag, self.statistics)
+        return d.gather
+
+
+    def getPortHandler(self):
+        return self.encoder
+
+
+    def shutdown(self, torrentdata = {}):
+        if self.checking or self.started:
+            self.storagewrapper.sync()
+            self.storage.close()
+            self.rerequest_stopped()
+        if self.fileselector and self.started:
+            if not self.failed:
+                self.fileselector.finish()
+                torrentdata['resume data'] = self.fileselector.pickle()
+            try:
+                self.appdataobj.writeTorrentData(self.infohash,torrentdata)
+            except:
+                self.appdataobj.deleteTorrentData(self.infohash) # clear it
+        return not self.failed and not self.excflag.isSet()
+        # if returns false, you may wish to auto-restart the torrent
+
+
+    def setUploadRate(self, rate):
+        try:
+            def s(self = self, rate = rate):
+                self.config['max_upload_rate'] = rate
+                self.ratelimiter.set_upload_rate(rate)
+            self.rawserver.add_task(s)
+        except AttributeError:
+            pass
+
+    def setConns(self, conns, conns2 = None):
+        if not conns2:
+            conns2 = conns
+        try:
+            def s(self = self, conns = conns, conns2 = conns2):
+                self.config['min_uploads'] = conns
+                self.config['max_uploads'] = conns2
+                if (conns > 30):
+                    self.config['max_initiate'] = conns + 10
+            self.rawserver.add_task(s)
+        except AttributeError:
+            pass
+        
+    def setDownloadRate(self, rate):
+        try:
+            def s(self = self, rate = rate):
+                self.config['max_download_rate'] = rate
+                self.downloader.set_download_rate(rate)
+            self.rawserver.add_task(s)
+        except AttributeError:
+            pass
+
+    def startConnection(self, ip, port, id):
+        self.encoder._start_connection((ip, port), id)
+      
+    def _startConnection(self, ipandport, id):
+        self.encoder._start_connection(ipandport, id)
+        
+    def setInitiate(self, initiate):
+        try:
+            def s(self = self, initiate = initiate):
+                self.config['max_initiate'] = initiate
+            self.rawserver.add_task(s)
+        except AttributeError:
+            pass
+
+    def getConfig(self):
+        return self.config
+
+    def getDefaults(self):
+        return defaultargs(defaults)
+
+    def getUsageText(self):
+        return self.argslistheader
+
+    def reannounce(self, special = None):
+        try:
+            def r(self = self, special = special):
+                if special is None:
+                    self.rerequest.announce()
+                else:
+                    self.rerequest.announce(specialurl = special)
+            self.rawserver.add_task(r)
+        except AttributeError:
+            pass
+
+    def getResponse(self):
+        try:
+            return self.response
+        except:
+            return None
+
+    def Pause(self):
+        if not self.storagewrapper:
+            return False
+        self.unpauseflag.clear()
+        self.rawserver.add_task(self.onPause)
+        return True
+
+    def onPause(self):
+        self.whenpaused = clock()
+        if not self.downloader:
+            return
+        self.downloader.pause(True)
+        self.encoder.pause(True)
+        self.choker.pause(True)
+    
+    def Unpause(self):
+        self.unpauseflag.set()
+        self.rawserver.add_task(self.onUnpause)
+
+    def onUnpause(self):
+        if not self.downloader:
+            return
+        self.downloader.pause(False)
+        self.encoder.pause(False)
+        self.choker.pause(False)
+        if self.rerequest and self.whenpaused and clock()-self.whenpaused > 60:
+            self.rerequest.announce(3)      # rerequest automatically if paused for >60 seconds
+
+    def set_super_seed(self):
+        self.superseedflag.set()
+        self.rawserver.add_task(self._set_super_seed)
+
+    def _set_super_seed(self):
+        if not self.super_seeding_active and self.finflag.isSet():
+            self.super_seeding_active = True
+            self.errorfunc('        ** SUPER-SEED OPERATION ACTIVE **\n' +
+                           '  please set Max uploads so each peer gets 6-8 kB/s')
+            def s(self = self):
+                self.downloader.set_super_seed()
+                self.choker.set_super_seed()
+            self.rawserver.add_task(s)
+            if self.finflag.isSet():        # mode started when already finished
+                def r(self = self):
+                    self.rerequest.announce(3)  # so after kicking everyone off, reannounce
+                self.rawserver.add_task(r)
+
+    def am_I_finished(self):
+        return self.finflag.isSet()
+
+    def get_transfer_stats(self):
+        return self.upmeasure.get_total(), self.downmeasure.get_total()
+    
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/__init__.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/__init__.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/__init__.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/__init__.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,63 @@
+product_name = 'BitTornado'
+version_short = 'T-0.3.18'
+
+version = version_short+' ('+product_name+')'
+report_email = 'http://www.debian.org/Bugs/Reporting'
+
+from types import StringType
+from sha import sha
+from time import time, clock
+try:
+    from os import getpid
+except ImportError:
+    def getpid():
+        return 1
+
+mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
+
+_idprefix = version_short[0]
+for subver in version_short[2:].split('.'):
+    try:
+        subver = int(subver)
+    except:
+        subver = 0
+    _idprefix += mapbase64[subver]
+_idprefix += ('-' * (6-len(_idprefix)))
+_idrandom = [None]
+
+def resetPeerIDs():
+    try:
+        f = open('/dev/urandom','rb')
+        x = f.read(20)
+        f.close()
+    except:
+        x = ''
+
+    l1 = 0
+    t = clock()
+    while t == clock():
+        l1 += 1
+    l2 = 0
+    t = long(time()*100)
+    while t == long(time()*100):
+        l2 += 1
+    l3 = 0
+    if l2 < 1000:
+        t = long(time()*10)
+        while t == long(clock()*10):
+            l3 += 1
+    x += ( repr(time()) + '/' + str(time()) + '/'
+           + str(l1) + '/' + str(l2) + '/' + str(l3) + '/'
+           + str(getpid()) )
+
+    s = ''
+    for i in sha(x).digest()[-11:]:
+        s += mapbase64[ord(i) & 0x3F]
+    _idrandom[0] = s
+        
+resetPeerIDs()
+
+def createPeerID(ins = '---'):
+    assert type(ins) is StringType
+    assert len(ins) == 3
+    return _idprefix + ins + _idrandom[0]
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/parsedir.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/parsedir.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/BitTornado/parsedir.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/BitTornado/parsedir.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,151 @@
+# Written by John Hoffman and Uoti Urpala
+# see LICENSE.txt for license information
+from bencode import bencode, bdecode
+from BT1.btformats import check_info
+from os.path import exists, isfile
+from sha import sha
+import sys, os
+
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+NOISY = False
+
+def _errfunc(x):
+    print ":: "+x
+
+def parsedir(directory, parsed, files, blocked,
+             exts = ['.torrent'], return_metainfo = False, errfunc = _errfunc):
+    if NOISY:
+        errfunc('checking dir')
+    dirs_to_check = [directory]
+    new_files = {}
+    new_blocked = {}
+    torrent_type = {}
+    while dirs_to_check:    # first, recurse directories and gather torrents
+        directory = dirs_to_check.pop()
+        newtorrents = False
+        for f in os.listdir(directory):
+            newtorrent = None
+            for ext in exts:
+                if f.endswith(ext):
+                    newtorrent = ext[1:]
+                    break
+            if newtorrent:
+                newtorrents = True
+                p = os.path.join(directory, f)
+                new_files[p] = [(int(os.path.getmtime(p)),
+                                 os.path.getsize(p)), 0]
+                torrent_type[p] = newtorrent
+        if not newtorrents:
+            for f in os.listdir(directory):
+                p = os.path.join(directory, f)
+                if os.path.isdir(p):
+                    dirs_to_check.append(p)
+
+    new_parsed = {}
+    to_add = []
+    added = {}
+    removed = {}
+    # files[path] = [(modification_time, size), hash], hash is 0 if the file
+    # has not been successfully parsed
+    for p,v in new_files.items():   # re-add old items and check for changes
+        oldval = files.get(p)
+        if not oldval:          # new file
+            to_add.append(p)
+            continue
+        h = oldval[1]
+        if oldval[0] == v[0]:   # file is unchanged from last parse
+            if h:
+                if blocked.has_key(p):  # parseable + blocked means duplicate
+                    to_add.append(p)    # other duplicate may have gone away
+                else:
+                    new_parsed[h] = parsed[h]
+                new_files[p] = oldval
+            else:
+                new_blocked[p] = 1  # same broken unparseable file
+            continue
+        if parsed.has_key(h) and not blocked.has_key(p):
+            if NOISY:
+                errfunc('removing '+p+' (will re-add)')
+            removed[h] = parsed[h]
+        to_add.append(p)
+
+    to_add.sort()
+    for p in to_add:                # then, parse new and changed torrents
+        new_file = new_files[p]
+        v,h = new_file
+        if new_parsed.has_key(h): # duplicate
+            if not blocked.has_key(p) or files[p][0] != v:
+                errfunc('**warning** '+
+                    p +' is a duplicate torrent for '+new_parsed[h]['path'])
+            new_blocked[p] = 1
+            continue
+                
+        if NOISY:
+            errfunc('adding '+p)
+        try:
+            ff = open(p, 'rb')
+            d = bdecode(ff.read())
+            check_info(d['info'])
+            h = sha(bencode(d['info'])).digest()
+            new_file[1] = h
+            if new_parsed.has_key(h):
+                errfunc('**warning** '+
+                    p +' is a duplicate torrent for '+new_parsed[h]['path'])
+                new_blocked[p] = 1
+                continue
+
+            a = {}
+            a['path'] = p
+            f = os.path.basename(p)
+            a['file'] = f
+            a['type'] = torrent_type[p]
+            i = d['info']
+            l = 0
+            nf = 0
+            if i.has_key('length'):
+                l = i.get('length',0)
+                nf = 1
+            elif i.has_key('files'):
+                for li in i['files']:
+                    nf += 1
+                    if li.has_key('length'):
+                        l += li['length']
+            a['numfiles'] = nf
+            a['length'] = l
+            a['name'] = i.get('name', f)
+            def setkey(k, d = d, a = a):
+                if d.has_key(k):
+                    a[k] = d[k]
+            setkey('failure reason')
+            setkey('warning message')
+            setkey('announce-list')
+            if return_metainfo:
+                a['metainfo'] = d
+        except:
+            errfunc('**warning** '+p+' has errors')
+            new_blocked[p] = 1
+            continue
+        try:
+            ff.close()
+        except:
+            pass
+        if NOISY:
+            errfunc('... successful')
+        new_parsed[h] = a
+        added[h] = a
+
+    for p,v in files.items():       # and finally, mark removed torrents
+        if not new_files.has_key(p) and not blocked.has_key(p):
+            if NOISY:
+                errfunc('removing '+p)
+            removed[v[1]] = parsed[v[1]]
+
+    if NOISY:
+        errfunc('done checking')
+    return (new_parsed, new_files, new_blocked, added, removed)
+
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadcurses.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadcurses.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadcurses.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadcurses.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,412 @@
+#!/usr/bin/env python
+
+# Written by Henry 'Pi' James
+# see LICENSE.txt for license information
+
+SPEW_SCROLL_RATE = 1
+
+from BitTornado import PSYCO
+if PSYCO.psyco:
+    try:
+        import psyco
+        assert psyco.__version__ >= 0x010100f0
+        psyco.full()
+    except:
+        pass
+
+from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
+from BitTornado.RawServer import RawServer, UPnP_ERROR
+from random import seed
+from socket import error as socketerror
+from BitTornado.bencode import bencode
+from BitTornado.natpunch import UPnP_test
+from threading import Event
+from os.path import abspath
+from signal import signal, SIGWINCH
+from sha import sha
+from sys import argv, exit
+import sys
+from time import time, strftime
+from BitTornado.clock import clock
+from BitTornado import createPeerID, version
+from BitTornado.ConfigDir import ConfigDir
+
+try:
+    import curses
+    import curses.panel
+    from curses.wrapper import wrapper as curses_wrapper
+    from signal import signal, SIGWINCH 
+except:
+    print 'Textmode GUI initialization failed, cannot proceed.'
+    print
+    print 'This download interface requires the standard Python module ' \
+       '"curses", which is unfortunately not available for the native ' \
+       'Windows port of Python. It is however available for the Cygwin ' \
+       'port of Python, running on all Win32 systems (www.cygwin.com).'
+    print
+    print 'You may still use "btdownloadheadless.py" to download.'
+    sys.exit(1)
+
+assert sys.version >= '2', "Install Python 2.0 or greater"
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+def fmttime(n):
+    if n == 0:
+        return 'download complete!'
+    try:
+        n = int(n)
+        assert n >= 0 and n < 5184000  # 60 days
+    except:
+        return '<unknown>'
+    m, s = divmod(n, 60)
+    h, m = divmod(m, 60)
+    return 'finishing in %d:%02d:%02d' % (h, m, s)
+
+def fmtsize(n):
+    s = str(n)
+    size = s[-3:]
+    while len(s) > 3:
+        s = s[:-3]
+        size = '%s,%s' % (s[-3:], size)
+    if n > 999:
+        unit = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
+        i = 1
+        while i + 1 < len(unit) and (n >> 10) >= 999:
+            i += 1
+            n >>= 10
+        n = float(n) / (1 << 10)
+	size = '%s (%.2f %s)' % (size, n, unit[i])
+    return size
+
+
+class CursesDisplayer:
+    def __init__(self, scrwin, errlist, doneflag):
+        self.scrwin = scrwin
+        self.errlist = errlist
+        self.doneflag = doneflag
+        
+        signal(SIGWINCH, self.winch_handler)
+        self.changeflag = Event()
+
+        self.done = 0
+        self.file = ''
+        self.fileSize = ''
+        self.activity = ''
+        self.status = ''
+        self.progress = ''
+        self.downloadTo = ''
+        self.downRate = '---'
+        self.upRate = '---'
+        self.shareRating = ''
+        self.seedStatus = ''
+        self.peerStatus = ''
+        self.errors = []
+        self.last_update_time = 0
+        self.spew_scroll_time = 0
+        self.spew_scroll_pos = 0
+
+        self._remake_window()
+
+    def winch_handler(self, signum, stackframe):
+        self.changeflag.set()
+        curses.endwin()
+        self.scrwin.refresh()
+        self.scrwin = curses.newwin(0, 0, 0, 0)
+        self._remake_window()
+
+    def _remake_window(self):
+        self.scrh, self.scrw = self.scrwin.getmaxyx()
+        self.scrpan = curses.panel.new_panel(self.scrwin)
+        self.labelh, self.labelw, self.labely, self.labelx = 11, 9, 1, 2
+        self.labelwin = curses.newwin(self.labelh, self.labelw,
+                                      self.labely, self.labelx)
+        self.labelpan = curses.panel.new_panel(self.labelwin)
+        self.fieldh, self.fieldw, self.fieldy, self.fieldx = (
+                            self.labelh, self.scrw-2 - self.labelw-3,
+                            1, self.labelw+3)
+        self.fieldwin = curses.newwin(self.fieldh, self.fieldw,
+                                      self.fieldy, self.fieldx)
+        self.fieldwin.nodelay(1)
+        self.fieldpan = curses.panel.new_panel(self.fieldwin)
+        self.spewh, self.speww, self.spewy, self.spewx = (
+            self.scrh - self.labelh - 2, self.scrw - 3, 1 + self.labelh, 2)
+        self.spewwin = curses.newwin(self.spewh, self.speww,
+                                     self.spewy, self.spewx)
+        self.spewpan = curses.panel.new_panel(self.spewwin)
+        try:
+            self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
+        except:
+            pass
+        self.labelwin.addstr(0, 0, 'file:')
+        self.labelwin.addstr(1, 0, 'size:')
+        self.labelwin.addstr(2, 0, 'dest:')
+        self.labelwin.addstr(3, 0, 'progress:')
+        self.labelwin.addstr(4, 0, 'status:')
+        self.labelwin.addstr(5, 0, 'dl speed:')
+        self.labelwin.addstr(6, 0, 'ul speed:')
+        self.labelwin.addstr(7, 0, 'sharing:')
+        self.labelwin.addstr(8, 0, 'seeds:')
+        self.labelwin.addstr(9, 0, 'peers:')
+        curses.panel.update_panels()
+        curses.doupdate()
+        self.changeflag.clear()
+
+
+    def finished(self):
+        self.done = 1
+        self.activity = 'download succeeded!'
+        self.downRate = '---'
+        self.display(fractionDone = 1)
+
+    def failed(self, errormsg = None):
+        self.done = 1
+        self.activity = 'download failed!'
+        self.downRate = '---'
+        if errormsg:
+            newerrmsg = strftime('[%H:%M:%S] ') + errormsg
+            self.errors.append(newerrmsg)
+            self.errlist.append(newerrmsg)
+        self.display()
+
+    def error(self, errormsg):
+        newerrmsg = strftime('[%H:%M:%S] ') + errormsg
+        self.errors.append(newerrmsg)
+        self.errlist.append(newerrmsg)
+        self.display()
+
+    def display(self, dpflag = Event(), fractionDone = None, timeEst = None,
+            downRate = None, upRate = None, activity = None,
+            statistics = None, spew = None, **kws):
+
+        inchar = self.fieldwin.getch()
+        if inchar == 12: # ^L
+            self._remake_window()
+        elif inchar in (ord('q'),ord('Q')):
+            self.doneflag.set()
+
+        if activity is not None and not self.done:
+            self.activity = activity
+        elif timeEst is not None:
+            self.activity = fmttime(timeEst)
+        if self.changeflag.isSet():
+            return
+        if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
+            return
+        self.last_update_time = clock()
+        if fractionDone is not None:
+            blocknum = int(self.fieldw * fractionDone)
+            self.progress = blocknum * '#' + (self.fieldw - blocknum) * '_'
+            self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100)
+        else:
+            self.status = self.activity
+        if downRate is not None:
+            self.downRate = '%.1f KB/s' % (float(downRate) / (1 << 10))
+        if upRate is not None:
+            self.upRate = '%.1f KB/s' % (float(upRate) / (1 << 10))
+        if statistics is not None:
+           if (statistics.shareRating < 0) or (statistics.shareRating > 100):
+               self.shareRating = 'oo  (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
+           else:
+               self.shareRating = '%.3f  (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
+           if not self.done:
+              self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies2))
+           else:
+              self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
+           self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
+
+        self.fieldwin.erase()
+        self.fieldwin.addnstr(0, 0, self.file, self.fieldw, curses.A_BOLD)
+        self.fieldwin.addnstr(1, 0, self.fileSize, self.fieldw)
+        self.fieldwin.addnstr(2, 0, self.downloadTo, self.fieldw)
+        if self.progress:
+            self.fieldwin.addnstr(3, 0, self.progress, self.fieldw, curses.A_BOLD)
+        self.fieldwin.addnstr(4, 0, self.status, self.fieldw)
+        self.fieldwin.addnstr(5, 0, self.downRate, self.fieldw)
+        self.fieldwin.addnstr(6, 0, self.upRate, self.fieldw)
+        self.fieldwin.addnstr(7, 0, self.shareRating, self.fieldw)
+        self.fieldwin.addnstr(8, 0, self.seedStatus, self.fieldw)
+        self.fieldwin.addnstr(9, 0, self.peerStatus, self.fieldw)
+
+        self.spewwin.erase()
+
+        if not spew:
+            errsize = self.spewh
+            if self.errors:
+                self.spewwin.addnstr(0, 0, "error(s):", self.speww, curses.A_BOLD)
+                errsize = len(self.errors)
+                displaysize = min(errsize, self.spewh)
+                displaytop = errsize - displaysize
+                for i in range(displaysize):
+                    self.spewwin.addnstr(i, self.labelw, self.errors[displaytop + i],
+                                 self.speww-self.labelw-1, curses.A_BOLD)
+        else:
+            if self.errors:
+                self.spewwin.addnstr(0, 0, "error:", self.speww, curses.A_BOLD)
+                self.spewwin.addnstr(0, self.labelw, self.errors[-1],
+                                 self.speww-self.labelw-1, curses.A_BOLD)
+            self.spewwin.addnstr(2, 0, "  #     IP                 Upload           Download     Completed  Speed", self.speww, curses.A_BOLD)
+
+
+            if self.spew_scroll_time + SPEW_SCROLL_RATE < clock():
+                self.spew_scroll_time = clock()
+                if len(spew) > self.spewh-5 or self.spew_scroll_pos > 0:
+                    self.spew_scroll_pos += 1
+            if self.spew_scroll_pos > len(spew):
+                self.spew_scroll_pos = 0
+
+            for i in range(len(spew)):
+                spew[i]['lineno'] = i+1
+            spew.append({'lineno': None})
+            spew = spew[self.spew_scroll_pos:] + spew[:self.spew_scroll_pos]                
+            
+            for i in range(min(self.spewh - 5, len(spew))):
+                if not spew[i]['lineno']:
+                    continue
+                self.spewwin.addnstr(i+3, 0, '%3d' % spew[i]['lineno'], 3)
+                self.spewwin.addnstr(i+3, 4, spew[i]['ip']+spew[i]['direction'], 16)
+                if spew[i]['uprate'] > 100:
+                    self.spewwin.addnstr(i+3, 20, '%6.0f KB/s' % (float(spew[i]['uprate']) / 1000), 11)
+                self.spewwin.addnstr(i+3, 32, '-----', 5)
+                if spew[i]['uinterested'] == 1:
+                    self.spewwin.addnstr(i+3, 33, 'I', 1)
+                if spew[i]['uchoked'] == 1:
+                    self.spewwin.addnstr(i+3, 35, 'C', 1)
+                if spew[i]['downrate'] > 100:
+                    self.spewwin.addnstr(i+3, 38, '%6.0f KB/s' % (float(spew[i]['downrate']) / 1000), 11)
+                self.spewwin.addnstr(i+3, 50, '-------', 7)
+                if spew[i]['dinterested'] == 1:
+                    self.spewwin.addnstr(i+3, 51, 'I', 1)
+                if spew[i]['dchoked'] == 1:
+                    self.spewwin.addnstr(i+3, 53, 'C', 1)
+                if spew[i]['snubbed'] == 1:
+                    self.spewwin.addnstr(i+3, 55, 'S', 1)
+                self.spewwin.addnstr(i+3, 58, '%5.1f%%' % (float(int(spew[i]['completed']*1000))/10), 6)
+                if spew[i]['speed'] is not None:
+                    self.spewwin.addnstr(i+3, 64, '%5.0f KB/s' % (float(spew[i]['speed'])/1000), 10)
+
+            if statistics is not None:
+                self.spewwin.addnstr(self.spewh-1, 0,
+                        'downloading %d pieces, have %d fragments, %d of %d pieces completed'
+                        % ( statistics.storage_active, statistics.storage_dirty,
+                            statistics.storage_numcomplete,
+                            statistics.storage_totalpieces ), self.speww-1 )
+
+        curses.panel.update_panels()
+        curses.doupdate()
+        dpflag.set()
+
+    def chooseFile(self, default, size, saveas, dir):
+        self.file = default
+        self.fileSize = fmtsize(size)
+        if saveas == '':
+            saveas = default
+        self.downloadTo = abspath(saveas)
+        return saveas
+
+def run(scrwin, errlist, params):
+    doneflag = Event()
+    d = CursesDisplayer(scrwin, errlist, doneflag)
+    try:
+        while 1:
+            configdir = ConfigDir('downloadcurses')
+            defaultsToIgnore = ['responsefile', 'url', 'priority']
+            configdir.setDefaults(defaults,defaultsToIgnore)
+            configdefaults = configdir.loadConfig()
+            defaults.append(('save_options',0,
+             "whether to save the current options as the new default configuration " +
+             "(only for btdownloadcurses.py)"))
+            try:
+                config = parse_params(params, configdefaults)
+            except ValueError, e:
+                d.error('error: ' + str(e) + '\nrun with no args for parameter explanations')
+                break
+            if not config:
+                d.error(get_usage(defaults, d.fieldw, configdefaults))
+                break
+            if config['save_options']:
+                configdir.saveConfig(config)
+            configdir.deleteOldCacheData(config['expire_cache_data'])
+
+            myid = createPeerID()
+            seed(myid)
+
+            rawserver = RawServer(doneflag, config['timeout_check_interval'],
+                                  config['timeout'], ipv6_enable = config['ipv6_enabled'],
+                                  failfunc = d.failed, errorfunc = d.error)
+
+            upnp_type = 0
+            while True:
+                try:
+                    listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
+                                    config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
+                                    upnp = upnp_type, randomizer = config['random_port'])
+                    break
+                except socketerror, e:
+                    if upnp_type and e == UPnP_ERROR:
+                        d.error('WARNING: COULD NOT FORWARD VIA UPnP')
+                        upnp_type = 0
+                        continue
+                    d.error("Couldn't listen - " + str(e))
+                    d.failed()
+                    return
+
+            response = get_response(config['responsefile'], config['url'], d.error)
+            if not response:
+                break
+
+            infohash = sha(bencode(response['info'])).digest()
+            
+            dow = BT1Download(d.display, d.finished, d.error, d.error, doneflag,
+                            config, response, infohash, myid, rawserver, listen_port,
+                            configdir)
+            
+            if not dow.saveAs(d.chooseFile):
+                break
+
+            if not dow.initFiles(old_style = True):
+                break
+            if not dow.startEngine():
+                dow.shutdown()
+                break
+            dow.startRerequester()
+            dow.autoStats()
+
+            if not dow.am_I_finished():
+                d.display(activity = 'connecting to peers')
+            rawserver.listen_forever(dow.getPortHandler())
+            d.display(activity = 'shutting down')
+            dow.shutdown()
+            break
+
+    except KeyboardInterrupt:
+        # ^C to exit.. 
+        pass 
+    try:
+        rawserver.shutdown()
+    except:
+        pass
+    if not d.done:
+        d.failed()
+
+
+if __name__ == '__main__':
+    if argv[1:] == ['--version']:
+        print version
+        exit(0)
+    if len(argv) <= 1:
+        print "Usage: btdownloadcurses.py <global options>\n"
+        print get_usage(defaults)
+        exit(1)
+
+    errlist = []
+    curses_wrapper(run, errlist, argv[1:])
+
+    if errlist:
+        print "These errors occurred during execution:"
+        for error in errlist:
+            print error
+            
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadgui.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadgui.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadgui.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadgui.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,2368 @@
+#!/usr/bin/env python
+
+# Written by Bram Cohen and Myers Carpenter
+# Modifications by various people
+# see LICENSE.txt for license information
+
+from BitTornado import PSYCO
+if PSYCO.psyco:
+    try:
+        import psyco
+        assert psyco.__version__ >= 0x010100f0
+        psyco.full()
+    except:
+        pass
+
+from sys import argv, version, exit
+assert version >= '2', "Install Python 2.0 or greater"
+
+try:
+    import wx
+except:
+    print 'wxPython is either not installed or has not been installed properly.'
+    exit(1)
+from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
+from BitTornado.RawServer import RawServer, UPnP_ERROR
+from random import seed
+from socket import error as socketerror
+from BitTornado.ConnChoice import *
+from BitTornado.ConfigReader import configReader
+from BitTornado.bencode import bencode, bdecode
+from BitTornado.natpunch import UPnP_test
+from threading import Event, Thread
+from os.path import *
+from os import getcwd
+from time import strftime, time, localtime, sleep
+from BitTornado.clock import clock
+from webbrowser import open_new
+from traceback import print_exc
+from StringIO import StringIO
+from sha import sha
+import re
+import sys, os
+from BitTornado import version, createPeerID, report_email
+
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+PROFILER = False
+WXPROFILER = False
+
+# Note to packagers: edit OLDICONPATH in BitTornado/ConfigDir.py
+
+def hours(n):
+    if n == 0:
+        return 'download complete'
+    try:
+        n = int(n)
+        assert n >= 0 and n < 5184000  # 60 days
+    except:
+        return '<unknown>'
+    m, s = divmod(n, 60)
+    h, m = divmod(m, 60)
+    if h > 0:
+        return '%d hour(s) %02d min %02d sec' % (h, m, s)
+    else:
+        return '%d min %02d sec' % (m, s)
+
+def size_format(s):
+    if (s < 1024):
+        r = str(s) + 'B'
+    elif (s < 1048576):
+        r = str(int(s/1024)) + 'KiB'
+    elif (s < 1073741824L):
+        r = str(int(s/1048576)) + 'MiB'
+    elif (s < 1099511627776L):
+        r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB'
+    else:
+        r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB'
+    return(r)
+
+def comma_format(s):
+    r = str(s)
+    for i in range(len(r)-3, 0, -3):
+        r = r[:i]+','+r[i:]
+    return(r)
+
+hexchars = '0123456789abcdef'
+hexmap = []
+for i in xrange(256):
+    x = hexchars[(i&0xF0)/16]+hexchars[i&0x0F]
+    hexmap.append(x)
+
+def tohex(s):
+    r = []
+    for c in s:
+        r.append(hexmap[ord(c)])
+    return ''.join(r)
+
+wxEVT_INVOKE = wx.NewEventType()
+
+def EVT_INVOKE(win, func):
+    win.Connect(-1, -1, wxEVT_INVOKE, func)
+
+class InvokeEvent(wx.PyEvent):
+    def __init__(self, func = None, args = None, kwargs = None):
+        wx.PyEvent.__init__(self)
+        self.SetEventType(wxEVT_INVOKE)
+        self.func = func
+        self.args = args
+        self.kwargs = kwargs
+
+
+
+class DownloadInfoFrame:
+    def __init__(self, flag, configfile):
+        self._errorwindow = None
+        try:
+            self.FONT = configfile.config['gui_font']
+            self.default_font = wx.Font(self.FONT, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False)
+            frame = wx.Frame(None, -1, 'BitTorrent ' + version + ' download',
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            self.flag = flag
+            self.configfile = configfile
+            self.configfileargs = configfile.config
+            self.uiflag = Event()
+            self.fin = False
+            self.aboutBox = None
+            self.detailBox = None
+            self.advBox = None
+            self.creditsBox = None
+            self.statusIconHelpBox = None
+            self.reannouncelast = 0
+            self.spinlock = 0
+            self.scrollock = 0
+            self.lastError = 0
+            self.spewwait = clock()
+            self.config = None
+            self.updateSpinnerFlag = 0
+            self.updateSliderFlag = 0
+            self.statusIconValue = ' '
+            self.iconized = 0
+            self.taskbaricon = False
+            self.checking = None
+            self.activity = 'Starting up...'
+            self.firstupdate = True
+            self.shuttingdown = False
+            self.ispaused = False
+            self.bgalloc_periods = 0
+            self.gui_fractiondone = None
+            self.fileList = None
+            self.lastexternalannounce = ''
+            self.refresh_details = False
+            self.lastuploadsettings = 0
+            self.old_download = 0
+            self.old_upload = 0
+            self.old_ratesettings = None
+            self.current_ratesetting = None
+            self.gaugemode = None
+            self.autorate = False
+            
+            self.filename = None
+            self.dow = None
+            if sys.platform == 'win32':
+                self.invokeLaterEvent = InvokeEvent()
+                self.invokeLaterList = []
+
+            wx.InitAllImageHandlers()
+            self.basepath = self.configfile.getIconDir()
+            self.icon = wx.Icon(os.path.join(self.basepath,'icon_bt.ico'), wx.BITMAP_TYPE_ICO)
+            self.finicon = wx.Icon(os.path.join(self.basepath,'icon_done.ico'), wx.BITMAP_TYPE_ICO)
+            self.statusIconFiles={
+                'startup':os.path.join(self.basepath,'white.ico'),
+                'disconnected':os.path.join(self.basepath,'black.ico'),
+                'noconnections':os.path.join(self.basepath,'red.ico'),
+                'nocompletes':os.path.join(self.basepath,'blue.ico'),
+                'noincoming':os.path.join(self.basepath,'yellow.ico'),
+                'allgood':os.path.join(self.basepath,'green.ico'),
+                }
+            self.statusIcons={}
+            self.filestatusIcons = wx.ImageList(16, 16)
+            self.filestatusIcons.Add(wx.Bitmap(os.path.join(self.basepath,'black1.ico'),wx.BITMAP_TYPE_ICO))
+            self.filestatusIcons.Add(wx.Bitmap(os.path.join(self.basepath,'yellow1.ico'), wx.BITMAP_TYPE_ICO))
+            self.filestatusIcons.Add(wx.Bitmap(os.path.join(self.basepath,'green1.ico'), wx.BITMAP_TYPE_ICO))
+
+            self.allocbuttonBitmap = wx.Bitmap(os.path.join(self.basepath,'alloc.gif'), wx.BITMAP_TYPE_GIF)
+
+            self.starttime = clock()
+
+            self.frame = frame
+            try:
+                self.frame.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(frame, -1)
+            self.bgcolor = panel.GetBackgroundColour()
+
+            def StaticText(text, font = self.FONT-1, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_LEFT)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            colSizer = wx.FlexGridSizer(cols = 1, vgap = 3)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(colSizer, 1, wx.EXPAND | wx.ALL, 4)
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            topboxsizer = wx.FlexGridSizer(cols = 3, vgap = 0)
+            topboxsizer.AddGrowableCol (0)
+
+            fnsizer = wx.FlexGridSizer(cols = 1, vgap = 0)
+            fnsizer.AddGrowableCol (0)
+            fnsizer.AddGrowableRow (1)
+
+            fileNameText = StaticText('', self.FONT+4)
+            fnsizer.Add(fileNameText, 1, wx.ALIGN_BOTTOM|wx.EXPAND)
+            self.fileNameText = fileNameText
+
+            fnsizer2 = wx.FlexGridSizer(cols = 8, vgap = 0)
+            fnsizer2.AddGrowableCol (0)
+
+            fileSizeText = StaticText('')
+            fnsizer2.Add(fileSizeText, 1, wx.ALIGN_BOTTOM|wx.EXPAND)
+            self.fileSizeText = fileSizeText
+
+            fileDetails = StaticText('Details', self.FONT, True, 'Blue')
+            fnsizer2.Add(fileDetails, 0, wx.ALIGN_BOTTOM)
+
+            fnsizer2.Add(StaticText('  '))
+
+            advText = StaticText('Advanced', self.FONT, True, 'Blue')
+            fnsizer2.Add(advText, 0, wx.ALIGN_BOTTOM)
+            fnsizer2.Add(StaticText('  '))
+
+            prefsText = StaticText('Prefs', self.FONT, True, 'Blue')
+            fnsizer2.Add(prefsText, 0, wx.ALIGN_BOTTOM)
+            fnsizer2.Add(StaticText('  '))
+
+            aboutText = StaticText('About', self.FONT, True, 'Blue')
+            fnsizer2.Add(aboutText, 0, wx.ALIGN_BOTTOM)
+
+            fnsizer2.Add(StaticText('  '))
+            fnsizer.Add(fnsizer2,0,wx.EXPAND)
+            topboxsizer.Add(fnsizer,0,wx.EXPAND)
+            topboxsizer.Add(StaticText('  '))
+
+            self.statusIcon = wx.EmptyBitmap(32,32)
+            statidata = wx.MemoryDC()
+            statidata.SelectObject(self.statusIcon)
+            statidata.SetPen(wx.TRANSPARENT_PEN)
+            statidata.SetBrush(wx.Brush(self.bgcolor,wx.SOLID))
+            statidata.DrawRectangle(0,0,32,32)
+            self.statusIconPtr = wx.StaticBitmap(panel, -1, self.statusIcon)
+            topboxsizer.Add(self.statusIconPtr)
+
+            self.fnsizer = fnsizer
+            self.fnsizer2 = fnsizer2
+            self.topboxsizer = topboxsizer
+            colSizer.Add(topboxsizer, 0, wx.EXPAND)
+
+            self.gauge = wx.Gauge(panel, -1, range = 1000, style = wx.GA_SMOOTH)
+            colSizer.Add(self.gauge, 0, wx.EXPAND)
+
+            timeSizer = wx.FlexGridSizer(cols = 2)
+            timeSizer.Add(StaticText('Time elapsed / estimated : '))
+            self.timeText = StaticText(self.activity+'                    ')
+            timeSizer.Add(self.timeText)
+            timeSizer.AddGrowableCol(1)
+            colSizer.Add(timeSizer)
+
+            destSizer = wx.FlexGridSizer(cols = 2, hgap = 8)
+            self.fileDestLabel = StaticText('Download to:')
+            destSizer.Add(self.fileDestLabel)
+            self.fileDestText = StaticText('')
+            destSizer.Add(self.fileDestText, flag = wx.EXPAND)
+            destSizer.AddGrowableCol(1)
+            colSizer.Add(destSizer, flag = wx.EXPAND)
+            self.destSizer = destSizer
+
+            statSizer = wx.FlexGridSizer(cols = 3, hgap = 8)
+
+            self.ratesSizer = wx.FlexGridSizer(cols = 2)
+            self.infoSizer = wx.FlexGridSizer(cols = 2)
+
+            self.ratesSizer.Add(StaticText('   Download rate: '))
+            self.downRateText = StaticText('0 kB/s       ')
+            self.ratesSizer.Add(self.downRateText, flag = wx.EXPAND)
+
+            self.downTextLabel = StaticText('Downloaded: ')
+            self.infoSizer.Add(self.downTextLabel)
+            self.downText = StaticText('0.00 MiB        ')
+            self.infoSizer.Add(self.downText, flag = wx.EXPAND)
+
+            self.ratesSizer.Add(StaticText('   Upload rate: '))
+            self.upRateText = StaticText('0 kB/s       ')
+            self.ratesSizer.Add(self.upRateText, flag = wx.EXPAND)
+
+            self.upTextLabel = StaticText('Uploaded: ')
+            self.infoSizer.Add(self.upTextLabel)
+            self.upText = StaticText('0.00 MiB        ')
+            self.infoSizer.Add(self.upText, flag = wx.EXPAND)
+
+            shareSizer = wx.FlexGridSizer(cols = 2, hgap = 8)
+            shareSizer.Add(StaticText('Share rating:'))
+            self.shareRatingText = StaticText('')
+            shareSizer.AddGrowableCol(1)
+            shareSizer.Add(self.shareRatingText, flag = wx.EXPAND)
+
+            statSizer.Add(self.ratesSizer)
+            statSizer.Add(self.infoSizer)
+            statSizer.Add(shareSizer, flag = wx.ALIGN_CENTER_VERTICAL)
+            colSizer.Add (statSizer)
+
+            torrentSizer = wx.FlexGridSizer(cols = 1)
+            self.peerStatusText = StaticText('')
+            torrentSizer.Add(self.peerStatusText, 0, wx.EXPAND)
+            self.seedStatusText = StaticText('')
+            torrentSizer.Add(self.seedStatusText, 0, wx.EXPAND)
+            torrentSizer.AddGrowableCol(0)
+            colSizer.Add(torrentSizer, 0, wx.EXPAND)
+            self.torrentSizer = torrentSizer
+
+            self.errorTextSizer = wx.FlexGridSizer(cols = 1)
+            self.errorText = StaticText('', self.FONT, False, 'Red')
+            self.errorTextSizer.Add(self.errorText, 0, wx.EXPAND)
+            colSizer.Add(self.errorTextSizer, 0, wx.EXPAND)
+
+            cancelSizer=wx.GridSizer(cols = 2, hgap = 40)
+            self.pauseButton = wx.Button(panel, -1, 'Pause')
+            cancelSizer.Add(self.pauseButton, 0, wx.ALIGN_CENTER)
+
+            self.cancelButton = wx.Button(panel, -1, 'Cancel')
+            cancelSizer.Add(self.cancelButton, 0, wx.ALIGN_CENTER)
+            colSizer.Add(cancelSizer, 0, wx.ALIGN_CENTER)
+
+            # Setting options
+
+            slideSizer = wx.FlexGridSizer(cols = 7, hgap = 0, vgap = 5)
+
+            # dropdown
+
+            self.connChoiceLabel = StaticText('Settings for ')
+            slideSizer.Add (self.connChoiceLabel, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
+            self.connChoice = wx.Choice (panel, -1, (-1, -1), (self.FONT*12, -1),
+                                        choices = connChoiceList)
+            self.connChoice.SetFont(self.default_font)
+            self.connChoice.SetSelection(0)
+            slideSizer.Add (self.connChoice, 0, wx.ALIGN_CENTER)
+            self.rateSpinnerLabel = StaticText(' Upload rate (kB/s) ')
+            slideSizer.Add (self.rateSpinnerLabel, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
+
+            # max upload rate
+
+            self.rateSpinner = wx.SpinCtrl (panel, -1, "", (-1,-1), (50, -1))
+            self.rateSpinner.SetFont(self.default_font)
+            self.rateSpinner.SetRange(0,5000)
+            self.rateSpinner.SetValue(0)
+            slideSizer.Add (self.rateSpinner, 0, wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
+
+            self.rateLowerText = StaticText('  %5d' % (0))
+            self.rateUpperText = StaticText('%5d' % (5000))
+            self.rateslider = wx.Slider(panel, -1, 0, 0, 5000, (-1, -1), (80, -1))
+
+            slideSizer.Add(self.rateLowerText, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
+            slideSizer.Add(self.rateslider,    0, wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
+            slideSizer.Add(self.rateUpperText, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
+
+            slideSizer.Add(StaticText(''), 0, wx.ALIGN_LEFT)
+
+            self.bgallocText = StaticText('', self.FONT+2, False, 'Red')
+            slideSizer.Add(self.bgallocText, 0, wx.ALIGN_LEFT)
+
+            # max uploads
+
+            self.connSpinnerLabel = StaticText(' Max uploads ')
+            slideSizer.Add (self.connSpinnerLabel, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
+            self.connSpinner = wx.SpinCtrl (panel, -1, "", (-1,-1), (50, -1))
+            self.connSpinner.SetFont(self.default_font)
+            self.connSpinner.SetRange(4,100)
+            self.connSpinner.SetValue(4)
+            slideSizer.Add (self.connSpinner, 0, wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
+
+            self.connLowerText = StaticText('  %5d' % (4))
+            self.connUpperText = StaticText('%5d' % (100))
+            self.connslider = wx.Slider(panel, -1, 4, 4, 100, (-1, -1), (80, -1))
+
+            slideSizer.Add(self.connLowerText, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
+            slideSizer.Add(self.connslider,    0, wx.ALIGN_CENTER|wx.ALIGN_CENTER_VERTICAL)
+            slideSizer.Add(self.connUpperText, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
+
+            colSizer.Add(slideSizer, 1, wx.ALL|wx.ALIGN_CENTER|wx.EXPAND, 0)
+
+            self.unlimitedLabel = StaticText('0 kB/s means unlimited. Tip: your download rate is proportional to your upload rate', self.FONT-2)
+            colSizer.Add(self.unlimitedLabel, 0, wx.ALIGN_CENTER)
+
+            self.priorityIDs = [wx.NewId(),wx.NewId(),wx.NewId(),wx.NewId()]
+            self.prioritycolors = [ wx.Colour(160,160,160),
+                                    wx.Colour(255,64,0),
+                                    wx.Colour(0,0,0),
+                                    wx.Colour(64,64,255) ]
+
+
+            wx.EVT_LEFT_DOWN(aboutText, self.about)
+            wx.EVT_LEFT_DOWN(fileDetails, self.details)
+            wx.EVT_LEFT_DOWN(self.statusIconPtr,self.statusIconHelp)
+            wx.EVT_LEFT_DOWN(advText, self.advanced)
+            wx.EVT_LEFT_DOWN(prefsText, self.openConfigMenu)
+            wx.EVT_CLOSE(frame, self.done)
+            wx.EVT_BUTTON(frame, self.pauseButton.GetId(), self.pause)
+            wx.EVT_BUTTON(frame, self.cancelButton.GetId(), self.done)
+            EVT_INVOKE(frame, self.onInvoke)
+            wx.EVT_SCROLL(self.rateslider, self.onRateScroll)
+            wx.EVT_SCROLL(self.connslider, self.onConnScroll)
+            wx.EVT_CHOICE(self.connChoice, -1, self.onConnChoice)
+            wx.EVT_SPINCTRL(self.connSpinner, -1, self.onConnSpinner)
+            wx.EVT_SPINCTRL(self.rateSpinner, -1, self.onRateSpinner)
+            if (sys.platform == 'win32'):
+                self.frame.tbicon = wx.TaskBarIcon()
+                wx.EVT_ICONIZE(self.frame, self.onIconify)
+                wx.EVT_TASKBAR_LEFT_DCLICK(self.frame.tbicon, self.onTaskBarActivate)
+                wx.EVT_TASKBAR_RIGHT_UP(self.frame.tbicon, self.onTaskBarMenu)
+                wx.EVT_MENU(self.frame.tbicon, self.TBMENU_RESTORE, self.onTaskBarActivate)
+                wx.EVT_MENU(self.frame.tbicon, self.TBMENU_CLOSE, self.done)
+            colSizer.AddGrowableCol (0)
+            colSizer.AddGrowableRow (6)
+            self.frame.Show()
+            border.Fit(panel)
+            self.frame.Fit()
+            self.panel = panel
+            self.border = border
+            self.addwidth = aboutText.GetBestSize().GetWidth() + fileDetails.GetBestSize().GetWidth() + (self.FONT*16)
+            self.fnsizer = fnsizer
+            self.colSizer = colSizer
+            minsize = self.colSizer.GetSize()
+            minsize.SetWidth (minsize.GetWidth())
+            minsize.SetHeight (minsize.GetHeight())
+            self.colSizer.SetMinSize (minsize)
+            self.colSizer.Fit(self.frame)
+            colSizer.Fit(frame)
+        except:
+            self.exception()
+
+    if sys.platform == 'win32':     # windows-only optimization
+        def onInvoke(self, event):
+            while self.invokeLaterList:
+                func,args,kwargs = self.invokeLaterList[0]
+                if self.uiflag.isSet():
+                    return
+                try:
+                    apply(func,args,kwargs)
+                except:
+                    self.exception()
+                del self.invokeLaterList[0]
+
+        def invokeLater(self, func, args = [], kwargs = {}):
+            if not self.uiflag.isSet():
+                self.invokeLaterList.append((func,args,kwargs))
+                if len(self.invokeLaterList) == 1:
+                    wx.PostEvent(self.frame, self.invokeLaterEvent)
+    else:
+        def onInvoke(self, event):
+            if not self.uiflag.isSet():
+                try:
+                    apply(event.func, event.args, event.kwargs)
+                except:
+                    self.exception()
+
+        def invokeLater(self, func, args = [], kwargs = {}):
+            if not self.uiflag.isSet():
+                wx.PostEvent(self.frame, InvokeEvent(func, args, kwargs))
+
+
+    def getStatusIcon(self, name, bitmap=False):
+        if self.statusIcons.has_key(name):
+            i = self.statusIcons[name]
+            if type(i)  == type(self.icon) and not bitmap:
+                return i
+        if bitmap:
+            i = wx.Bitmap(self.statusIconFiles[name], wx.BITMAP_TYPE_ICO)
+        else:
+            i = wx.Icon(self.statusIconFiles[name], wx.BITMAP_TYPE_ICO)
+        self.statusIcons[name] = i
+        return i
+
+
+    def setStatusIcon(self, name):
+        if name == self.statusIconValue:
+            return
+        self.statusIconValue = name
+        statidata = wx.MemoryDC()
+        statidata.SelectObject(self.statusIcon)
+        statidata.BeginDrawing()
+        try:
+            statidata.DrawIcon(self.getStatusIcon(name),0,0)
+        except:
+            statidata.DrawBitmap(self.getStatusIcon(name,True),0,0,True)
+        statidata.EndDrawing()
+        statidata.SelectObject(wx.NullBitmap)
+        self.statusIconPtr.Refresh()
+
+
+    def createStatusIcon(self, name):
+        iconbuffer = wx.EmptyBitmap(32,32)
+        bbdata = wx.MemoryDC()
+        bbdata.SelectObject(iconbuffer)
+        bbdata.SetPen(wx.TRANSPARENT_PEN)
+        bbdata.SetBrush(wx.Brush(self.bgcolor,wx.SOLID))
+        bbdata.DrawRectangle(0,0,32,32)
+        try:
+            bbdata.DrawIcon(self.getStatusIcon(name),0,0)
+        except:
+            bbdata.DrawBitmap(self.getStatusIcon(name,True),0,0,True)
+        return iconbuffer
+
+
+    def setgaugemode(self, selection):
+        if selection is None:
+            selection = self.gaugemode
+        elif selection == self.gaugemode:
+            return
+        else:
+            self.gaugemode = selection
+        if selection < 0:
+            self.gauge.SetForegroundColour(self.configfile.getcheckingcolor())
+            self.gauge.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENU))
+        elif selection == 0:
+            self.gauge.SetForegroundColour(self.configfile.getdownloadcolor())
+            self.gauge.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENU))
+        else:
+            self.gauge.SetForegroundColour(self.configfile.getseedingcolor())
+            self.gauge.SetBackgroundColour(self.configfile.getdownloadcolor())
+
+
+    def onIconify(self, evt):
+        try:
+            if self.configfileargs['win32_taskbar_icon']:
+                if self.fin:
+                    self.frame.tbicon.SetIcon(self.finicon, "BitTorrent")
+                else:
+                    self.frame.tbicon.SetIcon(self.icon, "BitTorrent")
+                self.frame.Hide()
+                self.taskbaricon = True
+            else:
+                return
+        except:
+            self.exception()
+
+
+    def onTaskBarActivate(self, evt):
+        try:
+            if self.frame.IsIconized():
+                self.frame.Iconize(False)
+            if not self.frame.IsShown():
+                self.frame.Show(True)
+                self.frame.Raise()
+            self.frame.tbicon.RemoveIcon()
+            self.taskbaricon = False
+        except wx.PyDeadObjectError:
+            pass
+        except:
+            self.exception()
+
+    TBMENU_RESTORE = 1000
+    TBMENU_CLOSE   = 1001
+
+    def onTaskBarMenu(self, evt):
+        menu = wx.Menu()
+        menu.Append(self.TBMENU_RESTORE, "Restore BitTorrent")
+        menu.Append(self.TBMENU_CLOSE,   "Close")
+        self.frame.tbicon.PopupMenu(menu)
+        menu.Destroy()
+
+
+    def _try_get_config(self):
+        if self.config is None:
+            try:
+                self.config = self.dow.getConfig()
+            except:
+                pass
+        return self.config != None
+
+    def onRateScroll(self, event):
+        try:
+            if self.autorate:
+                return
+            if not self._try_get_config():
+                return
+            if (self.scrollock == 0):
+                self.scrollock = 1
+                self.updateSpinnerFlag = 1
+                self.dow.setUploadRate(self.rateslider.GetValue()
+                            * connChoices[self.connChoice.GetSelection()]['rate'].get('div',1))
+                self.scrollock = 0
+        except:
+            self.exception()
+
+    def onConnScroll(self, event):
+        try:
+            if self.autorate:
+                return
+            if not self._try_get_config():
+                return
+            self.connSpinner.SetValue (self.connslider.GetValue ())
+            self.dow.setConns(self.connslider.GetValue())
+        except:
+            self.exception()
+
+    def onRateSpinner(self, event = None):
+        try:
+            if self.autorate:
+                return
+            if not self._try_get_config():
+                return
+            if (self.spinlock == 0):
+                self.spinlock = 1
+                spinnerValue = self.rateSpinner.GetValue()
+                div = connChoices[self.connChoice.GetSelection()]['rate'].get('div',1)
+                if div > 1:
+                    if spinnerValue > (self.config['max_upload_rate']):
+                        round_up = div - 1
+                    else:
+                        round_up = 0
+                    newValue = int((spinnerValue + round_up) / div) * div
+                    if newValue != spinnerValue:
+                        self.rateSpinner.SetValue(newValue)
+                else:
+                    newValue = spinnerValue
+                self.dow.setUploadRate(newValue)
+                self.updateSliderFlag = 1
+                self.spinlock = 0
+        except:
+            self.exception()
+
+    def onDownRateSpinner(self, event=None):
+        try:
+            if not self._try_get_config():
+                return
+            spinnerValue = self.downrateSpinner.GetValue()
+            self.dow.setDownloadRate(self.downrateSpinner.GetValue())
+        except:
+            self.exception()
+
+    def onConnSpinner(self, event = None):
+        try:
+            if self.autorate:
+                return
+            if not self._try_get_config():
+                return
+            self.connslider.SetValue (self.connSpinner.GetValue())
+            self.dow.setConns(self.connslider.GetValue())
+        except:
+            self.exception()
+
+    def onConnChoice(self, event, cons=None, rate=None):
+        try:
+            if not self._try_get_config():
+                return
+            num = self.connChoice.GetSelection()
+            choice = connChoices[num]
+            if choice.has_key('super-seed'):  # selecting super-seed is now a toggle
+                self.dow.set_super_seed()     # one way change, don't go back
+                self.connChoice.SetSelection(self.lastuploadsettings)
+                return
+            self.lastuploadsettings = num
+            self.current_ratesetting = self.connChoice.GetStringSelection()
+            if rate is None:
+                rate = choice['rate']['def']
+            self.rateSpinner.SetRange (choice['rate']['min'],
+                                   choice['rate']['max'])
+            self.rateSpinner.SetValue(rate)
+            self.rateslider.SetRange(
+                choice['rate']['min']/choice['rate'].get('div',1),
+                choice['rate']['max']/choice['rate'].get('div',1))
+            self.rateslider.SetValue (rate/choice['rate'].get('div',1))
+            self.rateLowerText.SetLabel ('  %d' % (choice['rate']['min']))
+            self.rateUpperText.SetLabel ('%d' % (choice['rate']['max']))
+            if cons is None:
+                cons = choice['conn']['def']
+            self.connSpinner.SetRange (choice['conn']['min'],
+                                       choice['conn']['max'])
+            self.connSpinner.SetValue (cons)
+            self.connslider.SetRange (choice['conn']['min'],
+                                      choice['conn']['max'])
+            self.connslider.SetValue (cons)
+            self.connLowerText.SetLabel ('  %d' % (choice['conn']['min']))
+            self.connUpperText.SetLabel ('%d' % (choice['conn']['max']))
+            self.onConnScroll (0)
+            self.onRateScroll (0)
+            self.dow.setInitiate(choice.get('initiate', 40))
+            if choice.has_key('automatic'):
+                if not self.autorate:
+                    self.autorate = True
+                    self.rateSpinner.Enable(False)
+                    self.connSpinner.Enable(False)
+                    self.dow.setUploadRate(-1)
+            else:
+                if self.autorate:
+                    self.autorate = False
+                    self.rateSpinner.Enable(True)
+                    self.connSpinner.Enable(True)
+                    self.onRateSpinner()
+                    self.onConnSpinner()
+        except:
+            self.exception()
+
+
+    def about(self, event):
+        try:
+            if (self.aboutBox is not None):
+                try:
+                    self.aboutBox.Close ()
+                except wx.PyDeadObjectError, e:
+                    self.aboutBox = None
+
+            self.aboutBox = wx.Frame(None, -1, 'About BitTorrent', size = (1,1),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            try:
+                self.aboutBox.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(self.aboutBox, -1)
+
+            def StaticText(text, font = self.FONT, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_LEFT)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            colSizer = wx.FlexGridSizer(cols = 1, vgap = 3)
+
+            titleSizer = wx.BoxSizer(wx.HORIZONTAL)
+            aboutTitle = StaticText('BitTorrent ' + version + '  ', self.FONT+4)
+            titleSizer.Add (aboutTitle)
+            linkDonate = StaticText('Donate to Bram', self.FONT, True, 'Blue')
+            titleSizer.Add (linkDonate, 1, wx.ALIGN_BOTTOM&wx.EXPAND)
+            colSizer.Add(titleSizer, 0, wx.EXPAND)
+
+            colSizer.Add(StaticText('created by Bram Cohen, Copyright 2001-2003,'))
+            colSizer.Add(StaticText('experimental version maintained by John Hoffman 2003'))
+            colSizer.Add(StaticText('modified from experimental version by Eike Frost 2003'))
+            credits = StaticText('full credits\n', self.FONT, True, 'Blue')
+            colSizer.Add(credits);
+
+            si = ( 'exact Version String: ' + version + '\n' +
+                   'Python version: ' + sys.version + '\n' +
+                   'wxPython version: ' + wx.VERSION_STRING + '\n' )
+            try:
+                si += 'Psyco version: ' + hex(psyco.__version__)[2:] + '\n'
+            except:
+                pass
+            colSizer.Add(StaticText(si))
+
+            babble1 = StaticText(
+             'This is an experimental, unofficial build of BitTorrent.\n' +
+             'It is Free Software under an MIT-Style license.')
+            babble2 = StaticText('BitTorrent Homepage (link)', self.FONT, True, 'Blue')
+            babble3 = StaticText("TheSHAD0W's Client Homepage (link)", self.FONT, True, 'Blue')
+            babble4 = StaticText("Eike Frost's Client Homepage (link)", self.FONT, True, 'Blue')
+            babble6 = StaticText('License Terms (link)', self.FONT, True, 'Blue')
+            colSizer.Add (babble1)
+            colSizer.Add (babble2)
+            colSizer.Add (babble3)
+            colSizer.Add (babble4)
+            colSizer.Add (babble6)
+
+            okButton = wx.Button(panel, -1, 'Ok')
+            colSizer.Add(okButton, 0, wx.ALIGN_RIGHT)
+            colSizer.AddGrowableCol(0)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(colSizer, 1, wx.EXPAND | wx.ALL, 4)
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            def donatelink(self):
+                Thread(target = open_new('https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=bram@bitconjurer.org&item_name=BitTorrent&amount=5.00&submit=donate')).start()
+            wx.EVT_LEFT_DOWN(linkDonate, donatelink)
+            def aboutlink(self):
+                Thread(target = open_new('http://bitconjurer.org/BitTorrent/')).start()
+            wx.EVT_LEFT_DOWN(babble2, aboutlink)
+            def shadlink(self):
+                Thread(target = open_new('http://www.bittornado.com/')).start()
+            wx.EVT_LEFT_DOWN(babble3, shadlink)
+            def explink(self):
+                Thread(target = open_new('http://ei.kefro.st/projects/btclient/')).start()
+            wx.EVT_LEFT_DOWN(babble4, explink)
+            def licenselink(self):
+                Thread(target = open_new('http://ei.kefro.st/projects/btclient/LICENSE.TXT')).start()
+            wx.EVT_LEFT_DOWN(babble6, licenselink)
+            wx.EVT_LEFT_DOWN(credits, self.credits)
+
+            def closeAbout(e, self = self):
+                if self.aboutBox:
+                    self.aboutBox.Close()
+            wx.EVT_BUTTON(self.aboutBox, okButton.GetId(), closeAbout)
+            def kill(e, self = self):
+                try:
+                    self.aboutBox.RemoveIcon()
+                except:
+                    pass
+                self.aboutBox.Destroy()
+                self.aboutBox = None
+            wx.EVT_CLOSE(self.aboutBox, kill)
+
+            self.aboutBox.Show()
+            border.Fit(panel)
+            self.aboutBox.Fit()
+        except:
+            self.exception()
+
+
+    def details(self, event):
+        try:
+            if not self.dow or not self.filename:
+                return
+            metainfo = self.dow.getResponse()
+            if metainfo is None:
+                return
+            if metainfo.has_key('announce'):
+                announce = metainfo['announce']
+            else:
+                announce = None
+            if metainfo.has_key('announce-list'):
+                announce_list = metainfo['announce-list']
+            else:
+                announce_list = None
+            info = metainfo['info']
+            info_hash = self.dow.infohash
+            piece_length = info['piece length']
+            fileselector = self.dow.fileselector
+
+            if (self.detailBox is not None):
+                try:
+                    self.detailBox.Close()
+                except wx.PyDeadObjectError, e:
+                    self.detailBox = None
+
+            self.detailBox = wx.Frame(None, -1, 'Torrent Details ', size = wx.Size(405,230),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            try:
+                self.detailBox.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(self.detailBox, -1, size = wx.Size (400,220))
+
+            def StaticText(text, font = self.FONT-1, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_CENTER_VERTICAL)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            colSizer = wx.FlexGridSizer(cols = 1, vgap = 3)
+            colSizer.AddGrowableCol(0)
+
+            titleSizer = wx.BoxSizer(wx.HORIZONTAL)
+            aboutTitle = StaticText('Details about ' + self.filename, self.FONT+4)
+
+            titleSizer.Add (aboutTitle)
+            colSizer.Add (titleSizer)
+
+            detailSizer = wx.FlexGridSizer(cols = 2, vgap = 6)
+
+            if info.has_key('length'):
+                fileListID = None
+                detailSizer.Add(StaticText('file name :'))
+                detailSizer.Add(StaticText(info['name']))
+                if info.has_key('md5sum'):
+                    detailSizer.Add(StaticText('MD5 hash :'))
+                    detailSizer.Add(StaticText(info['md5sum']))
+                file_length = info['length']
+                name = "file size"
+            else:
+                detail1Sizer = wx.FlexGridSizer(cols = 1, vgap = 6)
+                detail1Sizer.Add(StaticText('directory name : ' + info['name']))
+                colSizer.Add (detail1Sizer)
+                bgallocButton = wx.BitmapButton(panel, -1, self.allocbuttonBitmap, size = (52,20))
+                def bgalloc(self, frame = self):
+                    if frame.dow.storagewrapper is not None:
+                        frame.dow.storagewrapper.bgalloc()
+                wx.EVT_BUTTON(self.detailBox, bgallocButton.GetId(), bgalloc)
+
+                bgallocbuttonSizer = wx.FlexGridSizer(cols = 4, hgap = 4, vgap = 0)
+                bgallocbuttonSizer.Add(StaticText('(right-click to set priority)',self.FONT-1),0,wx.ALIGN_BOTTOM)
+                bgallocbuttonSizer.Add(StaticText('(finish allocation)'), -1, wx.ALIGN_CENTER_VERTICAL)
+                bgallocbuttonSizer.Add(bgallocButton, -1, wx.ALIGN_CENTER)
+                bgallocbuttonSizer.AddGrowableCol(0)
+                colSizer.Add(bgallocbuttonSizer, -1, wx.EXPAND)
+
+                file_length = 0
+
+                fileListID = wx.NewId()
+                fileList = wx.ListCtrl(panel, fileListID,
+                                      wx.Point(-1,-1), (325,100), wx.LC_REPORT)
+                self.fileList = fileList
+                fileList.SetImageList(self.filestatusIcons, wx.IMAGE_LIST_SMALL)
+
+                fileList.SetAutoLayout (True)
+                fileList.InsertColumn(0, "file")
+                fileList.InsertColumn(1, "", format=wx.LIST_FORMAT_RIGHT, width=55)
+                fileList.InsertColumn(2, "")
+
+                for i in range(len(info['files'])):
+                    x = wx.ListItem()
+                    fileList.InsertItem(x)
+
+                x = 0
+                for file in info['files']:
+                    path = ' '
+                    for item in file['path']:
+                        if (path != ''):
+                            path = path + "/"
+                        path = path + item
+                    path += ' (' + str(file['length']) + ')'
+                    fileList.SetStringItem(x, 0, path)
+                    if file.has_key('md5sum'):
+                        fileList.SetStringItem(x, 2, '    [' + str(file['md5sum']) + ']')
+                    if fileselector:
+                        p = fileselector[x]
+                        item = self.fileList.GetItem(x)
+                        item.SetTextColour(self.prioritycolors[p+1])
+                        fileList.SetItem(item)
+                    x += 1
+                    file_length += file['length']
+                fileList.SetColumnWidth(0,wx.LIST_AUTOSIZE)
+                fileList.SetColumnWidth(2,wx.LIST_AUTOSIZE)
+
+                name = 'archive size'
+                colSizer.Add(fileList, 1, wx.EXPAND)
+                colSizer.AddGrowableRow(3)
+
+            detailSizer.Add(StaticText('info_hash :'),0,wx.ALIGN_CENTER_VERTICAL)
+            detailSizer.Add(wx.TextCtrl(panel, -1, tohex(info_hash), size = (325, -1), style = wx.TE_READONLY))
+            num_pieces = int((file_length+piece_length-1)/piece_length)
+            detailSizer.Add(StaticText(name + ' : '))
+            detailSizer.Add(StaticText('%s (%s bytes)' % (size_format(file_length), comma_format(file_length))))
+            detailSizer.Add(StaticText('pieces : '))
+            if num_pieces > 1:
+                detailSizer.Add(StaticText('%i (%s bytes each)' % (num_pieces, comma_format(piece_length))))
+            else:
+                detailSizer.Add(StaticText('1'))
+
+            if announce_list is None:
+                detailSizer.Add(StaticText('announce url : '),0,wx.ALIGN_CENTER_VERTICAL)
+                detailSizer.Add(wx.TextCtrl(panel, -1, announce, size = (325, -1), style = wx.TE_READONLY))
+            else:
+                detailSizer.Add(StaticText(''))
+                trackerList = wx.ListCtrl(panel, -1, wx.Point(-1,-1), (325,75), wx.LC_REPORT)
+                trackerList.SetAutoLayout (True)
+                trackerList.InsertColumn(0, "")
+                trackerList.InsertColumn(1, "announce urls")
+
+                for tier in range(len(announce_list)):
+                    for t in range(len(announce_list[tier])):
+                        i = wx.ListItem()
+                        trackerList.InsertItem(i)
+                if announce is not None:
+                    for l in [1,2]:
+                        i = wx.ListItem()
+                        trackerList.InsertItem(i)
+
+                x = 0
+                for tier in range(len(announce_list)):
+                    for t in range(len(announce_list[tier])):
+                        if t == 0:
+                            trackerList.SetStringItem(x, 0, 'tier '+str(tier)+':')
+                        trackerList.SetStringItem(x, 1, announce_list[tier][t])
+                        x += 1
+                if announce is not None:
+                    trackerList.SetStringItem(x+1, 0, 'single:')
+                    trackerList.SetStringItem(x+1, 1, announce)
+                trackerList.SetColumnWidth(0,wx.LIST_AUTOSIZE)
+                trackerList.SetColumnWidth(1,wx.LIST_AUTOSIZE)
+                detailSizer.Add(trackerList)
+
+            if announce is None and announce_list is not None:
+                announce = announce_list[0][0]
+            if announce is not None:
+                detailSizer.Add(StaticText('likely tracker :'))
+                p = re.compile( '(.*/)[^/]+')
+                turl = p.sub (r'\1', announce)
+                trackerUrl = StaticText(turl, self.FONT, True, 'Blue')
+                detailSizer.Add(trackerUrl)
+            if metainfo.has_key('comment'):
+                detailSizer.Add(StaticText('comment :'))
+                detailSizer.Add(StaticText(metainfo['comment']))
+            if metainfo.has_key('creation date'):
+                detailSizer.Add(StaticText('creation date :'))
+                try:
+                    detailSizer.Add(StaticText(
+                        strftime('%x %X',localtime(metainfo['creation date']))))
+                except:
+                    try:
+                        detailSizer.Add(StaticText(metainfo['creation date']))
+                    except:
+                        detailSizer.Add(StaticText('<cannot read date>'))
+
+            detailSizer.AddGrowableCol(1)
+            colSizer.Add (detailSizer, 1, wx.EXPAND)
+
+            okButton = wx.Button(panel, -1, 'Ok')
+            colSizer.Add(okButton, 0, wx.ALIGN_RIGHT)
+            colSizer.AddGrowableCol(0)
+
+            if not self.configfileargs['gui_stretchwindow']:
+                aboutTitle.SetSize((400,-1))
+            else:
+                panel.SetAutoLayout(True)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(colSizer, 1, wx.EXPAND | wx.ALL, 4)
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            if fileselector and fileListID:
+                def onRightClick(evt, self = self):
+                    s = []
+                    i = -1
+                    while True:
+                        i = self.fileList.GetNextItem(i,state=wx.LIST_STATE_SELECTED)
+                        if i == -1:
+                            break
+                        s.append(i)
+                    if not s:   # just in case
+                        return
+                    oldstate = self.dow.fileselector[s[0]]
+                    kind=wx.ITEM_RADIO
+                    for i in s[1:]:
+                        if self.dow.fileselector[i] != oldstate:
+                            oldstate = None
+                            kind = wx.ITEM_NORMAL
+                            break
+                    menu = wx.Menu()
+                    menu.Append(self.priorityIDs[1], "download first", kind=kind)
+                    menu.Append(self.priorityIDs[2], "download normally", kind=kind)
+                    menu.Append(self.priorityIDs[3], "download later", kind=kind)
+                    menu.Append(self.priorityIDs[0], "download never (deletes)", kind=kind)
+                    if oldstate is not None:
+                        menu.Check(self.priorityIDs[oldstate+1], True)
+
+                    def onSelection(evt, self = self, s = s):
+                        p = evt.GetId()
+                        priorities = self.dow.fileselector.get_priorities()
+                        for i in xrange(len(self.priorityIDs)):
+                            if p == self.priorityIDs[i]:
+                                for ss in s:
+                                    priorities[ss] = i-1
+                                    item = self.fileList.GetItem(ss)
+                                    item.SetTextColour(self.prioritycolors[i])
+                                    self.fileList.SetItem(item)
+                                self.dow.fileselector.set_priorities(priorities)
+                                self.fileList.Refresh()
+                                self.refresh_details = True
+                                break
+                        
+                    for id in self.priorityIDs:
+                        wx.EVT_MENU(self.detailBox, id, onSelection)
+
+                    self.detailBox.PopupMenu(menu, evt.GetPoint())
+                        
+                wx.EVT_LIST_ITEM_RIGHT_CLICK(self.detailBox, fileListID, onRightClick)
+
+            def closeDetail(evt, self = self):
+                if self.detailBox:
+                    self.detailBox.Close()
+            wx.EVT_BUTTON(self.detailBox, okButton.GetId(), closeDetail)
+            def kill(evt, self = self):
+                try:
+                    self.detailBox.RemoveIcon()
+                except:
+                    pass
+                self.detailBox.Destroy()
+                self.detailBox = None
+                self.fileList = None
+                self.dow.filedatflag.clear()
+            wx.EVT_CLOSE(self.detailBox, kill)
+
+            def trackerurl(self, turl = turl):
+                try:
+                    Thread(target = open_new(turl)).start()
+                except:
+                    pass
+            wx.EVT_LEFT_DOWN(trackerUrl, trackerurl)
+
+            self.detailBox.Show ()
+            border.Fit(panel)
+            self.detailBox.Fit()
+
+            self.refresh_details = True
+            self.dow.filedatflag.set()
+        except:
+            self.exception()
+
+
+    def credits(self, event):
+        try:
+            if (self.creditsBox is not None):
+                try:
+                    self.creditsBox.Close()
+                except wx.PyDeadObjectError, e:
+                    self.creditsBox = None
+
+            self.creditsBox = wx.Frame(None, -1, 'Credits', size = (1,1),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            try:
+                self.creditsBox.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(self.creditsBox, -1)
+
+            def StaticText(text, font = self.FONT, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_LEFT)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            colSizer = wx.FlexGridSizer(cols = 1, vgap = 3)
+
+            titleSizer = wx.BoxSizer(wx.HORIZONTAL)
+            aboutTitle = StaticText('Credits', self.FONT+4)
+            titleSizer.Add (aboutTitle)
+            colSizer.Add (titleSizer)
+            colSizer.Add (StaticText(
+              'The following people have all helped with this\n' +
+              'version of BitTorrent in some way (in no particular order) -\n'));
+            creditSizer = wx.FlexGridSizer(cols = 3)
+            creditSizer.Add(StaticText(
+              'Bill Bumgarner\n' +
+              'David Creswick\n' +
+              'Andrew Loewenstern\n' +
+              'Ross Cohen\n' +
+              'Jeremy Avnet\n' +
+              'Greg Broiles\n' +
+              'Barry Cohen\n' +
+              'Bram Cohen\n' +
+              'sayke\n' +
+              'Steve Jenson\n' +
+              'Myers Carpenter\n' +
+              'Francis Crick\n' +
+              'Petru Paler\n' +
+              'Jeff Darcy\n' +
+              'John Gilmore\n' +
+              'Xavier Bassery\n' +
+              'Pav Lucistnik'))
+            creditSizer.Add(StaticText('  '))
+            creditSizer.Add(StaticText(
+              'Yann Vernier\n' +
+              'Pat Mahoney\n' +
+              'Boris Zbarsky\n' +
+              'Eric Tiedemann\n' +
+              'Henry "Pi" James\n' +
+              'Loring Holden\n' +
+              'Robert Stone\n' +
+              'Michael Janssen\n' +
+              'Eike Frost\n' +
+              'Andrew Todd\n' +
+              'otaku\n' +
+              'Edward Keyes\n' +
+              'John Hoffman\n' +
+              'Uoti Urpala\n' +
+              'Jon Wolf\n' +
+              'Christoph Hohmann\n' +
+              'Micah Anderson'))
+            colSizer.Add (creditSizer, flag = wx.ALIGN_CENTER_HORIZONTAL)
+            okButton = wx.Button(panel, -1, 'Ok')
+            colSizer.Add(okButton, 0, wx.ALIGN_RIGHT)
+            colSizer.AddGrowableCol(0)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(colSizer, 1, wx.EXPAND | wx.ALL, 4)
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            def closeCredits(e, self = self):
+                if self.creditsBox:
+                    self.creditsBox.Close()
+            wx.EVT_BUTTON(self.creditsBox, okButton.GetId(), closeCredits)
+            def kill(e, self = self):
+                try:
+                    self.creditsBox.RemoveIcon()
+                except:
+                    pass
+                self.creditsBox.Destroy()
+                self.creditsBox = None
+            wx.EVT_CLOSE(self.creditsBox, kill)
+
+            self.creditsBox.Show()
+            border.Fit(panel)
+            self.creditsBox.Fit()
+        except:
+            self.exception()
+
+
+    def statusIconHelp(self, event):
+        try:
+            if (self.statusIconHelpBox is not None):
+                try:
+                    self.statusIconHelpBox.Close()
+                except wx.PyDeadObjectError, e:
+                    self.statusIconHelpBox = None
+
+            self.statusIconHelpBox = wx.Frame(None, -1, 'Help with the BitTorrent Status Light', size = (1,1),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            try:
+                self.statusIconHelpBox.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(self.statusIconHelpBox, -1)
+
+            def StaticText(text, font = self.FONT, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_LEFT)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            fullsizer = wx.FlexGridSizer(cols = 1, vgap = 13)
+            colsizer = wx.FlexGridSizer(cols = 2, hgap = 13, vgap = 13)
+
+            disconnectedicon=self.createStatusIcon('disconnected')
+            colsizer.Add(wx.StaticBitmap(panel, -1, disconnectedicon))
+            colsizer.Add(StaticText(
+                'Waiting to connect to the tracker.\n' +
+                'If the status light stays black for a long time the tracker\n' +
+                'you are trying to connect to may not be working.  Unless you\n' +
+                'are receiving a message telling you otherwise, please wait,\n' +
+                'and BitTorrent will automatically try to reconnect for you.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            noconnectionsicon=self.createStatusIcon('noconnections')
+            colsizer.Add(wx.StaticBitmap(panel, -1, noconnectionsicon))
+            colsizer.Add(StaticText(
+                'You have no connections with other clients.\n' +
+                'Please be patient.  If after several minutes the status\n' +
+                'light remains red, this torrent may be old and abandoned.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            noincomingicon=self.createStatusIcon('noincoming')
+            colsizer.Add(wx.StaticBitmap(panel, -1, noincomingicon))
+            colsizer.Add(StaticText(
+                'You have not received any incoming connections from others.\n' +
+                'It may only be because no one has tried.  If you never see\n' +
+                'the status light turn green, it may indicate your system\n' +
+                'is behind a firewall or proxy server.  Please look into\n' +
+                'routing BitTorrent through your firewall in order to receive\n' +
+                'the best possible download rate.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            nocompletesicon=self.createStatusIcon('nocompletes')
+            colsizer.Add(wx.StaticBitmap(panel, -1, nocompletesicon))
+            colsizer.Add(StaticText(
+                'There are no complete copies among the clients you are\n' +
+                'connected to.  Don\'t panic, other clients in the torrent\n' +
+                "you can't see may have the missing data.\n" +
+                'If the status light remains blue, you may have problems\n' +
+                'completing your download.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            allgoodicon=self.createStatusIcon('allgood')
+            colsizer.Add(wx.StaticBitmap(panel, -1, allgoodicon))
+            colsizer.Add(StaticText(
+                'The torrent is operating properly.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            fullsizer.Add(colsizer, 0, wx.ALIGN_CENTER)
+            colsizer2 = wx.FlexGridSizer(cols = 1, hgap = 13)
+
+            colsizer2.Add(StaticText(
+                'Please note that the status light is not omniscient, and that it may\n' +
+                'be wrong in many instances.  A torrent with a blue light may complete\n' +
+                "normally, and an occasional yellow light doesn't mean your computer\n" +
+                'has suddenly become firewalled.'), 1, wx.ALIGN_CENTER_VERTICAL)
+
+            colspacer = StaticText('  ')
+            colsizer2.Add(colspacer)
+
+            okButton = wx.Button(panel, -1, 'Ok')
+            colsizer2.Add(okButton, 0, wx.ALIGN_CENTER)
+            fullsizer.Add(colsizer2, 0, wx.ALIGN_CENTER)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(fullsizer, 1, wx.EXPAND | wx.ALL, 4)
+
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+
+            def closeHelp(self, frame = self):
+                frame.statusIconHelpBox.Close()
+            wx.EVT_BUTTON(self.statusIconHelpBox, okButton.GetId(), closeHelp)
+
+            self.statusIconHelpBox.Show ()
+            border.Fit(panel)
+            self.statusIconHelpBox.Fit()
+        except:
+            self.exception()
+
+
+    def openConfigMenu(self, event):
+        try:
+            self.configfile.configMenu(self)
+        except:
+            self.exception()
+
+
+    def advanced(self, event):
+        try:
+            if not self.dow or not self.filename:
+                return
+            if (self.advBox is not None):
+                try:
+                    self.advBox.Close ()
+                except wx.PyDeadObjectError, e:
+                    self.advBox = None
+
+            self.advBox = wx.Frame(None, -1, 'BitTorrent Advanced', size = wx.Size(200,200),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            try:
+                self.advBox.SetIcon(self.icon)
+            except:
+                pass
+
+            panel = wx.Panel(self.advBox, -1, size = wx.Size (200,200))
+
+            def StaticText(text, font = self.FONT-1, underline = False, color = None, panel = panel):
+                x = wx.StaticText(panel, -1, text, style = wx.ALIGN_LEFT)
+                x.SetFont(wx.Font(font, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline))
+                if color is not None:
+                    x.SetForegroundColour(color)
+                return x
+
+            colSizer = wx.FlexGridSizer (cols = 1, vgap = 1)
+            colSizer.Add (StaticText('Advanced Info for ' + self.filename, self.FONT+4))
+
+            try:    # get system font width
+                fw = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT).GetPointSize()+1
+            except:
+                fw = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT).GetPointSize()+1
+
+            spewList = wx.ListCtrl(panel, -1, wx.Point(-1,-1), (fw*66,350), wx.LC_REPORT|wx.LC_HRULES|wx.LC_VRULES)
+            self.spewList = spewList
+            spewList.SetAutoLayout (True)
+
+            colSizer.Add(spewList, -1, wx.EXPAND)
+
+            colSizer.Add(StaticText(''))
+            self.storagestats1 = StaticText('')
+            self.storagestats2 = StaticText('')
+            colSizer.Add(self.storagestats1, -1, wx.EXPAND)
+            colSizer.Add(self.storagestats2, -1, wx.EXPAND)
+            spinnerSizer = wx.FlexGridSizer(cols=4,vgap=0,hgap=0)
+            cstats = '          Listening on '
+            if self.connection_stats['interfaces']:
+                cstats += ', '.join(self.connection_stats['interfaces']) + ' on '
+            cstats += 'port ' + str(self.connection_stats['port'])
+            if self.connection_stats['upnp']:
+                cstats += ', UPnP port forwarded'
+            spinnerSizer.Add(StaticText(cstats), -1, wx.EXPAND)
+            spinnerSizer.AddGrowableCol(0)
+            spinnerSizer.Add(StaticText('Max download rate (kB/s) '),0,wx.ALIGN_CENTER_VERTICAL)
+            self.downrateSpinner = wx.SpinCtrl (panel, -1, "", (-1,-1), (50, -1))
+            self.downrateSpinner.SetFont(self.default_font)
+            self.downrateSpinner.SetRange(0,5000)
+            self.downrateSpinner.SetValue(self.config['max_download_rate'])
+            spinnerSizer.Add (self.downrateSpinner, 0)
+            wx.EVT_SPINCTRL(self.downrateSpinner, -1, self.onDownRateSpinner)
+            spinnerSizer.Add(StaticText(' (0 = unlimited)  '),0,wx.ALIGN_CENTER_VERTICAL)
+            colSizer.Add(spinnerSizer,0,wx.EXPAND)
+
+            colSizer.Add(StaticText(''))
+
+            buttonSizer = wx.FlexGridSizer (cols = 5, hgap = 20)
+
+            reannounceButton = wx.Button(panel, -1, 'Manual Announce')
+            buttonSizer.Add (reannounceButton)
+
+            extannounceButton = wx.Button(panel, -1, 'External Announce')
+            buttonSizer.Add (extannounceButton)
+
+            bgallocButton = wx.Button(panel, -1, 'Finish Allocation')
+            buttonSizer.Add (bgallocButton)
+
+            buttonSizer.Add(StaticText(''))
+
+            okButton = wx.Button(panel, -1, 'Ok')
+            buttonSizer.Add (okButton)
+
+            colSizer.Add (buttonSizer, 0, wx.ALIGN_CENTER)
+            colSizer.AddGrowableCol(0)
+            colSizer.AddGrowableRow(1)
+
+            panel.SetSizer(colSizer)
+            panel.SetAutoLayout(True)
+
+            spewList.InsertColumn(0, "Optimistic Unchoke", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(1, "Peer ID", width=0)
+            spewList.InsertColumn(2, "IP", width=fw*11)
+            spewList.InsertColumn(3, "Local/Remote", format=wx.LIST_FORMAT_CENTER, width=fw*3)
+            spewList.InsertColumn(4, "Up", format=wx.LIST_FORMAT_RIGHT, width=fw*6)
+            spewList.InsertColumn(5, "Interested", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(6, "Choking", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(7, "Down", format=wx.LIST_FORMAT_RIGHT, width=fw*6)
+            spewList.InsertColumn(8, "Interesting", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(9, "Choked", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(10, "Snubbed", format=wx.LIST_FORMAT_CENTER, width=fw*2)
+            spewList.InsertColumn(11, "Downloaded", format=wx.LIST_FORMAT_RIGHT, width=fw*7)
+            spewList.InsertColumn(12, "Uploaded", format=wx.LIST_FORMAT_RIGHT, width=fw*7)
+            spewList.InsertColumn(13, "Completed", format=wx.LIST_FORMAT_RIGHT, width=fw*6)
+            spewList.InsertColumn(14, "Peer Download Speed", format=wx.LIST_FORMAT_RIGHT, width=fw*6)
+
+            def reannounce(self, frame = self):
+                if (clock() - frame.reannouncelast > 60):
+                    frame.reannouncelast = clock()
+                    frame.dow.reannounce()
+            wx.EVT_BUTTON(self.advBox, reannounceButton.GetId(), reannounce)
+
+            self.advextannouncebox = None
+            def reannounce_external(self, frame = self):
+                if (frame.advextannouncebox is not None):
+                    try:
+                        frame.advextannouncebox.Close ()
+                    except wx.PyDeadObjectError, e:
+                        frame.advextannouncebox = None
+
+                frame.advextannouncebox = wx.Frame(None, -1, 'External Announce', size = (1,1),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+                try:
+                    frame.advextannouncebox.SetIcon(frame.icon)
+                except:
+                    pass
+
+                panel = wx.Panel(frame.advextannouncebox, -1)
+
+                fullsizer = wx.FlexGridSizer(cols = 1, vgap = 13)
+                msg = wx.StaticText(panel, -1, "Enter tracker anounce URL:")
+                msg.SetFont(frame.default_font)
+                fullsizer.Add(msg)
+
+                frame.advexturl = wx.TextCtrl(parent = panel, id = -1, value = '',
+                                    size = (255, 20), style = wx.TE_PROCESS_TAB)
+                frame.advexturl.SetFont(frame.default_font)
+                frame.advexturl.SetValue(frame.lastexternalannounce)
+                fullsizer.Add(frame.advexturl)
+
+                buttonSizer = wx.FlexGridSizer (cols = 2, hgap = 10)
+
+                okButton = wx.Button(panel, -1, 'OK')
+                buttonSizer.Add (okButton)
+
+                cancelButton = wx.Button(panel, -1, 'Cancel')
+                buttonSizer.Add (cancelButton)
+
+                fullsizer.Add (buttonSizer, 0, wx.ALIGN_CENTER)
+
+                border = wx.BoxSizer(wx.HORIZONTAL)
+                border.Add(fullsizer, 1, wx.EXPAND | wx.ALL, 4)
+
+                panel.SetSizer(border)
+                panel.SetAutoLayout(True)
+
+                def ok(self, frame = frame):
+                    special = frame.advexturl.GetValue()
+                    if special:
+                        frame.lastexternalannounce = special
+                        if (clock() - frame.reannouncelast > 60):
+                            frame.reannouncelast = clock()
+                            frame.dow.reannounce(special)
+                    frame.advextannouncebox.Close()
+                wx.EVT_BUTTON(frame.advextannouncebox, okButton.GetId(), ok)
+
+                def cancel(self, frame = frame):
+                    frame.advextannouncebox.Close()
+                wx.EVT_BUTTON(frame.advextannouncebox, cancelButton.GetId(), cancel)
+
+                frame.advextannouncebox.Show ()
+                fullsizer.Fit(panel)
+                frame.advextannouncebox.Fit()
+
+            wx.EVT_BUTTON(self.advBox, extannounceButton.GetId(), reannounce_external)
+
+            def bgalloc(self, frame = self):
+                if frame.dow.storagewrapper is not None:
+                    frame.dow.storagewrapper.bgalloc()
+            wx.EVT_BUTTON(self.advBox, bgallocButton.GetId(), bgalloc)
+
+            def closeAdv(evt, self = self):
+                self.advBox.Close()
+            def killAdv(evt, self = self):
+                try:
+                    self.advBox.RemoveIcon()
+                except:
+                    pass
+                self.onDownRateSpinner()
+                self.dow.spewflag.clear()
+                self.advBox.Destroy()
+                self.advBox = None
+                if (self.advextannouncebox is not None):
+                    try:
+                        self.advextannouncebox.Close()
+                    except wx.PyDeadObjectError, e:
+                        pass
+                    self.advextannouncebox = None
+            wx.EVT_BUTTON(self.advBox, okButton.GetId(), closeAdv)
+            wx.EVT_CLOSE(self.advBox, killAdv)
+
+            self.advBox.Show ()
+            colSizer.Fit(panel)
+            self.advBox.Fit()
+            if self.dow:
+                self.dow.spewflag.set()
+        except:
+            self.exception()
+
+
+    def displayUsage(self, text):
+        self.invokeLater(self.onDisplayUsage, [text])
+
+    def onDisplayUsage(self, text):        
+        try:
+            self.done(None)
+            w = wx.Frame(None, -1, 'BITTORRENT USAGE',
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            panel = wx.Panel(w, -1)
+            sizer = wx.FlexGridSizer(cols = 1)
+            sizer.Add(wx.TextCtrl(panel, -1, text,
+                        size = (500,300), style = wx.TE_READONLY|wx.TE_MULTILINE))
+            okButton = wx.Button(panel, -1, 'Ok')
+
+            def closeUsage(self, frame = self):
+                frame.usageBox.Close()
+            wx.EVT_BUTTON(w, okButton.GetId(), closeUsage)
+            def kill(self, frame = self):
+                frame.usageBox.Destroy()
+                frame.usageBox = None
+            wx.EVT_CLOSE(w, kill)
+
+            sizer.Add(okButton, 0, wx.ALIGN_RIGHT)
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(sizer, 1, wx.EXPAND | wx.ALL, 4)
+
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            border.Fit(panel)
+            w.Fit()
+            w.Show()
+            self.usageBox = w
+        except:
+            self.exception()
+
+
+    def updateStatus(self, dpflag = Event(), fractionDone = None,
+            timeEst = None, downRate = None, upRate = None,
+            activity = None, statistics = None, spew = None, sizeDone = None,
+            **kws):
+        if activity is not None:
+            self.activity = activity
+        self.gui_fractiondone = fractionDone
+        self.invokeLater(self.onUpdateStatus,
+                 [dpflag, timeEst, downRate, upRate, statistics, spew, sizeDone])
+
+    def onUpdateStatus(self, dpflag, timeEst, downRate, upRate,
+                             statistics, spew, sizeDone):
+        if self.firstupdate:
+            if not self.old_ratesettings:
+                self.old_ratesettings = {}
+            self.connChoice.SetStringSelection(
+                self.old_ratesettings.get('rate setting',
+                                  self.configfileargs['gui_ratesettingsdefault']))
+            self.onConnChoice(0,
+                              self.old_ratesettings.get('uploads'),
+                              self.old_ratesettings.get('max upload rate'))
+            if self.old_ratesettings.has_key('max download rate'):
+                self.dow.setDownloadRate(self.old_ratesettings['max download rate'])
+                if self.advBox:
+                    self.downrateSpinner.SetValue(self.old_ratesettings['max download rate'])
+            self.firstupdate = False
+            if self.advBox:
+                self.dow.spewflag.set()
+        if self.ispaused or statistics is None:
+            self.setStatusIcon('startup')
+        elif statistics.numPeers + statistics.numSeeds + statistics.numOldSeeds == 0:
+            if statistics.last_failed:
+                self.setStatusIcon('disconnected')
+            else:
+                self.setStatusIcon('noconnections')
+        elif ( not statistics.external_connection_made
+            and not self.configfileargs['gui_forcegreenonfirewall'] ):
+            self.setStatusIcon('noincoming')
+        elif ( (statistics.numSeeds + statistics.numOldSeeds == 0)
+               and ( (self.fin and statistics.numCopies < 1)
+                    or (not self.fin and statistics.numCopies2 < 1) ) ):
+            self.setStatusIcon('nocompletes')
+        elif timeEst == 0 and sizeDone < self.torrentsize:
+            self.setStatusIcon('nocompletes')
+        else:
+            self.setStatusIcon('allgood')
+        if statistics is None:
+            self.setgaugemode(-1)
+        elif self.gui_fractiondone == None or self.gui_fractiondone == 1.0:
+            self.setgaugemode(1)
+        else:
+            self.setgaugemode(0)
+
+        if self.updateSliderFlag == 1:
+            self.updateSliderFlag = 0
+            newValue = (self.rateSpinner.GetValue()
+                         / connChoices[self.connChoice.GetSelection()]['rate'].get('div',1))
+            if self.rateslider.GetValue() != newValue:
+                self.rateslider.SetValue(newValue)
+        if self.updateSpinnerFlag == 1:
+            self.updateSpinnerFlag = 0
+            cc = connChoices[self.connChoice.GetSelection()]
+            if cc.has_key('rate'):
+                newValue = (self.rateslider.GetValue() * cc['rate'].get('div',1))
+                if self.rateSpinner.GetValue() != newValue:
+                    self.rateSpinner.SetValue(newValue)
+
+        if self.fin:
+            if statistics is None or statistics.numOldSeeds > 0 or statistics.numCopies > 1:
+                self.gauge.SetValue(1000)
+            else:
+                self.gauge.SetValue(int(1000*statistics.numCopies))
+        elif self.gui_fractiondone is not None:
+            gaugelevel = int(self.gui_fractiondone * 1000)
+            self.gauge.SetValue(gaugelevel)
+            if statistics is not None and statistics.downTotal is not None:
+                if self.configfileargs['gui_displaymiscstats']:
+                    self.frame.SetTitle('%.1f%% (%.2f MiB) %s - BitTorrent %s' % (float(gaugelevel)/10, float(sizeDone) / (1<<20), self.filename, version))
+                else:
+                    self.frame.SetTitle('%.1f%% %s - BitTorrent %s' % (float(gaugelevel)/10, self.filename, version))
+            else:
+                self.frame.SetTitle('%.0f%% %s - BitTorrent %s' % (float(gaugelevel)/10, self.filename, version))
+        if self.ispaused:
+            self.timeText.SetLabel(hours(clock() - self.starttime) + ' /')
+        elif timeEst is None:
+            self.timeText.SetLabel(hours(clock() - self.starttime) + ' / ' + self.activity)
+        else:
+            self.timeText.SetLabel(hours(clock() - self.starttime) + ' / ' + hours(timeEst))
+        if not self.ispaused:
+            if downRate is not None:
+                self.downRateText.SetLabel('%.0f kB/s' % (float(downRate) / 1000))
+            if upRate is not None:
+                self.upRateText.SetLabel('%.0f kB/s' % (float(upRate) / 1000))
+        if self.taskbaricon:
+            icontext='BitTorrent '
+            if self.gui_fractiondone is not None and not self.fin:
+                if statistics is not None and statistics.downTotal is not None:
+                    icontext=icontext+' %.1f%% (%.2f MiB)' % (self.gui_fractiondone*100, float(sizeDone) / (1<<20))
+                else:
+                    icontext=icontext+' %.0f%%' % (self.gui_fractiondone*100)
+            if upRate is not None:
+                icontext=icontext+' u:%.0f kB/s' % (float(upRate) / 1000)
+            if downRate is not None:
+                icontext=icontext+' d:%.0f kB/s' % (float(downRate) / 1000)
+            icontext+=' %s' % self.filename
+            try:
+                if self.gui_fractiondone == None or self.gui_fractiondone == 1.0:
+                    self.frame.tbicon.SetIcon(self.finicon,icontext)
+                else:
+                    self.frame.tbicon.SetIcon(self.icon,icontext)
+            except:
+                pass
+        if statistics is not None:
+            if self.autorate:
+                self.rateSpinner.SetValue(statistics.upRate)
+                self.connSpinner.SetValue(statistics.upSlots)
+
+            downtotal = statistics.downTotal + self.old_download
+            uptotal = statistics.upTotal + self.old_upload
+            if self.configfileargs['gui_displaymiscstats']:
+                self.downText.SetLabel('%.2f MiB' % (float(downtotal) / (1 << 20)))
+                self.upText.SetLabel('%.2f MiB' % (float(uptotal) / (1 << 20)))
+            if downtotal > 0:
+                sharerating = float(uptotal)/downtotal
+                if sharerating == 0:
+                    shareSmiley = ''
+                    color = 'Black'
+                elif sharerating < 0.5:
+                    shareSmiley = ':-('
+                    color = 'Red'
+                elif sharerating < 1.0:
+                    shareSmiley = ':-|'
+                    color = 'Orange'
+                else:
+                    shareSmiley = ':-)'
+                    color = 'Forest Green'
+            elif uptotal == 0:
+                sharerating = None
+                shareSmiley = ''
+                color = 'Black'
+            else:
+                sharerating = None
+                shareSmiley = '00 :-D'
+                color = 'Forest Green'
+            if sharerating is None:
+                self.shareRatingText.SetLabel(shareSmiley)
+            else:
+                self.shareRatingText.SetLabel('%.3f %s' % (sharerating, shareSmiley))
+            self.shareRatingText.SetForegroundColour(color)
+
+            if self.configfileargs['gui_displaystats']:
+                if not self.fin:
+                    self.seedStatusText.SetLabel('connected to %d seeds; also seeing %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies2)))
+                else:
+                    self.seedStatusText.SetLabel('%d seeds seen recently; also seeing %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies)))
+                self.peerStatusText.SetLabel('connected to %d peers with an average of %.1f%% completed (total speed %.0f kB/s)' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1000)))
+        if ((clock() - self.lastError) > 300):
+            self.errorText.SetLabel('')
+
+        if ( self.configfileargs['gui_displaymiscstats']
+            and statistics is not None and statistics.backgroundallocating ):
+            self.bgalloc_periods += 1
+            if self.bgalloc_periods > 3:
+                self.bgalloc_periods = 0
+            self.bgallocText.SetLabel('ALLOCATING'+(' .'*self.bgalloc_periods))
+        elif self.dow.superseedflag.isSet():
+            self.bgallocText.SetLabel('SUPER-SEED')
+        else:
+            self.bgallocText.SetLabel('')
+
+
+        if spew is not None and (clock()-self.spewwait>1):
+            if (self.advBox is not None):
+                self.spewwait = clock()
+                spewList = self.spewList
+                spewlen = len(spew)+2
+                if statistics is not None:
+                    kickbanlen = len(statistics.peers_kicked)+len(statistics.peers_banned)
+                    if kickbanlen:
+                        spewlen += kickbanlen+1
+                else:
+                    kickbanlen = 0
+                for x in range(spewlen-spewList.GetItemCount()):
+                    i = wx.ListItem()
+                    spewList.InsertItem(i)
+                for x in range(spewlen,spewList.GetItemCount()):
+                    spewList.DeleteItem(len(spew)+1)
+
+                tot_uprate = 0.0
+                tot_downrate = 0.0
+                for x in range(len(spew)):
+                    if (spew[x]['optimistic'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 0, a)
+                    spewList.SetStringItem(x, 1, spew[x]['id'])
+                    spewList.SetStringItem(x, 2, spew[x]['ip'])
+                    spewList.SetStringItem(x, 3, spew[x]['direction'])
+                    if spew[x]['uprate'] > 100:
+                        spewList.SetStringItem(x, 4, '%.0f kB/s' % (float(spew[x]['uprate']) / 1000))
+                    else:
+                        spewList.SetStringItem(x, 4, ' ')
+                    tot_uprate += spew[x]['uprate']
+                    if (spew[x]['uinterested'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 5, a)
+                    if (spew[x]['uchoked'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 6, a)
+
+                    if spew[x]['downrate'] > 100:
+                        spewList.SetStringItem(x, 7, '%.0f kB/s' % (float(spew[x]['downrate']) / 1000))
+                    else:
+                        spewList.SetStringItem(x, 7, ' ')
+                    tot_downrate += spew[x]['downrate']
+
+                    if (spew[x]['dinterested'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 8, a)
+                    if (spew[x]['dchoked'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 9, a)
+                    if (spew[x]['snubbed'] == 1):
+                        a = '*'
+                    else:
+                        a = ' '
+                    spewList.SetStringItem(x, 10, a)
+                    spewList.SetStringItem(x, 11, '%.2f MiB' % (float(spew[x]['dtotal']) / (1 << 20)))
+                    if spew[x]['utotal'] is not None:
+                        a = '%.2f MiB' % (float(spew[x]['utotal']) / (1 << 20))
+                    else:
+                        a = ''
+                    spewList.SetStringItem(x, 12, a)
+                    spewList.SetStringItem(x, 13, '%.1f%%' % (float(int(spew[x]['completed']*1000))/10))
+                    if spew[x]['speed'] is not None:
+                        a = '%.0f kB/s' % (float(spew[x]['speed']) / 1000)
+                    else:
+                        a = ''
+                    spewList.SetStringItem(x, 14, a)
+
+                x = len(spew)
+                for i in range(15):
+                    spewList.SetStringItem(x, i, '')
+
+                x += 1
+                spewList.SetStringItem(x, 2, '         TOTALS:')
+                spewList.SetStringItem(x, 4, '%.0f kB/s' % (float(tot_uprate) / 1000))
+                spewList.SetStringItem(x, 7, '%.0f kB/s' % (float(tot_downrate) / 1000))
+                if statistics is not None:
+                    spewList.SetStringItem(x, 11, '%.2f MiB' % (float(statistics.downTotal) / (1 << 20)))
+                    spewList.SetStringItem(x, 12, '%.2f MiB' % (float(statistics.upTotal) / (1 << 20)))
+                else:
+                    spewList.SetStringItem(x, 11, '')
+                    spewList.SetStringItem(x, 12, '')
+                for i in [0,1,3,5,6,8,9,10,13,14]:
+                    spewList.SetStringItem(x, i, '')
+
+                if kickbanlen:
+                    x += 1
+                    for i in range(14):
+                        spewList.SetStringItem(x, i, '')
+
+                    for peer in statistics.peers_kicked:
+                        x += 1
+                        spewList.SetStringItem(x, 2, peer[0])
+                        spewList.SetStringItem(x, 1, peer[1])
+                        spewList.SetStringItem(x, 4, 'KICKED')
+                        for i in [0,3,5,6,7,8,9,10,11,12,13,14]:
+                            spewList.SetStringItem(x, i, '')
+
+                    for peer in statistics.peers_banned:
+                        x += 1
+                        spewList.SetStringItem(x, 2, peer[0])
+                        spewList.SetStringItem(x, 1, peer[1])
+                        spewList.SetStringItem(x, 4, 'BANNED')
+                        for i in [0,3,5,6,7,8,9,10,11,12,13,14]:
+                            spewList.SetStringItem(x, i, '')
+
+                if statistics is not None:
+                    l1 = (
+                        '          currently downloading %d pieces (%d just started), %d pieces partially retrieved'
+                                        % ( statistics.storage_active,
+                                            statistics.storage_new,
+                                            statistics.storage_dirty ) )
+                    if statistics.storage_isendgame:
+                        l1 += ', endgame mode'
+                    self.storagestats2.SetLabel(l1)
+                    self.storagestats1.SetLabel(
+                        '          %d of %d pieces complete (%d just downloaded), %d failed hash check, %sKiB redundant data discarded'
+                                        % ( statistics.storage_numcomplete,
+                                            statistics.storage_totalpieces,
+                                            statistics.storage_justdownloaded,
+                                            statistics.storage_numflunked,
+                                            comma_format(int(statistics.discarded/1024)) ) )
+
+        if ( self.fileList is not None and statistics is not None
+                and (statistics.filelistupdated.isSet() or self.refresh_details) ):
+            for i in range(len(statistics.filecomplete)):
+                if self.dow.fileselector[i] == -1:
+                    self.fileList.SetItemImage(i,0,0)
+                    self.fileList.SetStringItem(i,1,'')
+                elif statistics.fileinplace[i]:
+                    self.fileList.SetItemImage(i,2,2)
+                    self.fileList.SetStringItem(i,1,"done")
+                elif statistics.filecomplete[i]:
+                    self.fileList.SetItemImage(i,1,1)
+                    self.fileList.SetStringItem(i,1,"100%")
+                else:
+                    self.fileList.SetItemImage(i,0,0)
+                    frac = statistics.fileamtdone[i]
+                    if frac:
+                        self.fileList.SetStringItem(i,1,'%d%%' % (frac*100))
+                    else:
+                        self.fileList.SetStringItem(i,1,'')
+
+            statistics.filelistupdated.clear()
+            self.refresh_details = False
+
+        if self.configfile.configReset():     # whoopee!  Set everything invisible! :-)
+
+            self.dow.config['security'] = self.configfileargs['security']
+
+            statsdisplayflag = self.configfileargs['gui_displaymiscstats']
+            self.downTextLabel.Show(statsdisplayflag)
+            self.upTextLabel.Show(statsdisplayflag)
+            self.fileDestLabel.Show(statsdisplayflag)
+            self.fileDestText.Show(statsdisplayflag)
+            self.colSizer.Layout()
+
+            self.downText.SetLabel('')          # blank these to flush them
+            self.upText.SetLabel('')
+            self.seedStatusText.SetLabel('')
+            self.peerStatusText.SetLabel('')
+
+            ratesettingsmode = self.configfileargs['gui_ratesettingsmode']
+            ratesettingsflag1 = True    #\ settings
+            ratesettingsflag2 = False   #/ for 'basic'
+            if ratesettingsmode == 'none':
+                ratesettingsflag1 = False
+            elif ratesettingsmode == 'full':
+                ratesettingsflag2 = True
+            self.connChoiceLabel.Show(ratesettingsflag1)
+            self.connChoice.Show(ratesettingsflag1)
+            self.rateSpinnerLabel.Show(ratesettingsflag2)
+            self.rateSpinner.Show(ratesettingsflag2)
+            self.rateLowerText.Show(ratesettingsflag2)
+            self.rateUpperText.Show(ratesettingsflag2)
+            self.rateslider.Show(ratesettingsflag2)
+            self.connSpinnerLabel.Show(ratesettingsflag2)
+            self.connSpinner.Show(ratesettingsflag2)
+            self.connLowerText.Show(ratesettingsflag2)
+            self.connUpperText.Show(ratesettingsflag2)
+            self.connslider.Show(ratesettingsflag2)
+            self.unlimitedLabel.Show(ratesettingsflag2)
+
+            self.setgaugemode(None)
+
+        self.frame.Layout()
+        self.frame.Refresh()
+
+        self.gui_fractiondone = None
+        dpflag.set()
+
+
+    def finished(self):
+        self.fin = True
+        self.invokeLater(self.onFinishEvent)
+
+    def failed(self):
+        self.fin = True
+        self.invokeLater(self.onFailEvent)
+
+    def error(self, errormsg):
+        self.invokeLater(self.onErrorEvent, [errormsg])
+
+    def onFinishEvent(self):
+        self.activity = hours(clock() - self.starttime) + ' / ' +'Download Succeeded!'
+        self.cancelButton.SetLabel('Finish')
+        self.gauge.SetValue(0)
+        self.frame.SetTitle('%s - Upload - BitTorrent %s' % (self.filename, version))
+        try:
+            self.frame.SetIcon(self.finicon)
+        except:
+            pass
+        if self.taskbaricon:
+            self.frame.tbicon.SetIcon(self.finicon, "BitTorrent - Finished")
+        self.downRateText.SetLabel('')
+
+    def onFailEvent(self):
+        if not self.shuttingdown:
+            self.timeText.SetLabel(hours(clock() - self.starttime) + ' / ' +'Failed!')
+            self.activity = 'Failed!'
+            self.cancelButton.SetLabel('Close')
+            self.gauge.SetValue(0)
+            self.downRateText.SetLabel('')
+            self.setStatusIcon('startup')
+
+    def onErrorEvent(self, errormsg):
+        if errormsg[:2] == '  ':    # indent at least 2 spaces means a warning message
+            self.errorText.SetLabel(errormsg)
+            self.lastError = clock()
+        else:
+            self.errorText.SetLabel(strftime('ERROR (%x %X) -\n') + errormsg)
+            self.lastError = clock()
+
+
+    def chooseFile(self, default, size, saveas, dir):
+        f = Event()
+        bucket = [None]
+        self.invokeLater(self.onChooseFile, [default, bucket, f, size, dir, saveas])
+        f.wait()
+        return bucket[0]
+
+    def onChooseFile(self, default, bucket, f, size, dir, saveas):
+        if saveas == '':
+            if self.configfileargs['gui_default_savedir'] != '':
+                start_dir = self.configfileargs['gui_default_savedir']
+            else:
+                start_dir = self.configfileargs['last_saved']
+            if not isdir(start_dir):    # if it's not set properly
+                start_dir = '/'    # yes, this hack does work in Windows
+            if dir:
+                start_dir1 = start_dir
+                if isdir(join(start_dir,default)):
+                    start_dir = join(start_dir,default)
+                dl = wx.DirDialog(self.frame,
+                        'Choose a directory to save to, pick a partial download to resume',
+                        defaultPath = start_dir, style = wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
+            else:
+                dl = wx.FileDialog(self.frame,
+                        'Choose file to save as, pick a partial download to resume', 
+                        defaultDir = start_dir, defaultFile = default, wildcard = '*',
+                        style = wx.SAVE)
+
+            if dl.ShowModal() != wx.ID_OK:
+                f.set()
+                self.done(None)
+                return
+
+            d = dl.GetPath()
+            if d == start_dir:
+                d = start_dir1
+            bucket[0] = d
+            d1,d2 = split(d)
+            if d2 == default:
+                d = d1
+            self.configfile.WriteLastSaved(d)
+
+        else:
+            bucket[0] = saveas
+            default = basename(saveas)
+
+        self.onChooseFileDone(default, size)
+        f.set()
+
+    def ChooseFileDone(self, name, size):
+        self.invokeLater(self.onChooseFileDone, [name, size])
+
+    def onChooseFileDone(self, name, size):
+        self.torrentsize = size
+        lname = basename(name)
+        self.filename = lname
+        self.fileNameText.SetLabel('%s' % (lname))
+        self.fileSizeText.SetLabel('(%.2f MiB)' % (float(size) / (1 << 20)))
+        self.timeText.SetLabel(hours(clock() - self.starttime) + ' / ' + self.activity)
+        self.fileDestText.SetLabel(name)
+        self.frame.SetTitle(lname + '- BitTorrent ' + version)
+
+        minsize = self.fileNameText.GetBestSize()
+        if (not self.configfileargs['gui_stretchwindow'] or
+                            minsize.GetWidth() < self.addwidth):
+            minsize.SetWidth(self.addwidth)
+        self.fnsizer.SetMinSize (minsize)
+        minsize.SetHeight(self.fileSizeText.GetBestSize().GetHeight())
+        self.fnsizer2.SetMinSize (minsize)
+        minsize.SetWidth(minsize.GetWidth()+(self.FONT*8))
+        minsize.SetHeight(self.fileNameText.GetBestSize().GetHeight()+self.fileSizeText.GetBestSize().GetHeight())
+        minsize.SetHeight(2*self.errorText.GetBestSize().GetHeight())
+        self.errorTextSizer.SetMinSize(minsize)
+        self.topboxsizer.SetMinSize(minsize)
+
+        # Kludge to make details and about catch the event
+        self.frame.SetSize ((self.frame.GetSizeTuple()[0]+1, self.frame.GetSizeTuple()[1]+1))
+        self.frame.SetSize ((self.frame.GetSizeTuple()[0]-1, self.frame.GetSizeTuple()[1]-1))
+        self.colSizer.Fit(self.frame)
+        self.frame.Layout()
+        self.frame.Refresh()
+
+    def newpath(self, path):
+        self.invokeLater(self.onNewpath, [path])
+
+    def onNewpath(self, path):
+        self.fileDestText.SetLabel(path)
+
+    def pause(self, event):
+        self.invokeLater(self.onPause)
+
+    def onPause(self):
+        if not self.dow:
+            return
+        if self.ispaused:
+            self.ispaused = False
+            self.pauseButton.SetLabel('Pause')
+            self.dow.Unpause()
+        else:
+            if self.dow.Pause():
+                self.ispaused = True
+                self.pauseButton.SetLabel('Resume')
+                self.downRateText.SetLabel(' ')
+                self.upRateText.SetLabel(' ')
+                self.setStatusIcon('startup')
+
+    def done(self, event):
+        self.uiflag.set()
+        self.flag.set()
+        self.shuttingdown = True
+
+        try:
+            self.frame.tbicon.RemoveIcon()
+        except:
+            pass
+        try:
+            self.frame.tbicon.Destroy()
+        except:
+            pass
+        try:
+            self.detailBox.Close()
+        except:
+            self.detailBox = None
+        try:
+            self.aboutBox.Close()
+        except:
+            self.aboutBox = None
+        try:
+            self.creditsBox.Close()
+        except:
+            self.creditsBox = None
+        try:
+            self.advBox.Close()
+        except:
+            self.advBox = None
+        try:
+            self.statusIconHelpBox.Close()
+        except:
+            self.statusIconHelpBox = None
+        try:
+            self.frame.RemoveIcon()
+        except:
+            pass
+
+        self.frame.Destroy()
+
+
+    def exception(self):
+        data = StringIO()
+        print_exc(file = data)
+        print data.getvalue()   # report exception here too
+        self.on_errorwindow(data.getvalue())
+
+    def errorwindow(self, err):
+        self.invokeLater(self.on_errorwindow,[err])
+
+    def on_errorwindow(self, err):
+        if self._errorwindow is None:
+            w = wx.Frame(None, -1, 'BITTORRENT ERROR', size = (1,1),
+                            style = wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)
+            panel = wx.Panel(w, -1)
+
+            sizer = wx.FlexGridSizer(cols = 1)
+            t = ( 'BitTorrent ' + version + '\n' +
+                  'OS: ' + sys.platform + '\n' +
+                  'Python version: ' + sys.version + '\n' +
+                  'wx.Windows version: ' + wx.VERSION_STRING + '\n' )
+            try:
+                t += 'Psyco version: ' + hex(psyco.__version__)[2:] + '\n'
+            except:
+                pass
+            try:
+                t += 'Allocation method: ' + self.config['alloc_type']
+                if self.dow.storagewrapper.bgalloc_active:
+                    t += '*'
+                t += '\n'
+            except:
+                pass
+            sizer.Add(wx.TextCtrl(panel, -1, t + '\n' + err,
+                                size = (500,300), style = wx.TE_READONLY|wx.TE_MULTILINE))
+
+            sizer.Add(wx.StaticText(panel, -1,
+                    '\nHelp us iron out the bugs in the engine!'))
+            linkMail = wx.StaticText(panel, -1,
+                'Please report this error to '+report_email)
+            linkMail.SetFont(wx.Font(self.FONT, wx.DEFAULT, wx.NORMAL, wx.NORMAL, True))
+            linkMail.SetForegroundColour('Blue')
+            sizer.Add(linkMail)
+
+            def maillink(self):
+                Thread(target = open_new("mailto:" + report_email
+                                         + "?subject=autobugreport")).start()
+            wx.EVT_LEFT_DOWN(linkMail, maillink)
+
+            border = wx.BoxSizer(wx.HORIZONTAL)
+            border.Add(sizer, 1, wx.EXPAND | wx.ALL, 4)
+
+            panel.SetSizer(border)
+            panel.SetAutoLayout(True)
+
+            w.Show()
+            border.Fit(panel)
+            w.Fit()
+            self._errorwindow = w
+
+
+class btWxApp(wx.App):
+    def __init__(self, x, params):
+        self.params = params
+        wx.App.__init__(self, x)
+
+    def OnInit(self):
+        doneflag = Event()
+        self.configfile = configReader()
+        d = DownloadInfoFrame(doneflag, self.configfile)
+        self.SetTopWindow(d.frame)
+        if len(self.params) == 0:
+            b = wx.FileDialog (d.frame, 'Choose .torrent file to use',
+                        defaultDir = '', defaultFile = '', wildcard = '*.torrent',
+                        style = wx.OPEN)
+
+            if b.ShowModal() == wx.ID_OK:
+                self.params.append (b.GetPath())
+
+        thread = Thread(target = next, args = [self.params, d, doneflag, self.configfile])
+        thread.setDaemon(False)
+        thread.start()
+        return 1
+
+def run(params):
+    if WXPROFILER:
+        import profile, pstats
+        p = profile.Profile()
+        p.runcall(_run, params)
+        log = open('profile_data_wx.'+strftime('%y%m%d%H%M%S')+'.txt','a')
+        normalstdout = sys.stdout
+        sys.stdout = log
+#        pstats.Stats(p).strip_dirs().sort_stats('cumulative').print_stats()
+        pstats.Stats(p).strip_dirs().sort_stats('time').print_stats()
+        sys.stdout = normalstdout
+    else:
+        _run(params)
+        
+def _run(params):
+    app = btWxApp(0, params)
+    app.MainLoop()
+
+def next(params, d, doneflag, configfile):
+    if PROFILER:
+        import profile, pstats
+        p = profile.Profile()
+        p.runcall(_next, params, d, doneflag, configfile)
+        log = open('profile_data.'+strftime('%y%m%d%H%M%S')+'.txt','a')
+        normalstdout = sys.stdout
+        sys.stdout = log
+#        pstats.Stats(p).strip_dirs().sort_stats('cumulative').print_stats()
+        pstats.Stats(p).strip_dirs().sort_stats('time').print_stats()
+        sys.stdout = normalstdout
+    else:
+        _next(params, d, doneflag, configfile)
+
+def _next(params, d, doneflag, configfile):
+    err = False
+    try:
+        while 1:
+            try:            
+                config = parse_params(params, configfile.config)
+            except ValueError, e:
+                d.error('error: ' + str(e) + '\nrun with no args for parameter explanations')
+                break
+            if not config:
+                d.displayUsage(get_usage(presets = configfile.config))
+                break
+
+            myid = createPeerID()
+            seed(myid)
+            
+            rawserver = RawServer(doneflag, config['timeout_check_interval'],
+                                  config['timeout'], ipv6_enable = config['ipv6_enabled'],
+                                  failfunc = d.error, errorfunc = d.errorwindow)
+
+            upnp_type = 0
+            while True:
+                try:
+                    listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
+                                    config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
+                                    upnp = upnp_type, randomizer = config['random_port'])
+                    break
+                except socketerror, e:
+                    if upnp_type and e == UPnP_ERROR:
+                        d.error('WARNING: COULD NOT FORWARD VIA UPnP')
+                        upnp_type = 0
+                        continue
+                    d.error("Couldn't listen - " + str(e))
+                    d.failed()
+                    return
+            d.connection_stats = rawserver.get_stats()
+
+            response = get_response(config['responsefile'], config['url'], d.error)
+            if not response:
+                break
+
+            infohash = sha(bencode(response['info'])).digest()
+            
+            torrentdata = configfile.getTorrentData(infohash)
+            if torrentdata:
+                oldsave = torrentdata.get('saved as')
+                d.old_ratesettings = torrentdata.get('rate settings')
+                s = torrentdata.get('stats')
+                if s:
+                    d.old_upload = s['uploaded']
+                    d.old_download = s['downloaded']
+            else:
+                oldsave = None
+
+            dow = BT1Download(d.updateStatus, d.finished, d.error, d.errorwindow, doneflag,
+                            config, response, infohash, myid, rawserver, listen_port,
+                            configfile.getConfigDir())
+            d.dow = dow
+
+            if config['gui_saveas_ask'] == 1:
+                oldsave = None
+            if oldsave:
+                if not dow.checkSaveLocation(oldsave):
+                    oldsave = None
+            if oldsave:
+                def choosefile(default, size, saveas, dir, oldsave = oldsave):
+                    d.ChooseFileDone(oldsave, size)
+                    return oldsave
+            elif config['gui_saveas_ask'] == 0:
+                def choosefile(default, size, saveas, dir,
+                               spot = config['gui_default_savedir']):
+                    spot = os.path.join(spot,default)
+                    d.ChooseFileDone(spot, size)
+                    return spot
+            else:
+                choosefile = d.chooseFile
+            savedas = dow.saveAs(choosefile, d.newpath)
+            if not savedas: 
+                break
+
+            if not dow.initFiles(old_style = True):
+                break
+            if not dow.startEngine():
+                dow.shutdown()
+                break
+            dow.startRerequester()
+            dow.autoStats()
+
+            if not dow.am_I_finished():
+                d.updateStatus(activity = 'connecting to peers')
+            rawserver.listen_forever(dow.getPortHandler())
+
+            ratesettings = {
+                    'rate setting': d.current_ratesetting,
+                    'max download rate': config['max_download_rate']
+                }
+            if d.current_ratesetting != 'automatic':
+                ratesettings['uploads'] = config['min_uploads']
+                ratesettings['max upload rate'] = config['max_upload_rate']
+            up, dn = dow.get_transfer_stats()
+            stats = {
+                    'uploaded': up + d.old_upload,
+                    'downloaded': dn + d.old_download
+                }
+            torrentdata = {
+                    'saved as': savedas,
+                    'rate settings': ratesettings,
+                    'stats': stats
+                }
+            dow.shutdown(torrentdata)
+            break
+    except:
+        err = True
+        data = StringIO()
+        print_exc(file = data)
+        print data.getvalue()   # report exception here too
+        d.errorwindow(data.getvalue())
+    try:
+        rawserver.shutdown()
+    except:
+        pass
+    if not d.fin:
+        d.failed()
+    if err:
+        sleep(3600*24*30)   # this will make the app stick in the task manager,
+                            # but so be it
+
+
+if __name__ == '__main__':
+    if argv[1:] == ['--version']:
+        print version
+        exit(0)
+    run(argv[1:])
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadheadless.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadheadless.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btdownloadheadless.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btdownloadheadless.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+
+# Written by Bram Cohen
+# see LICENSE.txt for license information
+
+from BitTornado import PSYCO
+if PSYCO.psyco:
+    try:
+        import psyco
+        assert psyco.__version__ >= 0x010100f0
+        psyco.full()
+    except:
+        pass
+    
+from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
+from BitTornado.RawServer import RawServer, UPnP_ERROR
+from random import seed
+from socket import error as socketerror
+from BitTornado.bencode import bencode
+from BitTornado.natpunch import UPnP_test
+from threading import Event
+from os.path import abspath
+from sys import argv, stdout
+import sys
+from sha import sha
+from time import strftime
+from BitTornado.clock import clock
+from BitTornado import createPeerID, version
+from BitTornado.ConfigDir import ConfigDir
+
+assert sys.version >= '2', "Install Python 2.0 or greater"
+try:
+    True
+except:
+    True = 1
+    False = 0
+
+PROFILER = False
+
+def hours(n):
+    if n == 0:
+        return 'complete!'
+    try:
+        n = int(n)
+        assert n >= 0 and n < 5184000  # 60 days
+    except:
+        return '<unknown>'
+    m, s = divmod(n, 60)
+    h, m = divmod(m, 60)
+    if h > 0:
+        return '%d hour %02d min %02d sec' % (h, m, s)
+    else:
+        return '%d min %02d sec' % (m, s)
+
+class HeadlessDisplayer:
+    def __init__(self):
+        self.done = False
+        self.file = ''
+        self.percentDone = ''
+        self.timeEst = ''
+        self.downloadTo = ''
+        self.downRate = ''
+        self.upRate = ''
+        self.shareRating = ''
+        self.seedStatus = ''
+        self.peerStatus = ''
+        self.errors = []
+        self.last_update_time = -1
+
+    def finished(self):
+        self.done = True
+        self.percentDone = '100'
+        self.timeEst = 'Download Succeeded!'
+        self.downRate = ''
+        self.display()
+
+    def failed(self, errormsg = None):
+        self.done = True
+        self.percentDone = '0'
+        self.timeEst = 'Download Failed!'
+        self.downRate = ''
+        if errormsg:
+            self.errors.append(errormsg)
+        self.display()
+
+    def error(self, errormsg):
+        self.errors.append(errormsg)
+        self.display()
+
+    def display(self, dpflag = Event(), fractionDone = None, timeEst = None, 
+            downRate = None, upRate = None, activity = None,
+            statistics = None,  **kws):
+        if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
+            return
+        self.last_update_time = clock()        
+        if fractionDone is not None:
+            self.percentDone = str(float(int(fractionDone * 1000)) / 10)
+        if timeEst is not None:
+            self.timeEst = hours(timeEst)
+        if activity is not None and not self.done:
+            self.timeEst = activity
+        if downRate is not None:
+            self.downRate = '%.1f kB/s' % (float(downRate) / (1 << 10))
+        if upRate is not None:
+            self.upRate = '%.1f kB/s' % (float(upRate) / (1 << 10))
+        if statistics is not None:
+           if (statistics.shareRating < 0) or (statistics.shareRating > 100):
+               self.shareRating = 'oo  (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
+           else:
+               self.shareRating = '%.3f  (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
+           if not self.done:
+              self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies))
+           else:
+              self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
+           self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
+        print '\n\n\n\n'
+        for err in self.errors:
+            print 'ERROR:\n' + err + '\n'
+        print 'saving:        ', self.file
+        print 'percent done:  ', self.percentDone
+        print 'time left:     ', self.timeEst
+        print 'download to:   ', self.downloadTo
+        print 'download rate: ', self.downRate
+        print 'upload rate:   ', self.upRate
+        print 'share rating:  ', self.shareRating
+        print 'seed status:   ', self.seedStatus
+        print 'peer status:   ', self.peerStatus
+        stdout.flush()
+        dpflag.set()        
+
+    def chooseFile(self, default, size, saveas, dir):
+        self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20))
+        if saveas != '':
+            default = saveas
+        self.downloadTo = abspath(default)
+        return default
+
+    def newpath(self, path):
+        self.downloadTo = path
+
+def run(params):
+    h = HeadlessDisplayer()
+    while 1:
+        configdir = ConfigDir('downloadheadless')
+        defaultsToIgnore = ['responsefile', 'url', 'priority']
+        configdir.setDefaults(defaults,defaultsToIgnore)
+        configdefaults = configdir.loadConfig()
+        defaults.append(('save_options',0,
+         "whether to save the current options as the new default configuration " +
+         "(only for btdownloadheadless.py)"))
+        try:
+            config = parse_params(params, configdefaults)
+        except ValueError, e:
+            print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
+            break
+        if not config:
+            print get_usage(defaults, 80, configdefaults)
+            break
+        if config['save_options']:
+            configdir.saveConfig(config)
+        configdir.deleteOldCacheData(config['expire_cache_data'])
+
+        myid = createPeerID()
+        seed(myid)
+        
+        doneflag = Event()
+        def disp_exception(text):
+            print text
+        rawserver = RawServer(doneflag, config['timeout_check_interval'],
+                              config['timeout'], ipv6_enable = config['ipv6_enabled'],
+                              failfunc = h.failed, errorfunc = disp_exception)
+        upnp_type = 0
+        while True:
+            try:
+                listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
+                                config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
+                                upnp = upnp_type, randomizer = config['random_port'])
+                break
+            except socketerror, e:
+                if upnp_type and e == UPnP_ERROR:
+                    print 'WARNING: COULD NOT FORWARD VIA UPnP'
+                    upnp_type = 0
+                    continue
+                print "error: Couldn't listen - " + str(e)
+                h.failed()
+                return
+
+        response = get_response(config['responsefile'], config['url'], h.error)
+        if not response:
+            break
+
+        infohash = sha(bencode(response['info'])).digest()
+
+        dow = BT1Download(h.display, h.finished, h.error, disp_exception, doneflag,
+                        config, response, infohash, myid, rawserver, listen_port,
+                        configdir)
+        
+        if not dow.saveAs(h.chooseFile, h.newpath):
+            break
+
+        if not dow.initFiles(old_style = True):
+            break
+        if not dow.startEngine():
+            dow.shutdown()
+            break
+        dow.startRerequester()
+        dow.autoStats()
+
+        if not dow.am_I_finished():
+            h.display(activity = 'connecting to peers')
+        rawserver.listen_forever(dow.getPortHandler())
+        h.display(activity = 'shutting down')
+        dow.shutdown()
+        break
+    try:
+        rawserver.shutdown()
+    except:
+        pass
+    if not h.done:
+        h.failed()
+
+if __name__ == '__main__':
+    if argv[1:] == ['--version']:
+        print version
+        sys.exit(0)
+
+    if PROFILER:
+        import profile, pstats
+        p = profile.Profile()
+        p.runcall(run, argv[1:])
+        log = open('profile_data.'+strftime('%y%m%d%H%M%S')+'.txt','a')
+        normalstdout = sys.stdout
+        sys.stdout = log
+#        pstats.Stats(p).strip_dirs().sort_stats('cumulative').print_stats()
+        pstats.Stats(p).strip_dirs().sort_stats('time').print_stats()
+        sys.stdout = normalstdout
+    else:
+        run(argv[1:])
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btrename.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btrename.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btrename.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btrename.py	2006-12-23 18:20:56.000000000 +0000
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+# Written by Henry 'Pi' James
+# see LICENSE.txt for license information
+
+from sys import *
+from os.path import *
+from sha import *
+from BitTornado.bencode import *
+
+NAME, EXT = splitext(basename(argv[0]))
+VERSION = '20021119'
+
+print '%s %s - change the suggested filename in a .torrent file' % (NAME, VERSION)
+print
+
+if len(argv) != 3:
+  print '%s file.torrent new.filename.ext' % argv[0]
+  print
+  exit(2) # common exit code for syntax error
+
+metainfo_file = open(argv[1], 'rb')
+metainfo = bdecode(metainfo_file.read())
+metainfo_file.close()
+print 'old filename: %s' % metainfo['info']['name']
+metainfo['info']['name'] = argv[2]
+print 'new filename: %s' % metainfo['info']['name']
+metainfo_file = open(argv[1], 'wb')
+metainfo_file.write(bencode(metainfo))
+metainfo_file.close
+print
+print 'done.'
+print
diff -pruN 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btshowmetainfo.py 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btshowmetainfo.py
--- 0.3.18-10/.pc/32_use_hashlib_for_sha.patch/btshowmetainfo.py	1970-01-01 00:00:00.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/32_use_hashlib_for_sha.patch/btshowmetainfo.py	2011-12-31 04:28:59.000000000 +0000
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+# Written by Henry 'Pi' James and Loring Holden
+# modified for multitracker display by John Hoffman
+# see LICENSE.txt for license information
+
+from sys import *
+from os.path import *
+from sha import *
+from BitTornado.bencode import *
+
+NAME, EXT = splitext(basename(argv[0]))
+VERSION = '20030621'
+
+print '%s %s - decode BitTorrent metainfo files' % (NAME, VERSION)
+print
+
+if len(argv) == 1:
+    print '%s file1.torrent file2.torrent file3.torrent ...' % argv[0]
+    print
+    exit(2) # common exit code for syntax error
+
+for metainfo_name in argv[1:]:
+    print 'metainfo file.: %s' % basename(metainfo_name)
+
+    metainfo_file = open(metainfo_name, 'rb')
+    metainfo = metainfo_file.read()
+    metainfo_file.close()
+    metainfo = bdecode(metainfo)
+    try:    
+        info = metainfo['info']
+        info_hash = sha(bencode(info))
+
+        print 'info hash.....: %s' % info_hash.hexdigest()
+        piece_length = info['piece length']
+        if info.has_key('length'):
+            # let's assume we just have a file
+            print 'file name.....: %s' % info['name']
+            file_length = info['length']
+            name ='file size.....:'
+        else:
+            # let's assume we have a directory structure
+            print 'directory name: %s' % info['name']
+            print 'files.........: '
+            file_length = 0;
+            for file in info['files']:
+                path = ''
+                for item in file['path']:
+                    if (path != ''):
+                       path = path + "/"
+                    path = path + item
+                print '   %s (%d)' % (path, file['length'])
+                file_length += file['length']
+                name ='archive size..:'
+        piece_number, last_piece_length = divmod(file_length, piece_length)
+        print '%s %i (%i * %i + %i)' \
+              % (name,file_length, piece_number, piece_length, last_piece_length)
+        if metainfo.has_key('announce'):
+            print 'announce url..: %s' % metainfo['announce']
+        else:
+            print '***WARNING*** - no announce key'
+        if metainfo.has_key('announce-list'):
+            list = []
+            for tier in metainfo['announce-list']:
+                for tracker in tier:
+                    list+=[tracker,',']
+                del list[-1]
+                list+=['|']
+            del list[-1]
+            liststring = ''
+            for i in list:
+                liststring+=i
+            print 'announce-list.: %s' % liststring
+        if metainfo.has_key('httpseeds'):
+            list = []
+            for seed in metainfo['httpseeds']:
+                list += [seed,'|']
+            del list[-1]
+            liststring = ''
+            for i in list:
+                liststring+=i
+            print 'http seeds....: %s' % liststring
+        if metainfo.has_key('comment'):
+            print 'comment.......: %s' % metainfo['comment']
+
+    except:
+        print '***ERROR*** - metainfo out of spec'
diff -pruN 0.3.18-10/.pc/applied-patches 0.3.18-10ubuntu3/.pc/applied-patches
--- 0.3.18-10/.pc/applied-patches	2011-12-31 04:28:59.000000000 +0000
+++ 0.3.18-10ubuntu3/.pc/applied-patches	2011-12-31 04:28:59.000000000 +0000
@@ -25,3 +25,4 @@
 29_fix_urandom_error.dpatch
 30_announce_list_only_torrents.dpatch
 31_fix_for_compact_reqd_off.dpatch
+32_use_hashlib_for_sha.patch

