Common subdirectories: ./BitTorrent and ../BitTorrent-3.2.1b-experimental/BitTorrent
diff -u ./btcompletedir.py ../BitTorrent-3.2.1b-experimental/btcompletedir.py
--- ./btcompletedir.py	2003-03-17 11:26:39.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btcompletedir.py	2003-04-11 11:33:36.000000000 -0700
@@ -13,7 +13,7 @@
 def dummy(x):
     pass
 
-def completedir(dir, url, flag = Event(), vc = dummy, fc = dummy, piece_length = None):
+def completedir(dir, url, flag = Event(), vc = dummy, fc = dummy, piece_len_pow2 = None):
     files = listdir(dir)
     files.sort()
     ext = '.torrent'
@@ -36,7 +36,7 @@
         try:
             t = split(i)[-1] 
             if t not in ignore and t[0] != '.':
-                make_meta_file(i, url, flag = flag, progress = callback, progress_percent=0, piece_length = piece_length)
+                make_meta_file(i, url, flag = flag, progress = callback, progress_percent=0, piece_len_exp = piece_len_pow2)
         except ValueError:
             print_exc()
 
diff -u ./btdownloadcurses.py ../BitTorrent-3.2.1b-experimental/btdownloadcurses.py
--- ./btdownloadcurses.py	2003-03-27 21:14:08.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btdownloadcurses.py	2003-04-11 18:06:48.000000000 -0700
@@ -68,6 +68,9 @@
         self.downloadTo = ''
         self.downRate = '---'
         self.upRate = '---'
+        self.shareRating = ''
+        self.seedStatus = ''
+        self.peerStatus = ''
         self.errors = []
         self.globalerrlist = mainerrlist
 
@@ -89,7 +92,8 @@
         self.display()
 
     def display(self, fractionDone = None, timeEst = None,
-            downRate = None, upRate = None, activity = None):
+            downRate = None, upRate = None, activity = None,
+            statistics = None, **kws):
         if activity is not None and not self.done:
             self.activity = activity
         elif timeEst is not None:
@@ -104,6 +108,16 @@
             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))
 
         fieldwin.erase()
         fieldwin.addnstr(0, 0, self.file, fieldw, curses.A_BOLD)
@@ -114,12 +128,15 @@
         fieldwin.addnstr(4, 0, self.status, fieldw)
         fieldwin.addnstr(5, 0, self.downRate, fieldw)
         fieldwin.addnstr(6, 0, self.upRate, fieldw)
+        fieldwin.addnstr(7, 0, self.shareRating, fieldw)
+        fieldwin.addnstr(8, 0, self.seedStatus, fieldw)
+        fieldwin.addnstr(9, 0, self.peerStatus, fieldw)
 
         if self.errors:
             for i in range(len(self.errors)):
-                fieldwin.addnstr(7 + i, 0, self.errors[i], fieldw, curses.A_BOLD)
+                fieldwin.addnstr(10 + i, 0, self.errors[i], fieldw, curses.A_BOLD)
         else:
-            fieldwin.move(7, 0)
+            fieldwin.move(10, 0)
 
         curses.panel.update_panels()
         curses.doupdate()
@@ -151,7 +168,10 @@
     labelwin.addstr(4, 0, 'status:')
     labelwin.addstr(5, 0, 'dl speed:')
     labelwin.addstr(6, 0, 'ul speed:')
-    labelwin.addstr(7, 0, 'error(s):')
+    labelwin.addstr(7, 0, 'sharing:')
+    labelwin.addstr(8, 0, 'seeds:')
+    labelwin.addstr(9, 0, 'peers:')
+    labelwin.addstr(10, 0, 'error(s):')
     curses.panel.update_panels()
     curses.doupdate()
 
@@ -197,4 +217,3 @@
        print "These errors occurred during execution:"
        for error in mainerrlist:
           print error
-
diff -u ./btdownloadgui.py ../BitTorrent-3.2.1b-experimental/btdownloadgui.py
--- ./btdownloadgui.py	2003-03-27 20:35:58.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btdownloadgui.py	2003-04-11 20:22:08.000000000 -0700
@@ -1,19 +1,25 @@
 #!/usr/bin/env python
 
 # Written by Bram Cohen and Myers Carpenter
+# Modifications by various people
 # see LICENSE.txt for license information
 
 from sys import argv, version
 assert version >= '2', "Install Python 2.0 or greater"
 
 from BitTorrent import version
-from BitTorrent.download import download
+from BitTorrent.download import *
+from BitTorrent.ConnChoice import *
 from threading import Event, Thread
-from os.path import join
+from os.path import *
 from os import getcwd
 from wxPython.wx import *
 from time import strftime
 from webbrowser import open_new
+import re
+import sys
+true = 1
+false = 0
 
 def hours(n):
     if n == -1:
@@ -43,74 +49,521 @@
         self.args = args
         self.kwargs = kwargs
 
+def pr(event):
+    print 'augh!'
+
 class DownloadInfoFrame:
     def __init__(self, flag):
-        frame = wxFrame(None, -1, 'BitTorrent ' + version + ' download', size = wxSize(400, 250))
-        self.frame = frame
+        frame = wxFrame(None, -1, 'BitTorrent ' + version + ' download')
         self.flag = flag
         self.fin = false
+        self.aboutBox = None
+        self.detailBox = None
+        self.creditsBox = None
+        self.spinlock = 0
+        if (sys.platform == 'win32'):
+            self.icon = wxIcon('icon_bt.ico', wxBITMAP_TYPE_ICO)
+        self.starttime = time ()
+
+        self.frame = frame
+        if (sys.platform == 'win32'):
+            self.frame.SetIcon(self.icon)
 
         panel = wxPanel(frame, -1)
         colSizer = wxFlexGridSizer(cols = 1, vgap = 3)
+        colSizer.AddGrowableCol (0)
+        colSizer.AddGrowableRow (6)
+        border = wxBoxSizer(wxHORIZONTAL)
+        border.Add(colSizer, 1, wxEXPAND | wxALL, 4)
+        panel.SetSizer(border)
+        panel.SetAutoLayout(true)
 
         fnsizer = wxBoxSizer(wxHORIZONTAL)
-
-        self.fileNameText = wxStaticText(panel, -1, '', style = wxALIGN_LEFT)
-        fnsizer.Add(self.fileNameText, 1, wxALIGN_BOTTOM)
-        self.aboutText = wxStaticText(panel, -1, 'about', style = wxALIGN_RIGHT)
-        self.aboutText.SetForegroundColour('Blue')
-        self.aboutText.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, true))
-        fnsizer.Add(self.aboutText, 0, wxEXPAND)
+        fileNameText = wxStaticText(panel, -1, '', style = wxALIGN_LEFT)
+        fileNameText.SetFont(wxFont(12, wxNORMAL, wxNORMAL, wxNORMAL, false))
+        fnsizer.Add(fileNameText, 1, wxALIGN_BOTTOM|wxEXPAND)
+
+        fileDetails = wxStaticText(panel, -1, 'Details', style = wxALIGN_RIGHT)
+        fileDetails.SetFont(wxFont(12, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        fileDetails.SetForegroundColour('Blue')
+
+        fnsizer.Add(fileDetails, 0, wxALIGN_BOTTOM)                                     
+
+        fnsizer.Add(wxStaticText(panel, -1, '  ', style = wxALIGN_RIGHT))
+
+        aboutText = wxStaticText(panel, -1, 'About', style = wxALIGN_RIGHT)
+        aboutText.SetForegroundColour('Blue')
+        aboutText.SetFont(wxFont(12, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        fnsizer.Add(aboutText, 0, wxEXPAND)
+        self.fileNameText = fileNameText
+        self.fnsizer = fnsizer
         colSizer.Add(fnsizer, 0, wxEXPAND)
 
         self.gauge = wxGauge(panel, -1, range = 1000, style = wxGA_SMOOTH)
         colSizer.Add(self.gauge, 0, wxEXPAND)
 
-        gridSizer = wxFlexGridSizer(cols = 2, vgap = 3, hgap = 8)
-        
-        gridSizer.Add(wxStaticText(panel, -1, 'Estimated time left:'))
-        self.timeEstText = wxStaticText(panel, -1, '')
-        gridSizer.Add(self.timeEstText, 0, wxEXPAND)
+        timeSizer = wxFlexGridSizer(cols = 2)
+        timeSizer.Add(wxStaticText(panel, -1, 'Time elapsed / estimated : '))
+        self.timeText = wxStaticText(panel, -1, '                                  ')
+        timeSizer.Add(self.timeText)
+        timeSizer.AddGrowableCol(1)
+        colSizer.Add(timeSizer)
 
-        gridSizer.Add(wxStaticText(panel, -1, 'Download to:'))
+        destSizer = wxFlexGridSizer(cols = 2, hgap = 8)
+        destSizer.Add(wxStaticText(panel, -1, 'Download to:'))
         self.fileDestText = wxStaticText(panel, -1, '')
-        gridSizer.Add(self.fileDestText, 0, wxEXPAND)
-
-        gridSizer.Add(wxStaticText(panel, -1, 'Download rate:'))
-        self.downRateText = wxStaticText(panel, -1, '')
-        gridSizer.Add(self.downRateText, 0, wxEXPAND)
-
-        gridSizer.Add(wxStaticText(panel, -1, 'Upload rate:'))
-        self.upRateText = wxStaticText(panel, -1, '')
-        gridSizer.Add(self.upRateText, 0, wxEXPAND)
-        gridSizer.AddGrowableCol(1)
-
-        colSizer.Add(gridSizer, 0, wxEXPAND)
+        destSizer.Add(self.fileDestText, flag = wxEXPAND)
+        destSizer.AddGrowableCol(1)
+        colSizer.Add(destSizer, flag = wxEXPAND)
+
+        statSizer = wxFlexGridSizer(cols = 3, hgap = 8)
+
+        self.ratesSizer = wxFlexGridSizer(cols = 2)
+        self.infoSizer = wxFlexGridSizer(cols = 2)
+
+        self.ratesSizer.Add(wxStaticText(panel, -1, 'Download rate: '))
+        self.downRateText = wxStaticText(panel, -1, '   0 kB/s')
+        self.ratesSizer.Add(self.downRateText, flag = wxEXPAND)
+
+        self.infoSizer.Add(wxStaticText(panel, -1, 'Downloaded: '))
+        self.downText = wxStaticText(panel, -1, '    0.00 MiB')
+        self.infoSizer.Add(self.downText, flag = wxEXPAND)
+
+        self.ratesSizer.Add(wxStaticText(panel, -1, 'Upload rate: '))
+        self.upRateText = wxStaticText(panel, -1, '   0 kB/s')
+        self.ratesSizer.Add(self.upRateText, flag = wxEXPAND)
+
+        self.infoSizer.Add(wxStaticText(panel, -1, 'Uploaded: '))
+        self.upText = wxStaticText(panel, -1, '    0.00 MiB')
+        self.infoSizer.Add(self.upText, flag = wxEXPAND)
+
+        shareSizer = wxFlexGridSizer(cols = 2, hgap = 8)
+        shareSizer.Add(wxStaticText(panel, -1, 'Share rating:'))
+        self.shareRatingText = wxStaticText(panel, -1, '')
+        shareSizer.AddGrowableCol(1)
+        shareSizer.Add(self.shareRatingText, flag = wxEXPAND)
+
+        statSizer.Add(self.ratesSizer)
+        statSizer.Add(self.infoSizer)
+        statSizer.Add(shareSizer, flag = wxALIGN_CENTER_VERTICAL)
+        colSizer.Add (statSizer)
+
+        torrentSizer = wxFlexGridSizer(cols = 1)
+        self.peerStatusText = wxStaticText(panel, -1, '')
+        torrentSizer.Add(self.peerStatusText, 0, wxEXPAND)
+        self.seedStatusText = wxStaticText(panel, -1, '')
+        torrentSizer.Add(self.seedStatusText, 0, wxEXPAND)
+        torrentSizer.AddGrowableCol(0)
+        colSizer.Add(torrentSizer, 0, wxEXPAND)
 
         self.errorText = wxStaticText(panel, -1, '', style = wxALIGN_LEFT)
         self.errorText.SetForegroundColour('Red')
         colSizer.Add(self.errorText, 0, wxEXPAND)
         self.cancelButton = wxButton(panel, -1, 'Cancel')
         colSizer.Add(self.cancelButton, 0, wxALIGN_CENTER)
+
+        # Setting options
+
+        slideSizer = wxFlexGridSizer(cols = 7, hgap = 0, vgap = 0)
+
+        # dropdown
+
+        slideSizer.Add (wxStaticText(panel, -1, 'Settings for '), 0, wxALIGN_LEFT)
+        self.connChoice = wxChoice (panel, -1, (-1, -1), (90, -1), choices = connChoiceList)
+        self.connChoice.SetSelection(0)
+        slideSizer.Add (self.connChoice, 0, wxALIGN_CENTER)
+        slideSizer.Add (wxStaticText(panel, -1, ' Upload rate (kB/s) '), 0, wxALIGN_RIGHT)
+
+        # max upload rate
+
+        self.rateSpinner = wxSpinCtrl (panel, -1, "", (-1,-1), (50, -1))
+        self.rateSpinner.SetRange(0,5000)
+        self.rateSpinner.SetValue(0)
+        slideSizer.Add (self.rateSpinner, 0, wxALIGN_CENTER)
+
+        self.rateLowerText = wxStaticText(panel, -1, '  %5d' % (0))
+        self.rateUpperText = wxStaticText(panel, -1, '%5d' % (5000))
+        self.rateslider = wxSlider(panel, -1, 0, 0, 100, (-1, -1), (80, -1))
+
+        slideSizer.Add(self.rateLowerText, 0, wxALIGN_RIGHT)
+        slideSizer.Add(self.rateslider,    0, wxALIGN_CENTER)
+        slideSizer.Add(self.rateUpperText, 0, wxALIGN_LEFT)
+
+        # Placeholders in Layout
+
+        slideSizer.Add(wxStaticText(panel, -1, ''), 0, wxALIGN_LEFT)
+        slideSizer.Add(wxStaticText(panel, -1, ''), 0, wxALIGN_LEFT)
+
+        # max uploads
+
+        slideSizer.Add(wxStaticText(panel, -1, ' Max uploads '), 0, wxALIGN_RIGHT)
+        self.connSpinner = wxSpinCtrl (panel, -1, "", (-1,-1), (50, -1))
+        self.connSpinner.SetRange(4,100)
+        self.connSpinner.SetValue(4)
+        slideSizer.Add (self.connSpinner, 0, wxALIGN_CENTER)
+
+        self.connLowerText = wxStaticText(panel, -1, '  %5d' % (4))
+        self.connUpperText = wxStaticText(panel, -1, '%5d' % (100))
+        self.connslider = wxSlider(panel, -1, 4, 4, 100, (-1, -1), (80, -1))
+
+        slideSizer.Add(self.connLowerText, 0, wxALIGN_RIGHT)
+        slideSizer.Add(self.connslider,    0, wxALIGN_CENTER)
+        slideSizer.Add(self.connUpperText, 0, wxALIGN_LEFT)
+
+        colSizer.Add(slideSizer, 1, wxALL|wxALIGN_CENTER|wxEXPAND, 0)
+
+        colSizer.Add(wxStaticText(panel, -1, '0 kB/s means unlimited. Tip: your download rate is proportional to your upload rate'), 0, wxALIGN_CENTER)
+
+        EVT_LEFT_DOWN(aboutText, self.about)
+        EVT_LEFT_DOWN(fileDetails, self.details)
+        EVT_CLOSE(frame, self.done)
+        EVT_BUTTON(frame, self.cancelButton.GetId(), self.done)
+        EVT_INVOKE(frame, self.onInvoke)
+        EVT_SCROLL(self.rateslider, self.onRateScroll)
+        EVT_SCROLL(self.connslider, self.onConnScroll)
+        EVT_CHOICE(self.connChoice, -1, self.onConnChoice)
+        EVT_SPINCTRL(self.connSpinner, -1, self.onConnSpinner)
+        EVT_SPINCTRL(self.rateSpinner, -1, self.onRateSpinner)
+        if (sys.platform == 'win32'):
+            EVT_ICONIZE(self.frame,self.onIconify)
+
+        self.frame.Show()
+        border.Fit(panel)
+        self.frame.Fit()
+
+
+    def onIconify(self, evt):
+        if not hasattr(self.frame, "tbicon"):
+            self.frame.tbicon = wxTaskBarIcon()
+            self.frame.tbicon.SetIcon(self.icon, "BitTorrent")
+            # setup a taskbar icon, and catch some events from it
+            EVT_TASKBAR_LEFT_DCLICK(self.frame.tbicon, self.onTaskBarActivate)
+            EVT_TASKBAR_RIGHT_UP(self.frame.tbicon, self.onTaskBarMenu)
+            EVT_MENU(self.frame.tbicon, self.TBMENU_RESTORE, self.onTaskBarActivate)
+            EVT_MENU(self.frame.tbicon, self.TBMENU_CLOSE, self.done)
+        self.frame.Hide()
+
+    def onTaskBarActivate(self, evt):
+        if self.frame.IsIconized():
+            self.frame.Iconize(false)
+        if not self.frame.IsShown():
+            self.frame.Show(true)
+        self.frame.Raise()
+        if hasattr(self.frame, "tbicon"):
+            del self.frame.tbicon
+        return
+
+    TBMENU_RESTORE = 1000
+    TBMENU_CLOSE   = 1001
+
+    def onTaskBarMenu(self, evt):
+        menu = wxMenu()
+        menu.Append(self.TBMENU_RESTORE, "Restore BitTorrent")
+        menu.Append(self.TBMENU_CLOSE,   "Close")
+        self.frame.tbicon.PopupMenu(menu)
+        menu.Destroy()
+
+    def onRateScroll(self, event):
+        newValue = self.rateslider.GetValue()
+        if self.connChoice.GetSelection() == 0:
+          newValue = self.rateslider.GetValue() * 50
+        self.rateSpinner.SetValue (newValue)
+        self.dow.setUploadRate (newValue)
+
+    def onConnScroll(self, event):
+        self.connSpinner.SetValue (self.connslider.GetValue ())
+        self.dow.setConns (self.connslider.GetValue ())
+
+    def onRateSpinner(self, event):
+      if (self.spinlock == 0):
+        self.spinlock = 1
+        if self.connChoice.GetSelection() == 0:
+          newValue = self.rateSpinner.GetValue ()
+          if newValue != 0:
+            newValue /= 50
+            if self.rateSpinner.GetValue () % 10 != 9:
+              newValue += 1
+          self.rateslider.SetValue (newValue)
+          newValue *= 50
+          if (self.rateSpinner.GetValue () != newValue):
+            self.rateSpinner.SetValue (newValue)
+          self.dow.setUploadRate (self.rateSpinner.GetValue ())
+        else:
+          self.dow.setUploadRate (self.rateSpinner.GetValue ())
+          self.rateslider.SetValue (self.rateSpinner.GetValue ())
+        self.spinlock = 0
+
+    def onConnSpinner(self, event):
+        self.connslider.SetValue (self.connSpinner.GetValue())
+        self.dow.setConns (self.connSpinner.GetValue ())
+
+    def onConnChoice(self, event):
+        num = self.connChoice.GetSelection()
+        self.rateSpinner.SetValue (connChoices[num]['rate']['def'])
+        self.rateSpinner.SetRange (connChoices[num]['rate']['min'],
+                               connChoices[num]['rate']['max'])
+        self.rateslider.SetRange (
+            connChoices[num]['rate']['min']/connChoices[num]['rate'].get('div',1),
+            connChoices[num]['rate']['max']/connChoices[num]['rate'].get('div',1))
+        self.rateslider.SetValue (
+            connChoices[num]['rate']['def']/connChoices[num]['rate'].get('div',1))
+        self.rateLowerText.SetLabel ('  %d' % (connChoices[num]['rate']['min']))
+        self.rateUpperText.SetLabel ('%d' % (connChoices[num]['rate']['max']))
+        self.connSpinner.SetValue (connChoices[num]['conn']['def'])
+        self.connSpinner.SetRange (connChoices[num]['conn']['min'],
+                                   connChoices[num]['conn']['max'])
+        self.connslider.SetRange (connChoices[num]['conn']['min'],
+                                  connChoices[num]['conn']['max'])
+        self.connslider.SetValue (connChoices[num]['conn']['def'])
+        self.connLowerText.SetLabel ('  %d' % (connChoices[num]['conn']['min']))
+        self.connUpperText.SetLabel ('%d' % (connChoices[num]['conn']['max']))
+        self.onConnScroll (0)
+        self.onRateScroll (0)
+        self.dow.setInitiate (connChoices[num].get('initiate', 40))
+
+    def about(self, event):
+        if (self.aboutBox is not None):
+            try:
+                self.aboutBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.aboutBox = None
+
+        self.aboutBox = wxFrame(None, -1, 'About BitTorrent', size = (1,1))
+
+        panel = wxPanel(self.aboutBox, -1)
+        colSizer = wxFlexGridSizer(cols = 1, vgap = 3)
+
+        titleSizer = wxBoxSizer(wxHORIZONTAL)
+        aboutTitle = wxStaticText(panel, -1, 'BitTorrent ' + version)
+        aboutTitle.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, false))
+        titleSizer.Add (aboutTitle)
+        linkDonate = wxStaticText(panel, -1, 'Donate to Bram\n(link)', style = wxALIGN_RIGHT)
+        linkDonate.SetForegroundColour('Blue');
+        linkDonate.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        titleSizer.Add (linkDonate, 1, wxALIGN_BOTTOM&wxEXPAND)
+        colSizer.Add(titleSizer, 0, wxEXPAND)
+
+        colSizer.Add(wxStaticText(panel, -1, 'created by Bram Cohen, Copyright 2001-2003'))
+        credits = wxStaticText(panel, -1, 'full credits\n')
+        credits.SetForegroundColour('Blue');
+        credits.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        colSizer.Add(credits);
+
+        systemInformation = wxStaticText(panel, -1,
+          'exact Version String: ' + version + '\n'+
+          'Python version: ' + sys.version + '\n')
+        colSizer.Add(systemInformation)
+
+        babble1 = wxStaticText(panel, -1,
+         'This is an experimental, unofficial build of BitTorrent.\n' +
+         'It is Free Software under an MIT-Style license.')
+        babble2 = wxStaticText(panel, -1,'BitTorrent Homepage (link)')
+        babble2.SetForegroundColour('Blue');
+        babble2.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        babble4 = wxStaticText(panel, -1,'Experimental Client Homepage (link)')
+        babble4.SetForegroundColour('Blue');
+        babble4.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        babble6 = wxStaticText(panel, -1, 'License Terms (link)')
+        babble6.SetForegroundColour('Blue');
+        babble6.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        colSizer.Add (babble1)
+        colSizer.Add (babble2)
+        colSizer.Add (babble4)
+        colSizer.Add (babble6)
+
+        okButton = wxButton(panel, -1, 'Ok')
+        colSizer.Add(okButton, 0, wxALIGN_RIGHT)
+        colSizer.AddGrowableCol(0)
+
+        border = wxBoxSizer(wxHORIZONTAL)
+        border.Add(colSizer, 1, wxEXPAND | wxALL, 4)
+        panel.SetSizer(border)
+        panel.SetAutoLayout(true)
+
+        EVT_LEFT_DOWN(linkDonate, self.donatelink)
+        EVT_LEFT_DOWN(babble2, self.aboutlink)
+        EVT_LEFT_DOWN(babble4, self.explink)
+        EVT_LEFT_DOWN(babble6, self.licenselink)
+        EVT_LEFT_DOWN(credits, self.credits)
+
+        EVT_BUTTON(self.aboutBox, okButton.GetId(), self.closeAbout)
+
+        self.aboutBox.Show ()
+        border.Fit(panel)
+        self.aboutBox.Fit()
+
+    def details(self, event):
+        metainfo = self.dow.getResponse()
+        announce = metainfo['announce']
+        info = metainfo['info']
+        info_hash = sha(bencode(info))
+        piece_length = info['piece length']
+
+        if (self.detailBox is not None):
+            try:
+                self.detailBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.detailBox = None
+
+        self.detailBox = wxFrame(None, -1, 'Torrent Details ', size = wxSize(405,230))
+
+        panel = wxPanel(self.detailBox, -1, size = wxSize (400,220))
+        colSizer = wxFlexGridSizer(cols = 1, vgap = 3)
+
+        titleSizer = wxBoxSizer(wxHORIZONTAL)
+        aboutTitle = wxStaticText(panel, -1, 'Details about ' + self.filename)
+        aboutTitle.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, false))
+        titleSizer.Add (aboutTitle)
+        colSizer.Add (titleSizer)
+
+        detailSizer = wxFlexGridSizer(cols = 2, vgap = 3)
+        detailSizer.AddGrowableCol(1)
+        colSizer.Add (detailSizer)
+
+        if info.has_key('length'):
+            detailSizer.Add(wxStaticText(panel, -1, 'file name :'))
+            detailSizer.Add(wxStaticText(panel, -1, info['name']))
+            file_length = info['length']
+            name = "file size";
+        else:
+            detailSizer.Add(wxStaticText(panel, -1, 'directory name :'))
+            detailSizer.Add(wxStaticText(panel, -1, info['name']))
+            detailSizer.Add(wxStaticText(panel, -1, 'files :'))
+            file_length = 0;
+            for file in info['files']:
+                path = ''
+                for item in file['path']:
+                    if (path != ''):
+                        path = path + "/"
+                    path = path + item
+                detailSizer.Add(wxStaticText(panel, -1, '%s (%d)' % (path, file['length'])))
+                detailSizer.Add(wxStaticText(panel, -1, ''))
+                file_length += file['length']
+            name = 'archive size'
+            detailSizer.Add(wxStaticText(panel, -1, ''))
+
+        detailSizer.Add(wxStaticText(panel, -1, 'info_hash :'))
+        detailSizer.Add(wxTextCtrl(panel, -1, info_hash.hexdigest(), size = (300, -1), style = wxTE_READONLY))
+        piece_number, last_piece_length = divmod(file_length, piece_length)
+        detailSizer.Add(wxStaticText(panel, -1, name + ' : '))
+        detailSizer.Add(wxStaticText(panel, -1, '%i (%i * %i + %i)' % (file_length, piece_number, piece_length, last_piece_length)))
+        detailSizer.Add(wxStaticText(panel, -1, 'announce url : '))
+        detailSizer.Add(wxTextCtrl(panel, -1, announce, size = (300, -1), style = wxTE_READONLY))
+        detailSizer.Add(wxStaticText(panel, -1, 'likely tracker :'))
+        p = re.compile( '(.*/)[^/]+')
+        turl = p.sub (r'\1', announce)
+        trackerUrl = wxStaticText(panel, -1, turl)
+        trackerUrl.SetForegroundColour('Blue');
+        trackerUrl.SetFont(wxFont(-1, wxNORMAL, wxNORMAL, wxNORMAL, true))
+        detailSizer.Add(trackerUrl)
+
+        okButton = wxButton(panel, -1, 'Ok')
+        colSizer.Add(okButton, 0, wxALIGN_RIGHT)
+        colSizer.AddGrowableCol(0)
+
+        border = wxBoxSizer(wxHORIZONTAL)
+        border.Add(colSizer, 1, wxEXPAND | wxALL, 4)
+        panel.SetSizer(border)
+        panel.SetAutoLayout(true)
+
+        EVT_BUTTON(self.detailBox, okButton.GetId(), self.closeDetail)
+
+        def trackerurl(self):
+            Thread(target = open_new(turl)).start()
+
+        EVT_LEFT_DOWN(trackerUrl, trackerurl)
+
+        self.detailBox.Show ()
+        border.Fit(panel)
+        self.detailBox.Fit()
+
+    def credits(self, event):
+        if (self.creditsBox is not None):
+            try:
+                self.creditsBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.creditsBox = None
+
+        self.creditsBox = wxFrame(None, -1, 'Credits', size = (1,1))
+
+        panel = wxPanel(self.creditsBox, -1)
+        colSizer = wxFlexGridSizer(cols = 1, vgap = 3)
+
+        titleSizer = wxBoxSizer(wxHORIZONTAL)
+        aboutTitle = wxStaticText(panel, -1, 'Credits')
+        aboutTitle.SetFont(wxFont(14, wxNORMAL, wxNORMAL, wxNORMAL, false))
+        titleSizer.Add (aboutTitle)
+        colSizer.Add (titleSizer)
+        colSizer.Add (wxStaticText(panel, -1,
+          'The following people have all helped with this\n' +
+          'version of BitTorrent in some way (in no particular order) -\n'));
+        creditSizer = wxFlexGridSizer(cols = 3)
+        creditSizer.Add(wxStaticText(panel, -1,
+          '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'))
+        creditSizer.Add(wxStaticText(panel, -1, '  '))
+        creditSizer.Add(wxStaticText(panel, -1,
+          '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'))
+        colSizer.Add (creditSizer, flag = wxALIGN_CENTER_HORIZONTAL)
+        okButton = wxButton(panel, -1, 'Ok')
+        colSizer.Add(okButton, 0, wxALIGN_RIGHT)
         colSizer.AddGrowableCol(0)
-        colSizer.AddGrowableRow(3)
 
         border = wxBoxSizer(wxHORIZONTAL)
         border.Add(colSizer, 1, wxEXPAND | wxALL, 4)
         panel.SetSizer(border)
         panel.SetAutoLayout(true)
+
+        EVT_BUTTON(self.creditsBox, okButton.GetId(), self.closeCredits)
+
+        self.creditsBox.Show ()
+        border.Fit(panel)
+        self.creditsBox.Fit()
+
+    def closeAbout(self, event):
+        self.aboutBox.Close ()
+
+    def closeCredits(self, event):
+        self.creditsBox.Close ()
         
-        EVT_LEFT_DOWN(self.aboutText, self.donate)
-        EVT_CLOSE(frame, self.done)
-        EVT_BUTTON(frame, self.cancelButton.GetId(), self.done)
-        EVT_INVOKE(frame, self.onInvoke)
-        self.frame.Show()
+    def closeDetail(self, event):
+        self.detailBox.Close ()
+
+    def donatelink(self, event):
+        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()
 
-    def donate(self, event):
-        Thread(target = self.donate2).start()
+    def aboutlink(self, event):
+        Thread(target = open_new('http://bitconjurer.org/BitTorrent/')).start()
 
-    def donate2(self):
-        open_new('http://bitconjurer.org/BitTorrent/donate.html')
+    def explink(self, event):
+        Thread(target = open_new('http://ei.kefro.st/projects/btclient/')).start()
+
+    def licenselink(self, event):
+        Thread(target = open_new('http://ei.kefro.st/projects/btclient/LICENSE.TXT')).start()
 
     def onInvoke(self, event):
         if not self.flag.isSet():
@@ -122,21 +575,44 @@
 
     def updateStatus(self, fractionDone = None,
             timeEst = None, downRate = None, upRate = None,
-            activity=None):
-        self.invokeLater(self.onUpdateStatus, [fractionDone, timeEst, downRate, upRate, activity])
+            activity=None, up = None, down = None, statistics = None):
+        self.invokeLater(self.onUpdateStatus, [fractionDone, timeEst, downRate, upRate, activity, up, down, statistics])
 
-    def onUpdateStatus(self, fractionDone, timeEst, downRate, upRate, activity):
+    def onUpdateStatus(self, fractionDone, timeEst, downRate, upRate, activity, up, down, statistics):
         if fractionDone is not None and not self.fin:
             self.gauge.SetValue(int(fractionDone * 1000))
             self.frame.SetTitle('%d%% %s - BitTorrent %s' % (int(fractionDone*100), self.filename, version))
         if timeEst is not None:
-            self.timeEstText.SetLabel(hours(timeEst))
+            self.timeText.SetLabel(hours(time () - self.starttime) + ' / ' + hours(timeEst))
         if activity is not None and not self.fin:
-            self.timeEstText.SetLabel(activity)
+            self.timeText.SetLabel(hours(time () - self.starttime) + ' / ' + activity)
         if downRate is not None:
-            self.downRateText.SetLabel('%.0f kB/s' % (float(downRate) / (1 << 10)))
+            self.downRateText.SetLabel('%.0f kB/s' % (float(downRate) / (1000)))
         if upRate is not None:
-            self.upRateText.SetLabel('%.0f kB/s' % (float(upRate) / (1 << 10)))
+            self.upRateText.SetLabel('%.0f kB/s' % (float(upRate) / (1000)))
+        if down is not None:
+            self.downText.SetLabel('%.2f MiB' % (float(down) / (1 << 20)))
+        if up is not None:
+            self.upText.SetLabel('%.2f MiB' % (float(up) / (1 << 20)))
+        if hasattr(self.frame, "tbicon"):
+            icontext='BitTorrent %s' % self.filename
+            if fractionDone is not None and not self.fin:
+                icontext=icontext+' %d%%' % (int(fractionDone*100))
+            if upRate is not None:
+                icontext=icontext+' u:%.0f kB/s' %(float(upRate) / (1000))
+            if downRate is not None:
+                icontext=icontext+' u:%.0f kB/s' %(float(downRate) / (1000))
+            self.frame.tbicon.SetIcon(self.icon,icontext)
+        if statistics is not None:
+           if (statistics.shareRating < 0) or (statistics.shareRating > 1000):
+               self.shareRatingText.SetLabel('oo')
+           else:
+               self.shareRatingText.SetLabel('%.3f' % (statistics.shareRating))
+           if not self.fin:
+              self.seedStatusText.SetLabel('connected to %d seeds; also seeing %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies)))
+           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 %.1f kB/s)' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1000)))
 
     def finished(self):
         self.fin = true
@@ -150,14 +626,19 @@
         self.invokeLater(self.onErrorEvent, [errormsg])
 
     def onFinishEvent(self):
-        self.timeEstText.SetLabel('Download Succeeded!')
+        self.timeText.SetLabel(hours(time () - self.starttime) + ' / ' +'Download Succeeded!')
         self.cancelButton.SetLabel('Finish')
         self.gauge.SetValue(1000)
         self.frame.SetTitle('%s - Upload - BitTorrent %s' % (self.filename, version))
+        if (sys.platform == 'win32'):
+            self.icon = wxIcon('icon_done.ico', wxBITMAP_TYPE_ICO)
+            self.frame.SetIcon(self.icon)
+        if hasattr(self.frame, "tbicon"):
+                self.frame.tbicon.SetIcon(self.icon, "BitTorrent - Finished")
         self.downRateText.SetLabel('')
 
     def onFailEvent(self):
-        self.timeEstText.SetLabel('Failed!')
+        self.timeText.SetLabel(hours(time () - self.starttime) + ' / ' +'Failed!')
         self.cancelButton.SetLabel('Close')
         self.gauge.SetValue(0)
         self.downRateText.SetLabel('')
@@ -182,9 +663,12 @@
             self.done(None)
         else:
             bucket[0] = dl.GetPath()
-            self.fileNameText.SetLabel('%s (%.1f MB)' % (default, float(size) / (1 << 20)))
-            self.timeEstText.SetLabel('Starting up...')
+            self.fileNameText.SetLabel('%s (%.1f MiB)' % (default, float(size) / (1 << 20)))
+            self.timeText.SetLabel(hours(time () - self.starttime) + ' / ' +'Starting up...')
             self.fileDestText.SetLabel(dl.GetPath())
+            # 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.filename = default
             self.frame.SetTitle(default + '- BitTorrent ' + version)
         f.set()
@@ -193,9 +677,28 @@
         self.fileDestText.SetLabel(path)
 
     def done(self, event):
+        if hasattr(self.frame, "tbicon"):
+            self.frame.tbicon.Destroy()
+            del self.frame.tbicon
         self.flag.set()
+        if (self.detailBox is not None):
+            try:
+                self.detailBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.detailBox = None
+        if (self.aboutBox is not None):
+            try:
+                self.aboutBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.aboutBox = None
+        if (self.creditsBox is not None):
+            try:
+                self.creditsBox.Close ()
+            except wxPyDeadObjectError, e:
+                self.creditsBox = None
         self.frame.Destroy()
 
+
 class btWxApp(wxApp):
     def __init__(self, x, params):
         self.params = params
@@ -215,7 +718,9 @@
     app.MainLoop()
 
 def next(params, d, doneflag):
-    download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath)
+    dow = Download ()
+    d.dow = dow
+    dow.download(params, d.chooseFile, d.updateStatus, d.finished, d.error, doneflag, 100, d.newpath)
     if not d.fin:
         d.failed()
 
diff -u ./btdownloadheadless.py ../BitTorrent-3.2.1b-experimental/btdownloadheadless.py
--- ./btdownloadheadless.py	2003-03-17 11:26:39.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btdownloadheadless.py	2003-04-11 18:06:58.000000000 -0700
@@ -35,6 +35,9 @@
         self.downloadTo = ''
         self.downRate = ''
         self.upRate = ''
+        self.shareRating = ''
+        self.seedStatus = ''
+        self.peerStatus = ''
         self.errors = []
 
     def finished(self):
@@ -56,7 +59,8 @@
         self.display()
 
     def display(self, fractionDone = None, timeEst = None, 
-            downRate = None, upRate = None, activity = None):
+            downRate = None, upRate = None, activity = None,
+            statistics = None,  **kws):
         if fractionDone is not None:
             self.percentDone = str(float(int(fractionDone * 1000)) / 10)
         if timeEst is not None:
@@ -64,9 +68,19 @@
         if activity is not None and not self.done:
             self.timeEst = activity
         if downRate is not None:
-            self.downRate = '%.0f kB/s' % (float(downRate) / (1 << 10))
+            self.downRate = '%.1f kB/s' % (float(downRate) / (1 << 10))
         if upRate is not None:
-            self.upRate = '%.0f kB/s' % (float(upRate) / (1 << 10))
+            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'
@@ -76,6 +90,9 @@
         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()
 
     def chooseFile(self, default, size, saveas, dir):
diff -u ./btlaunchmanycurses.py ../BitTorrent-3.2.1b-experimental/btlaunchmanycurses.py
--- ./btlaunchmanycurses.py	2003-03-27 21:14:08.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btlaunchmanycurses.py	2003-04-11 18:07:14.000000000 -0700
@@ -1,37 +1,37 @@
 #!/usr/bin/env python
 
 # Written by Michael Janssen (jamuraa at base0 dot net)
-# heavily borrowed code from btlaunchmany.py written by Bram Cohen
+# originally heavily borrowed code from btlaunchmany.py by Bram Cohen
 # and btdownloadcurses.py written by Henry 'Pi' James
-# fmttime and fmtsize mercilessly stolen from btdownloadcurses. 0% of them are mine.
+# now not so much.
+# fmttime and fmtsize stolen from btdownloadcurses. 
 # see LICENSE.txt for license information
 
 from BitTorrent.download import download
-from threading import Thread, Event
+from threading import Thread, Event, RLock
 from os import listdir
 from os.path import abspath, join, exists
 from sys import argv, version, stdout, exit
 from time import sleep
-from signal import signal, SIGWINCH
+from signal import signal, SIGWINCH 
 import traceback
 
 assert version >= '2', "Install Python 2.0 or greater"
 
 def fmttime(n):
     if n == -1:
-       return 'download not progressing (file not being uploaded by others?)'
+        return 'download not progressing (no seeds?)'
     if n == 0:
-       return 'download complete!'
+        return 'download complete!'
     n = int(n)
     m, s = divmod(n, 60)
     h, m = divmod(m, 60)
     if h > 1000000:
-       return 'n/a'
+        return 'n/a'
     return 'finishing in %d:%02d:%02d' % (h, m, s)
 
-
 def fmtsize(n):
-    unit = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+    unit = [' B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
     i = 0
     if (n > 999):
         i = 1
@@ -48,222 +48,219 @@
 def dummy(*args, **kwargs):
     pass
 
-def winch_handler(signum, stackframe):
-    global scrwin, mainwin, mainwinw, headerwin, totalwin, statuswin
-    global scrpan, mainpan, headerpan, totalpan, statuspan
-    # SIGWINCH. Remake the frames!
-    ## Curses Trickery
-    curses.endwin()
-    # delete scrwin somehow?
-    scrwin.refresh()
-    scrwin = curses.newwin(0, 0, 0, 0)
-    scrh, scrw = scrwin.getmaxyx()
-    scrpan = curses.panel.new_panel(scrwin)
-    ### Curses Setup
-    scrh, scrw = scrwin.getmaxyx()
-    scrpan = curses.panel.new_panel(scrwin)
-    mainwinh = scrh - 5  # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin)
-    mainwinw = scrw - 4  # - 2 (bars) - 2 (spaces)
-    mainwiny = 2         # + 1 (bar) + 1 (titles)
-    mainwinx = 2         # + 1 (bar) + 1 (space)
-    # + 1 to all windows so we can write at mainwinw
-    mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx)
-    mainpan = curses.panel.new_panel(mainwin)
-
-    headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx)
-    headerpan = curses.panel.new_panel(headerwin)
-
-    totalwin = curses.newwin(1, mainwinw+1, scrh-3, mainwinx)
-    totalpan = curses.panel.new_panel(totalwin)
-
-    statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx)
-    statuspan = curses.panel.new_panel(statuswin)
-    mainwin.scrollok(0)
-    headerwin.scrollok(0)
-    totalwin.scrollok(0)
-    statuswin.addstr(0, 0, 'window resize: %s x %s' % (scrw, scrh))
-    statuswin.scrollok(0)
-    prepare_display()
-
+threads = {}
 ext = '.torrent'
-wininfo = {} 
+status = 'btlaunchmany starting..'
+filecheck = RLock()
 
-def runmany(d, params):
-    threads = []
+def dropdir_mainloop(d, params):
     deadfiles = []
-    try:
-        while 1:
-            files = listdir(d)
-            # new files
-            for file in files:
-                if file[-len(ext):] == ext:
-                    if file not in [x.getName() for x in threads] + deadfiles:
-                        wininfo[file] = {'basex': 2 * len(threads), 'killflag': Event()}
-                        statuswin.erase()
-                        statuswin.addnstr(0, 0,'new torrent detected: %s' % file, mainwinw)
-                        threads.append(Thread(target = SingleCursesDisplayer(join(d, file), params, file).download, name = file))
-                        threads[-1].start()
-            # gone files
-            for i in range(len(threads)):
-                try:
-                    threadname = threads[i].getName()
-                except IndexError:
-                    # raised when we delete a thread from earlier, so the last ones fall out of range
-                    continue
-                if not threads[i].isAlive():
-                    # died without "permission"
-                    deadfiles.append(threadname)
-                    statuswin.erase()
-                    statuswin.addnstr(0, 0,'torrent died: %s' % threadname, mainwinw)
-
-                    # rearrange remaining windows
-                    mainwin.addnstr(wininfo[threadname]['basex'], 0, ' ' * mainwinw, mainwinw)
-                    mainwin.addnstr(wininfo[threadname]['basex']+1, 0, ' ' * mainwinw, mainwinw)
-                    for _, win in wininfo.items():
-                        if win['basex'] > wininfo[threadname]['basex']:
-                            win['basex'] = win['basex'] - 2
-                    del wininfo[threadname]
-                    del threads[i]
-                elif threadname not in files:
-                    wininfo[threadname]['killflag'].set()
-                    # rearrange remaining windows
-                    mainwin.addnstr(wininfo[threadname]['basex'], 0, ' ' * mainwinw, mainwinw)
-                    mainwin.addnstr(wininfo[threadname]['basex']+1, 0, ' ' * mainwinw, mainwinw)
-                    for _, win in wininfo.items():
-                        if win['basex'] > wininfo[threadname]['basex']:
-                            win['basex'] = win['basex'] - 2
-                    threads[i].join()
-                    del wininfo[threadname]
-                    del threads[i]
-            # update the totals
-            totalup = 0
-            totaldown = 0
-            for info in wininfo.values():
-                totalup += info.get('uprate', 0)
-                totaldown += info.get('downrate', 0)
-            stringup = '%s/s' % fmtsize(totalup)
-            stringdown = '%s/s' % fmtsize(totaldown)
-
-            totalwin.addnstr(0, mainwinw-20, ' ' * 20, 20)
-            totalwin.addnstr(0, mainwinw-20 + (10 - len(stringdown)), stringdown, 10)
-            totalwin.addnstr(0, mainwinw-10 + (10 - len(stringup)), stringup, 10)
-
-            sleep(1)
-    except KeyboardInterrupt:
-        statuswin.erase()
-        statuswin.addnstr(0, 0,'^C caught.. cleaning up.. ', mainwinw)
+    global threads, status
+    while 1:
+        files = listdir(d)
+        # new files
+        for file in files: 
+            if file[-len(ext):] == ext:
+                if file not in threads.keys() + deadfiles:
+                    threads[file] = {'kill': Event(), 'try': 1}
+                    status = 'New torrent: %s' % file
+                    threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
+                    threads[file]['thread'].start()
+        # files with multiple tries
+        for file, threadinfo in threads.items():
+            if threadinfo.get('timeout') == 0:
+                # Zero seconds left, try and start the thing again.
+                threadinfo['try'] = threadinfo['try'] + 1
+                threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
+                threadinfo['thread'].start()
+                threadinfo['timeout'] = -1
+            elif threadinfo.get('timeout') > 0: 
+                # Decrement our counter by 1
+                threadinfo['timeout'] = threadinfo['timeout'] - 1
+            elif not threadinfo['thread'].isAlive():
+                # died without permission
+                if threadinfo.get('try') == 6: 
+                    # Died on the sixth try? You're dead.
+                    deadfiles.append(file)
+                    status = '%s died 6 times, added to dead list' % file
+                    del threads[file]
+                else:
+                    del threadinfo['thread']
+                    threadinfo['timeout'] = 10
+            # dealing with files that dissapear
+            if file not in files:
+                status = 'Gone torrent: %s' % file
+                threadinfo['kill'].set()
+                threadinfo['thread'].join()
+                del threads[file]
+        for file in deadfiles:
+            # if the file dissapears, remove it from our dead list
+            if file not in files: 
+                deadfiles.remove(file)
+        sleep(1)
+
+def display_thread(displaykiller):
+    interval = 0.1
+    global threads, status
+    while 1:
+        # display file info
+        if (displaykiller.isSet()): 
+            break
+        mainwin.erase()
+        winpos = 0
+        totalup = 0
+        totaldown = 0
+        for file, threadinfo in threads.items(): 
+            uprate = threadinfo.get('uprate', 0)
+            downrate = threadinfo.get('downrate', 0)
+            uptxt = '%s/s' % fmtsize(uprate)
+            downtxt = '%s/s' % fmtsize(downrate)
+            filesize = threadinfo.get('filesize', 'N/A')
+            mainwin.addnstr(winpos, 0, threadinfo.get('savefile', file), mainwinw - 28, curses.A_BOLD)
+            mainwin.addnstr(winpos, mainwinw - 28 + (8 - len(filesize)), filesize, 8)
+            mainwin.addnstr(winpos, mainwinw - 20 + (10 - len(downtxt)), downtxt, 10)
+            mainwin.addnstr(winpos, mainwinw - 10 + (10 - len(uptxt)), uptxt, 10)
+            winpos = winpos + 1
+            mainwin.addnstr(winpos, 0, '^--- ', 5) 
+            if threadinfo.get('timeout', 0) > 0:
+                mainwin.addnstr(winpos, 6, 'Try %d: died, retrying in %d' % (threadinfo.get('try', 1), threadinfo.get('timeout')), mainwinw - 5)
+            else:
+                mainwin.addnstr(winpos, 6, threadinfo.get('status',''), mainwinw - 5)
+            winpos = winpos + 1
+            totalup += uprate
+            totaldown += downrate
+        # display statusline
+        statuswin.erase() 
+        statuswin.addnstr(0, 0, status, mainwinw)
+        # display totals line
+        totaluptxt = '%s/s' % fmtsize(totaldown)
+        totaldowntxt = '%s/s' % fmtsize(totalup)
+        
+        totalwin.erase()
+        totalwin.addnstr(0, mainwinw - 27, 'Totals:', 7);
+        totalwin.addnstr(0, mainwinw - 20 + (10 - len(totaluptxt)), totaluptxt, 10)
+        totalwin.addnstr(0, mainwinw - 10 + (10 - len(totaldowntxt)), totaldowntxt, 10)
         curses.panel.update_panels()
         curses.doupdate()
-        for thread in threads: 
-            threadname = thread.getName()
-            statuswin.erase()
-            statuswin.addnstr(0, 0,'killing torrent %s' % threadname, mainwinw)
-            curses.panel.update_panels()
-            curses.doupdate()
-            wininfo[threadname]['killflag'].set()
-            thread.join()
-        statuswin.erase()
-        statuswin.addnstr(0, 0,'Bye Bye!', mainwinw)
-        curses.panel.update_panels()
-        curses.doupdate()
-
+        sleep(interval)
 
-class SingleCursesDisplayer: 
+class StatusUpdater:
     def __init__(self, file, params, name):
         self.file = file
         self.params = params
-        self.status = 'starting...'
-        self.doingdown = ''
-        self.doingup = ''
+        self.name = name
+        self.myinfo = threads[name]
         self.done = 0
-        self.downfile = ''
-        self.localfile = ''
-        self.fileSize = ''
-        self.activity = ''
-        self.myname = name
-        self.basex = wininfo[self.myname]['basex']
+        self.checking = 0
+        self.activity = 'starting up...'
         self.display()
+        self.myinfo['errors'] = []
 
-    def download(self):
-        download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, wininfo[self.myname]['killflag'], mainwinw)
-        statuswin.erase();
-        statuswin.addnstr(0, 0, '%s: torrent stopped' % self.localfile, mainwinw)
-        curses.panel.update_panels()
-        curses.doupdate()
+    def download(self): 
+        download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80)
+        status = 'Torrent %s stopped' % self.file
 
-    def finished(self):
+    def finished(self): 
         self.done = 1
-        self.doingdown = '--- KB/s'
+        self.myinfo['done'] = 1
         self.activity = 'download succeeded!'
         self.display(fractionDone = 1)
-   
+
     def err(self, msg): 
-        self.status = msg
+        self.myinfo['errors'].append(msg)
         self.display()
 
-    def failed(self):
-        self.activity = 'download failed!'
-        self.display()
- 
-    def choose(self, default, size, saveas, dir): 
-        self.downfile = default
-        self.fileSize = fmtsize(size)
-        if saveas == '':
+    def failed(self): 
+        self.activity = 'download failed!' 
+        self.display() 
+
+    def choose(self, default, size, saveas, dir):
+        global filecheck
+        self.myinfo['downfile'] = default
+        self.myinfo['filesize'] = fmtsize(size)
+        if saveas == '': 
             saveas = default
-        self.localfile = abspath(saveas)
+        # it asks me where I want to save it before checking the file.. 
+        if (exists(saveas)):
+            # file will get checked
+            while (not filecheck.acquire(blocking = 0) and not self.myinfo['kill'].isSet()):
+                self.myinfo['status'] = 'Waiting for disk check...'
+                sleep(0.1)
+            self.checking = 1
+        self.myinfo['savefile'] = saveas
         return saveas
 
-    def display(self, fractionDone = None, timeEst = None, downRate = None, upRate = None, activity = None):
-        if self.basex != wininfo[self.myname]['basex']: 
-            # leave nothing but blank space
-            mainwin.addnstr(self.basex, 0, ' ' * 1000, mainwinw)
-            mainwin.addnstr(self.basex+1, 0, ' ' * 1000, mainwinw)
-            self.basex = wininfo[self.myname]['basex']
-        if activity is not None and not self.done:
+    def display(self, fractionDone = None, timeEst = None, downRate = None, upRate = None, activity = None, statistics = None, **kws):
+        global filecheck, status
+        if activity is not None and not self.done: 
             self.activity = activity
-        elif timeEst is not None:
+        elif timeEst is not None: 
             self.activity = fmttime(timeEst)
-        if fractionDone is not None:
-            self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100)
+        if fractionDone is not None: 
+            self.myinfo['status'] = '%s (%.1f%%)' % (self.activity, fractionDone * 100)
+            if fractionDone == 1 and self.checking:
+                # we finished checking our files. 
+                filecheck.release()
+                self.checking = 0
         else:
-            self.status = self.activity
+            self.myinfo['status'] = self.activity
         if downRate is None: 
             downRate = 0
         if upRate is None:
             upRate = 0
-        wininfo[self.myname]['downrate'] = int(downRate)
-        wininfo[self.myname]['uprate'] = int(upRate)
-        self.doingdown = '%s/s' % fmtsize(int(downRate))
-        self.doingup = '%s/s' % fmtsize(int(upRate))
-   
-        # clear the stats section 
-        mainwin.addnstr(self.basex, 0, ' ' * mainwinw, mainwinw)
-        mainwin.addnstr(self.basex, 0, self.downfile, mainwinw - 28, curses.A_BOLD)
-        mainwin.addnstr(self.basex, mainwinw - 28 + (8 - len(self.fileSize)), self.fileSize, 8)
-        mainwin.addnstr(self.basex, mainwinw - 20 + (10 - len(self.doingdown)), self.doingdown, 10)
-        mainwin.addnstr(self.basex, mainwinw - 10 + (10 - len(self.doingup)), self.doingup, 10)
-        # clear the status bar first 
-        mainwin.addnstr(self.basex+1, 0, ' ' * mainwinw, mainwinw)
-        mainwin.addnstr(self.basex+1, 0, '^--- ', 5)
-        mainwin.addnstr(self.basex+1, 6, self.status, (mainwinw-1) - 5)
-        curses.panel.update_panels()
-        curses.doupdate()
+        self.myinfo['uprate'] = int(upRate)
+        self.myinfo['downrate'] = int(downRate)
 
-def prepare_display():
+def prepare_display(): 
     global mainwinw, scrwin, headerwin, totalwin
     scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
     headerwin.addnstr(0, 0, 'Filename', mainwinw - 25, curses.A_BOLD)
     headerwin.addnstr(0, mainwinw - 24, 'Size', 4);
     headerwin.addnstr(0, mainwinw - 18, 'Download', 8);
     headerwin.addnstr(0, mainwinw -  6, 'Upload', 6);
-
     totalwin.addnstr(0, mainwinw - 27, 'Totals:', 7);
-
     curses.panel.update_panels()
     curses.doupdate()
 
+def winch_handler(signum, stackframe): 
+    global scrwin, mainwin, mainwinw, headerwin, totalwin, statuswin
+    global scrpan, mainpan, headerpan, totalpan, statuspan
+    # SIGWINCH. Remake the frames!
+    ## Curses Trickery
+    curses.endwin()
+    # delete scrwin somehow?
+    scrwin.refresh()
+    scrwin = curses.newwin(0, 0, 0, 0)
+    scrh, scrw = scrwin.getmaxyx()
+    scrpan = curses.panel.new_panel(scrwin)
+    ### Curses Setup
+    scrh, scrw = scrwin.getmaxyx()
+    scrpan = curses.panel.new_panel(scrwin)
+    mainwinh = scrh - 5  # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin)
+    mainwinw = scrw - 4  # - 2 (bars) - 2 (spaces)
+    mainwiny = 2         # + 1 (bar) + 1 (titles)
+    mainwinx = 2         # + 1 (bar) + 1 (space)
+    # + 1 to all windows so we can write at mainwinw
+    mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx)
+    mainpan = curses.panel.new_panel(mainwin)
+
+    headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx)
+    headerpan = curses.panel.new_panel(headerwin)
+
+    totalwin = curses.newwin(1, mainwinw+1, scrh-3, mainwinx)
+    totalpan = curses.panel.new_panel(totalwin)
+
+    statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx)
+    statuspan = curses.panel.new_panel(statuswin)
+    mainwin.scrollok(0)
+    headerwin.scrollok(0)
+    totalwin.scrollok(0)
+    statuswin.addstr(0, 0, 'window resize: %s x %s' % (scrw, scrh))
+    statuswin.scrollok(0)
+    prepare_display()
+
 if __name__ == '__main__':
-    if (len(argv) < 2): 
+    if (len(argv) < 2):
         print """Usage: btlaunchmanycurses.py <directory> <global options>
   <directory> - directory to look for .torrent files (non-recursive)
   <global options> - options to be applied to all torrents (see btdownloadheadless.py)
@@ -272,7 +269,6 @@
     try: 
         import curses
         import curses.panel
-     
         scrwin = curses.initscr()
         curses.noecho()
         curses.cbreak()
@@ -280,39 +276,51 @@
         print 'Textmode GUI initialization failed, cannot proceed.'
         exit(-1)
     try:
-        try:
-            signal(SIGWINCH, winch_handler)
-            ### Curses Setup
-            scrh, scrw = scrwin.getmaxyx()
-            scrpan = curses.panel.new_panel(scrwin)
-            mainwinh = scrh - 5  # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin)
-            mainwinw = scrw - 4  # - 2 (bars) - 2 (spaces)
-            mainwiny = 2         # + 1 (bar) + 1 (titles)
-            mainwinx = 2         # + 1 (bar) + 1 (space)
-            # + 1 to all windows so we can write at mainwinw
-            mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx)
-            mainpan = curses.panel.new_panel(mainwin)
-
-            headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx)
-            headerpan = curses.panel.new_panel(headerwin)
-
-            totalwin = curses.newwin(1, mainwinw+1, scrh-3, mainwinx)
-            totalpan = curses.panel.new_panel(totalwin)
-
-            statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx)
-            statuspan = curses.panel.new_panel(statuswin)
-            mainwin.scrollok(0)
-            headerwin.scrollok(0)
-            totalwin.scrollok(0)
-            statuswin.addstr(0, 0, 'btlaunchmany started')
-            statuswin.scrollok(0)
-            prepare_display()
-            curses.panel.update_panels()
-            curses.doupdate()
-            runmany(argv[1], argv[2:])
-        finally:
-            curses.nocbreak()
-            curses.echo()
-            curses.endwin()
+        signal(SIGWINCH, winch_handler)
+        ### Curses Setup
+        scrh, scrw = scrwin.getmaxyx()
+        scrpan = curses.panel.new_panel(scrwin)
+        mainwinh = scrh - 5  # - 2 (bars) - 1 (debugwin) - 1 (borderwin) - 1 (totalwin)
+        mainwinw = scrw - 4  # - 2 (bars) - 2 (spaces)
+        mainwiny = 2         # + 1 (bar) + 1 (titles)
+        mainwinx = 2         # + 1 (bar) + 1 (space)
+        # + 1 to all windows so we can write at mainwinw
+        mainwin = curses.newwin(mainwinh, mainwinw+1, mainwiny, mainwinx)
+        mainpan = curses.panel.new_panel(mainwin)
+
+        headerwin = curses.newwin(1, mainwinw+1, 1, mainwinx)
+        headerpan = curses.panel.new_panel(headerwin)
+
+        totalwin = curses.newwin(1, mainwinw+1, scrh-3, mainwinx)
+        totalpan = curses.panel.new_panel(totalwin)
+
+        statuswin = curses.newwin(1, mainwinw+1, scrh-2, mainwinx)
+        statuspan = curses.panel.new_panel(statuswin)
+        mainwin.scrollok(0)
+        headerwin.scrollok(0)
+        totalwin.scrollok(0)
+        statuswin.addstr(0, 0, 'btlaunchmany started')
+        statuswin.scrollok(0)
+        prepare_display()
+        displaykiller = Event()
+        displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller])
+        displaythread.setDaemon(1)
+        displaythread.start()
+        dropdir_mainloop(argv[1], argv[2:])
+    except KeyboardInterrupt: 
+        status = '^C caught! Killing torrents..'
+        for file, threadinfo in threads.items(): 
+            status = 'Killing torrent %s' % file
+            threadinfo['kill'].set() 
+            threadinfo['thread'].join() 
+            del threads[file]
+        displaykiller.set()
+        displaythread.join()
+        curses.nocbreak()
+        curses.echo()
+        curses.endwin()
     except:
+        curses.nocbreak()
+        curses.echo()
+        curses.endwin()
         traceback.print_exc()
diff -u ./btlaunchmany.py ../BitTorrent-3.2.1b-experimental/btlaunchmany.py
--- ./btlaunchmany.py	2003-03-27 17:05:57.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btlaunchmany.py	2003-04-11 18:08:51.000000000 -0700
@@ -92,7 +92,7 @@
 
     def status(self, fractionDone = None,
             timeEst = None, downRate = None, upRate = None,
-            activity = None):
+            activity = None, statistics = None, **kws):
         if fractionDone is not None:
             newpercent = int(fractionDone*100)
             if newpercent != self.percentDone:
diff -u ./btmakemetafile.py ../BitTorrent-3.2.1b-experimental/btmakemetafile.py
--- ./btmakemetafile.py	2003-03-29 16:57:03.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/btmakemetafile.py	2003-04-11 11:35:08.000000000 -0700
@@ -14,10 +14,13 @@
 from BitTorrent.btformats import check_info
 from BitTorrent.parseargs import parseargs, formatDefinitions
 from threading import Event
+from time import asctime
 
 defaults = [
     ('piece_size_pow2', None, 18,
         "which power of 2 to set the piece size to"),
+    ('comment', None, '',
+        "optional human-readable comment to put in .torrent"),
     ]
 
 ignore = ['core', 'CVS'] # ignoring these files could be trouble
@@ -26,7 +29,7 @@
     pass
 
 def make_meta_file(file, url, piece_len_exp = 18, 
-        flag = Event(), progress = dummy, progress_percent=1):
+        flag = Event(), progress = dummy, progress_percent=1, comment = None):
     piece_length = 2 ** piece_len_exp
     a, b = split(file)
     if b == '':
@@ -38,7 +41,10 @@
         return
     check_info(info)
     h = open(f, 'wb')
-    h.write(bencode({'info': info, 'announce': strip(url)}))
+    data = {'info': info, 'announce': strip(url), 'creation date': asctime()}
+    if comment:
+        data['comment'] = comment
+    h.write(bencode(data))
     h.close()
 
 def calcsize(file):
@@ -138,7 +144,8 @@
     else:
         try:
             config, args = parseargs(argv[3:], defaults, 0, 0)
-            make_meta_file(argv[1], argv[2], config['piece_size_pow2'], progress = prog)
+            make_meta_file(argv[1], argv[2], config['piece_size_pow2'], progress = prog,
+                comment = config['comment'])
         except ValueError, e:
             print 'error: ' + str(e)
             print 'run with no args for parameter explanations'
Only in ../BitTorrent-3.2.1b-experimental/: build
diff -u ./credits.txt ../BitTorrent-3.2.1b-experimental/credits.txt
--- ./credits.txt	2003-03-27 19:05:46.000000000 -0800
+++ ../BitTorrent-3.2.1b-experimental/credits.txt	2003-04-11 11:53:25.000000000 -0700
@@ -23,3 +23,5 @@
 Loring Holden
 Robert Stone
 Michael Janssen
+Eike Frost
+Andrew Todd
Only in ../BitTorrent-3.2.1b-experimental/: CVS
Only in ../BitTorrent-3.2.1b-experimental/: docs-rescue.torrent
Only in .: FAQ.txt
Only in ../BitTorrent-3.2.1b-experimental/: icon_bt.ico
Only in ../BitTorrent-3.2.1b-experimental/: icon_done.ico
diff -u ./MANIFEST.in ../BitTorrent-3.2.1b-experimental/MANIFEST.in
--- ./MANIFEST.in	2002-07-14 11:50:52.000000000 -0700
+++ ../BitTorrent-3.2.1b-experimental/MANIFEST.in	2003-04-11 11:33:36.000000000 -0700
@@ -5,7 +5,6 @@
 include btdownloadlibrary.py
 include btdownloadheadless.py
 include btdownloadgui.py
-include btdownloadprefetched.py
 include btmakemetafile.py
 include bttrack.py
 include bttest.py
Common subdirectories: ./osx and ../BitTorrent-3.2.1b-experimental/osx
Only in .: todo.txt
