[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#488900: marked as done (bittorrent: Patch for four bugs)



Your message dated Fri, 18 Oct 2019 04:23:38 +0000
with message-id <E1iLJne-000Co3-DG@fasolo.debian.org>
and subject line Bug#936210: Removed package(s) from unstable
has caused the Debian Bug report #488900,
regarding bittorrent: Patch for four bugs
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
488900: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=488900
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: bittorrent
Version: 3.4.2-11.1
Severity: normal
Tags: patch


The attached patch fixes four bugs.  From my changelog entries:

bittorrent (3.4.2-11.2dfd06~005) unstable; urgency=low
	
  * Modify global upload throttle so that it uses full bandwidth, except
    for a small cushion for inactive torrents to start downloading

 -- Daniel Dickinson <cshore@fionavar.ca>  Mon, 30 Jun 2008 22:46:45 -0500	

bittorrent (3.4.2-11.2dfd05~015) unstable; urgency=low
	
  * Add global upload rate throttle that keeps a cushion for new
    sessions sessions under max to grow, but otherwise allocates full max
    global upload rate to sessions that want to upload at a rate greater
    than the per-session maximum upload rate (calculated as global
    maximum divided by number of active uploads) (Closes: #481276)
  * Fix deadfile handling and checking locking.  (Closes: #478758)
  * Fix exception handling (or rather lack thereof)
  * Comment a bunch of code
  * Change startup disk check to be one torrent at a time. (Closes: #482478)
  * Add outgoing port range limiting in order to play well with strict 
    firewalls.  (Closes: #481276)

 -- Daniel Dickinson <cshore@fionavar.ca>  Sun, 01 Jun 2008 02:09:55 -0500

-- System Information:
Debian Release: lenny/sid
  APT prefers testing
  APT policy: (500, 'testing'), (500, 'stable')
Architecture: i386 (i686)

Kernel: Linux 2.6.24-1-686 (SMP w/1 CPU core)
Locale: LANG=en_CA.UTF-8, LC_CTYPE=en_CA.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/bash
diff -Naur bittorrent_3.4.2-11.1/BitTorrent/Connecter.py bittorrent-3.4.2/BitTorrent/Connecter.py
--- bittorrent_3.4.2-11.1/BitTorrent/Connecter.py	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/BitTorrent/Connecter.py	2008-06-01 06:48:51.000000000 -0400
@@ -90,7 +90,7 @@
 
 class Connecter:
     def __init__(self, make_upload, downloader, choker, numpieces,
-            totalup, max_upload_rate = 0, sched = None):
+            totalup, max_upload_rate = 0, sched = None, global_cap = None):
         self.downloader = downloader
         self.make_upload = make_upload
         self.choker = choker
@@ -98,11 +98,26 @@
         self.max_upload_rate = max_upload_rate
         self.sched = sched
         self.totalup = totalup
+        self.global_cap = global_cap
         self.rate_capped = False
         self.connections = {}
 
     def _update_upload_rate(self, amount):
         self.totalup.update_rate(amount)
+        # If we have defined the global_cap dictionary, check if there is a
+        # global cap to enforce (and do so if necessary)
+        if self.global_cap:
+            global_max_upload_rate = self.global_cap['global_max_upload_rate']
+            global_rate = self.global_cap['global_rate']
+            global_slice = self.global_cap['global_slice']
+            # Only do global rate throttling if the global maximum and the 
+            # current global rate are not None (undefined)
+            if global_max_upload_rate != None and global_rate != None:
+                # If we have a global throttle limit
+                if global_max_upload_rate > 0:
+                    # Set the per-torrent maximum as calculated by the 
+                    # multi-downloader
+                    self.max_upload_rate = global_slice
         if self.max_upload_rate > 0 and self.totalup.get_rate_noupdate() > self.max_upload_rate:
             self.rate_capped = True
             self.sched(self._uncap, self.totalup.time_until_rate(self.max_upload_rate))
diff -Naur bittorrent_3.4.2-11.1/BitTorrent/download.py bittorrent-3.4.2/BitTorrent/download.py
--- bittorrent_3.4.2-11.1/BitTorrent/download.py	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/BitTorrent/download.py	2008-06-01 02:01:11.000000000 -0400
@@ -92,9 +92,13 @@
         "the number of uploads to fill out to with extra optimistic unchokes"),
     ('report_hash_failures', 0,
         "whether to inform the user that hash failures occur. They're non-fatal."),
+    ('min_outgoing_port', 1024,
+        "lowest port from which we are allowed to connect"),
+    ('max_outgoing_port', 65535,
+        "highest port from which we are allowed to connect"),
     ]
 
-def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols, pathFunc = None, paramfunc = None, spewflag = Event()):
+def download(params, filefunc, statusfunc, finfunc, errorfunc, doneflag, cols, pathFunc = None, paramfunc = None, spewflag = Event(), global_cap = None):
     if len(params) == 0:
         errorfunc('arguments are -\n' + formatDefinitions(defaults, cols))
         return
@@ -197,7 +201,7 @@
         doneflag.set()
         if reason is not None:
             errorfunc(reason)
-    rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc = errorfunc, maxconnects = config['max_allow_in'])
+    rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc = errorfunc, maxconnects = config['max_allow_in'], min_outgoing_port = config['min_outgoing_port'], max_outgoing_port = config['max_outgoing_port'] )
     try:
         try:
             storage = Storage(files, open, path.exists, path.getsize)
@@ -242,6 +246,13 @@
         errorfunc("Couldn't listen - " + str(e))
         return
 
+    if config['min_outgoing_port'] < 1024 or config['max_outgoing_port'] > 65535:
+        errorfunc("We can only connect to peers using ports between 1024 and 65535")
+        return
+    if config['min_outgoing_port'] > config['max_outgoing_port']:
+        errorfunc("max_outgoing_port less than min_outgoing_port; can't connect")
+        return
+
     choker = Choker(config['max_uploads'], rawserver.add_task, finflag.isSet, 
         config['min_uploads'])
     upmeasure = Measure(config['max_rate_period'], 
@@ -265,7 +276,7 @@
         len(pieces), downmeasure, config['snub_time'], 
         ratemeasure.data_came_in)
     connecter = Connecter(make_upload, downloader, choker,
-        len(pieces), upmeasure, config['max_upload_rate'] * 1024, rawserver.add_task)
+        len(pieces), upmeasure, config['max_upload_rate'] * 1024, rawserver.add_task, global_cap)
     infohash = sha(bencode(info)).digest()
     encoder = Encoder(connecter, rawserver, 
         myid, config['max_message_length'], rawserver.add_task, 
diff -Naur bittorrent_3.4.2-11.1/BitTorrent/RawServer.py bittorrent-3.4.2/BitTorrent/RawServer.py
--- bittorrent_3.4.2-11.1/BitTorrent/RawServer.py	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/BitTorrent/RawServer.py	2008-06-01 02:00:45.000000000 -0400
@@ -80,7 +80,7 @@
 
 class RawServer:
     def __init__(self, doneflag, timeout_check_interval, timeout, noisy = True,
-            errorfunc = default_error_handler, maxconnects = 55):
+            errorfunc = default_error_handler, maxconnects = 55, min_outgoing_port = 1024, max_outgoing_port = 65535):
         self.timeout_check_interval = timeout_check_interval
         self.timeout = timeout
         self.poll = poll()
@@ -92,6 +92,8 @@
         self.errorfunc = errorfunc
         self.maxconnects = maxconnects
         self.funcs = []
+        self.min_outgoing_port = min_outgoing_port
+        self.max_outgoing_port = max_outgoing_port
         self.unscheduled_tasks = []
         self.add_task(self.scan_for_timeouts, timeout_check_interval)
 
@@ -133,7 +135,14 @@
             sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 32)
         except:
             pass
-        sock.bind((self.bindaddr, 0))
+        for out_port in xrange(max_outgoing_port, min_outgoing_port - 1, -1):
+            was_bound = True
+            try:
+                sock.bind((self.bindaddr, out_port))
+            except:
+                was_bound = False
+            if was_bound:
+                break
         try:
             sock.connect_ex(dns)
         except socket.error:
diff -Naur bittorrent_3.4.2-11.1/btlaunchmany.py bittorrent-3.4.2/btlaunchmany.py
--- bittorrent_3.4.2-11.1/btlaunchmany.py	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/btlaunchmany.py	2008-07-01 18:10:02.000000000 -0400
@@ -12,134 +12,355 @@
 from threading import Thread, Event, Lock
 from os import listdir
 from os.path import abspath, join, exists, getsize
-from sys import argv, stdout, exit
+from sys import argv, stdout, exit, stderr
 from time import sleep
 import traceback
+from BitTorrent import Connecter
+import locale
+import time
 
 def dummy(*args, **kwargs):
     pass
 
 threads = {}
+global_max_upload_rate = 0
 ext = '.torrent'
 print 'btlaunchmany starting..'
-filecheck = Lock()
+logfile = stderr
+debug_level = 0
+display_interval = 1
+
+class Filecheck:
+    def __init__(self):
+        self.locked = False
+        self.lock = Lock()
+    def acquire(self, num):
+        self.locked = True
+        # Acquire a lock if possible, return success/failure of acquire
+        return self.lock.acquire(num)
+
+    def release(self):
+        # If we're locked
+        if self.locked:
+            # Release the lock
+            return self.lock.release()
+        else:
+            # Tell caller we weren't locked, which means logic is bad
+            return False
+
+filecheck = Filecheck()
+
+def log(mesg):
+    global logfile
+    date = time.strftime("%Y-%m-%d %H:%M:%S")
+    if logfile:
+        try:
+            logfile.write("%s btlaunchmany.py: %s\n" % (date, mesg))            
+        except EnvironmentError:
+            stderr.write("Error writing logfile")
+            stderr.flush()
+            sys.exit(1)
+        
+        logfile.flush()
 
 def dropdir_mainloop(d, params):
+    global filecheck
+    global global_max_upload_rate
     deadfiles = []
     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}
-                    print 'New torrent: %s' % file
-                    stdout.flush()
-                    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()
+        def start_torrent(trynum = 1):
+            # If we can obtain a lock (no other file is doing
+            # intial checking)
+            if filecheck.acquire(0):
+                # For this thread, set an event that will tell the
+                # thread when we want it to die, and the number of
+                # attempts we have made to start the thread 
+                threads[file]['kill'] = Event()
+                threads[file]['try'] = trynum
+                threads[file]['global_cap'] = { 'global_max_upload_rate': global_max_upload_rate, 'global_rate': 0, 'global_slice': 0 }
+                print 'New torrent: %s' % file
+                stdout.flush()
+                # Create a download (bittorrent) instance for the file
+                status_updater = StatusUpdater(join(d, file), params, file, 1, threads[file]['global_cap'])
+                # Create a thread from the instance
+                threads[file]['thread'] = Thread(target = status_updater.download, name = file)
+                # And start it
+                threads[file]['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 it was checking the file, it isn't anymore.
-                if threadinfo.get('checking', None):
-                    filecheck.release()
-                if threadinfo.get('try') == 6: 
-                    # Died on the sixth try? You're dead.
-                    deadfiles.append(file)
-                    print '%s died 6 times, added to dead list' % fil
+            # If we can't obtain a lock
+            else:
+                threadinfo['status'] = 'disk wait'
+
+        try:
+            # Find files in download directory
+            files = listdir(d)
+            # For each file in the download directory
+            for file in files: 
+                # for a .torrent
+                if file[-len(ext):] == ext:                
+                    # If file does not already have a table entry and it 
+                    # didn't fail to start before
+                    if file not in threads.keys() + deadfiles:
+                        # Create a table entry that has never tried and which
+                        # is done wait for retry (i.e. will start immediately)
+                        threads[file] = { 'try': 0 , 'timeout': 0 }
+                        threads[file]['global_cap'] = { 'global_max_upload_rate': global_max_upload_rate, 'global_rate': 0, 'global_slice': 0 }
+            # For existing table entries
+            for file, threadinfo in threads.items():
+                # If we're done waiting for a retry (zero seconds left)
+                if threadinfo.get('timeout') == 0:
+                    start_torrent(threadinfo['try'] + 1)
+                # Otherwise if there is a timeout
+                elif threadinfo.get('timeout') > 0: 
+                    # Update out timeout (reduce time left to wait)
+                    threadinfo['timeout'] = threadinfo['timeout'] - 1
+                # Otherwise, if the thread is dead
+                elif not threadinfo['thread'].isAlive():
+                    # died without permission
+                    # if it was checking the file, it isn't anymore.
+                    if threadinfo.get('checking', None):
+                        # Relase the lock
+                        filecheck.release()
+                        if threadinfo.get('try') == 6: 
+                            # Died on the sixth try? You're dead.
+                            deadfiles.append(file)
+                            print '%s died 6 times, added to dead list' % file
+                            stdout.flush()
+                            # Don't try again (remove table entry)
+                            del threads[file]
+                        else:
+                            # Remove the thread information (not table)
+                            del threadinfo['thread']
+                            # And set timeout so it will try again in 120s
+                            threadinfo['timeout'] = 120
+                # dealing with files that dissapear
+                if file not in files:
+                    print 'Torrent file dissapeared, killing %s' % file
                     stdout.flush()
-                    del threads[file]
+                    # If thread was active
+                    if threadinfo.get('timeout', -1) == -1:
+                        # Kill the thread, by setting even that tells it to die
+                        threadinfo['kill'].set()
+                        if threadinfo['thread']:
+                            # And attach to thread until it stops
+                            threadinfo['thread'].join()
+                    # if this thread was filechecking
+                    if threadinfo.get('checking', None): 
+                        # Release lock
+                        filecheck.release()
+                        # Remove table etnry
+                        del threads[file]
+                        # Check the list of dead files
+                        for file in deadfiles:
+                            # If the file no longer exists in the download dir
+                            if file not in files: 
+                                # Remove that file from the list of dead files
+                                deadfiles.remove(file)
+        # If there is a file or OS error
+        except EnvironmentError:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
+        sleep(1)
+
+def calc_slice(calckiller):
+    """Calculate bandwith cap for each active torrent"""
+    global threads
+    global global_max_upload_rate
+    debug_sec = 0
+
+    # Keep going until main program terminates this thread
+    while not calckiller.isSet():
+        try:
+            # Base calculation only on torrents that actually have traffic
+            active_uploads = 0
+            # Total upload rate
+            totalup = 0
+
+            # If there is an upload cap and there are torrents to cap
+            if global_max_upload_rate > 0 and len(threads) > 0:
+                # For each torrent
+                for file, threadinfo in threads.items():
+                    uprate = threadinfo.get('uprate', 0)
+                    # add torrents upload rate to the total
+                    totalup += uprate
+
+                    # If the torrent is uploading it is considered active, 
+                    # otherwise ignored for per-torrent rate cap calculation
+                    if uprate > 0:
+                        active_uploads += 1
+                
+                
+                # Minimum by which torrent upload rates can increase 
+                cushion = global_max_upload_rate * 0.05
+                
+                # If there is unused bandwidth greater than the cushion
+                if global_max_upload_rate - totalup > cushion:
+                    # Set the cushion to the amount of unused bandwidth
+                    cushion = global_max_upload_rate - totalup
+                
+                # cushion per torrent
+                cushion_slice = cushion / len(threads)
+
+                # max non-cushion bandwith for active torrents
+                active_max = global_max_upload_rate - cushion
+
+                # amount over max active bandwidth
+                reduce_by = totalup - active_max
+
+                # For each torrent
+                for file, threadinfo in threads.items():
+                    # Get the upload rate for this torrent
+                    uprate = threadinfo.get('uprate', 0)
+                    # This tells Connecter the current total upload rate
+                    threadinfo['global_cap']['global_rate'] = totalup
+                    # And the maxium (always one less than totalup so that torrents are always capped)
+                    threadinfo['global_cap']['global_max_upload_rate'] = totalup - 1
+                    # If not active
+                    if uprate <= 0:
+                        # cap is cushion (per torrent)
+                        threadinfo['global_cap']['global_slice'] = cushion_slice
+                    # Otherwise, if torrent is active
+                    else:
+                        # active cushion slice starts as normal slice
+                        active_cushion_slice = cushion_slice 
+
+                        # Calculate amount to reduce usage
+                        reduce_by_slice = uprate / totalup * reduce_by
+
+                        # a single upload just gets the entire active cushion
+                        if active_uploads > 1:
+                            if uprate > totalup / active_uploads:
+                                active_cushion_slice = cushion_slice - cushion_slice / active_uploads
+                            elif uprate < totalup / active_uploads:
+                                active_cushion_slice = cushion_slice + cushion_slice / active_uploads
+
+                        # Calculate new slice
+                        threadinfo['global_cap']['global_slice'] = uprate - reduce_by_slice + active_cushion_slice
+
+                    if debug_level >= 3 and debug_sec > 60:
+                        if uprate <= 0:
+                            reduce_by_slice = 0
+
+                        downrate = threadinfo.get('downrate', 0)
+                        log("%s: slice: %.0f, uprate: %.0f, downrate: %.0f, reduce_by: %.0f, cushion_slice: %0.f" % (file, threadinfo['global_cap']['global_slice'], uprate, downrate, reduce_by_slice, cushion_slice))
+                if debug_level >= 2 and debug_sec > 60:
+                    log("Summary: active_max: %.0f, totalup: %.0f, threads: %s, active: %s, reduce_by: %0.f, cushion: %0.f" % (active_max, totalup, len(threads), active_uploads, reduce_by, cushion))
+                if debug_sec <= 60:
+                    debug_sec += 1
                 else:
-                    del threadinfo['thread']
-                    threadinfo['timeout'] = 10
-            # dealing with files that dissapear
-            if file not in files:
-                print 'Torrent file dissapeared, killing %s' % file
-                stdout.flush()
-                if threadinfo.get('timeout', -1) == -1:
-                    threadinfo['kill'].set()
-                    threadinfo['thread'].join()
-                # if this thread was filechecking, open it up
-                if threadinfo.get('checking', None): 
-                    filecheck.release()
-                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)
+                    debug_sec = 0
+        except:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
         sleep(1)
 
 def display_thread(displaykiller):
-    interval = 1.0
+    global display_interval
+    interval = display_interval
+    hoursec = 0
+    houruprate = 0
+    hourdownrate = 0
+    houruptotal = 0
+    hourdowntotal = 0
     global threads, status
     while 1:
-        # display file info
-        if (displaykiller.isSet()): 
-            break
-        totalup = 0
-        totaldown = 0
-        totaluptotal = 0.0
-        totaldowntotal = 0.0
-        tdis = threads.items()
-        tdis.sort()
-        for file, threadinfo in tdis: 
-            uprate = threadinfo.get('uprate', 0)
-            downrate = threadinfo.get('downrate', 0)
-            uptxt = fmtsize(uprate, padded = 0)
-            downtxt = fmtsize(downrate, padded = 0)
-            uptotal = threadinfo.get('uptotal', 0.0)
-            downtotal = threadinfo.get('downtotal', 0.0)
-            uptotaltxt = fmtsize(uptotal, baseunit = 2, padded = 0)
-            downtotaltxt = fmtsize(downtotal, baseunit = 2, padded = 0)
-            filename = threadinfo.get('savefile', file)
-            if threadinfo.get('timeout', 0) > 0:
-                trys = threadinfo.get('try', 1)
-                timeout = threadinfo.get('timeout')
-                print '%s: try %d died, retry in %d' % (filename, trys, timeout)
+        try:
+            # display file info
+            if (displaykiller.isSet()): 
+                break
+            totalup = 0
+            totaldown = 0
+            totaluptotal = 0.0
+            totaldowntotal = 0.0
+            tdis = threads.items()
+            tdis.sort()
+            for file, threadinfo in tdis: 
+                uprate = threadinfo.get('uprate', 0)
+                downrate = threadinfo.get('downrate', 0)
+                uptxt = fmtsize(uprate, padded = 0)
+                downtxt = fmtsize(downrate, padded = 0)
+                uptotal = threadinfo.get('uptotal', 0.0)
+                downtotal = threadinfo.get('downtotal', 0.0)
+                uptotaltxt = fmtsize(uptotal, baseunit = 2, padded = 0)
+                downtotaltxt = fmtsize(downtotal, baseunit = 2, padded = 0)
+                filename = threadinfo.get('savefile', file)
+                if threadinfo.get('timeout', 0) > 0:
+                    trys = threadinfo.get('try', 1)
+                    timeout = threadinfo.get('timeout')
+                    print '%s: try %d died, retry in %d' % (filename, trys, timeout)
+                else:
+                    status = threadinfo.get('status','')
+                    print '%s: Spd: %s/s:%s/s Tot: %s:%s [%s]' % (filename, uptxt, downtxt, uptotaltxt, downtotaltxt, status)
+                totalup += uprate
+                totaldown += downrate
+                totaluptotal += uptotal
+                totaldowntotal += downtotal
+            # display totals line
+            houruprate += totalup
+            hourdownrate + totaldown
+            totaluptxt = fmtsize(totalup, padded = 0)
+            totaldowntxt = fmtsize(totaldown, padded = 0)
+            totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2, padded = 0)
+            totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2, padded = 0)
+            print 'All: Spd: %s/s :%s/s, Tot: %s :%s' % (totaluptxt, totaldowntxt, totaluptotaltxt, totaldowntotaltxt)
+            print
+            stdout.flush()
+            if hoursec < 3600 / interval:
+                hoursec +=1
             else:
-                status = threadinfo.get('status','')
-                print '%s: Spd: %s/s:%s/s Tot: %s:%s [%s]' % (filename, uptxt, downtxt, uptotaltxt, downtotaltxt, status)
-            totalup += uprate
-            totaldown += downrate
-            totaluptotal += uptotal
-            totaldowntotal += downtotal
-        # display totals line
-        totaluptxt = fmtsize(totalup, padded = 0)
-        totaldowntxt = fmtsize(totaldown, padded = 0)
-        totaluptotaltxt = fmtsize(totaluptotal, baseunit = 2, padded = 0)
-        totaldowntotaltxt = fmtsize(totaldowntotal, baseunit = 2, padded = 0)
-        print 'All: Spd: %s/s:%s/s Tot: %s:%s' % (totaluptxt, totaldowntxt, totaluptotaltxt, totaldowntotaltxt)
-        print
-        stdout.flush()
+                hoursec = 0
+                hourupratetxt = fmtsize(houruprate / 3600 / interval, padded = 0)
+                hourdownratetxt = fmtsize(hourdownrate / 3600 / interval , padded = 0)
+
+                log('Speed: %s/s : %s/s, Total: %s :%s' % (hourupratetxt, hourdownratetxt, totaluptotaltxt, totaldowntotaltxt))
+                houruprate = 0
+                hourdownrate = 0
+        except:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
         sleep(interval)
 
+
 class StatusUpdater:
-    def __init__(self, file, params, name):
+    def __init__(self, file, params, name, checking = 0, global_cap = None):
         self.file = file
         self.params = params
         self.name = name
         self.myinfo = threads[name]
         self.done = 0
-        self.checking = 0
+        self.checking = checking
+        self.global_cap = global_cap
+        self.myinfo['checking'] = checking
         self.activity = 'starting'
-        self.display()
+        self.myinfo['status'] = self.activity
         self.myinfo['errors'] = []
 
     def download(self): 
-        download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80)
-        print 'Torrent %s stopped' % self.file
-        stdout.flush()
+        try:
+            # Initial bittorrent session for file (80 is 80-column display)
+            download(self.params + ['--responsefile', self.file], self.choose, self.display, self.finished, self.err, self.myinfo['kill'], 80, global_cap = self.global_cap)
+            print 'Torrent %s stopped' % self.file
+            stdout.flush()
+        except:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
 
     def finished(self): 
         self.done = 1
@@ -157,74 +378,185 @@
 
     def choose(self, default, size, saveas, dir):
         global filecheck
-        self.myinfo['downfile'] = default
-        self.myinfo['filesize'] = fmtsize(size)
-        if saveas == '': 
-            saveas = default
-        # it asks me where I want to save it before checking the file.. 
-        if exists(self.file[:-len(ext)]) and (getsize(self.file[:-len(ext)]) > 0):
-            # file will get checked
-            while (not filecheck.acquire(0) and not self.myinfo['kill'].isSet()):
-                self.myinfo['status'] = 'disk wait'
-                sleep(0.1)
-            if not self.myinfo['kill'].isSet():
-                self.checking = 1
-                self.myinfo['checking'] = 1
-        self.myinfo['savefile'] = self.file[:-len(ext)]
+        try:
+            # Save file to the default location and same name as torrent, 
+            # without the .torrent
+            self.myinfo['downfile'] = default
+            self.myinfo['filesize'] = fmtsize(size)
+            if saveas == '': 
+                saveas = default
+                self.myinfo['savefile'] = self.file[:-len(ext)]
+        except:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
+
         return self.file[:-len(ext)]
     
     def display(self, dict = {}):
-        fractionDone = dict.get('fractionDone', None)
-        timeEst = dict.get('timeEst', None)
-        activity = dict.get('activity', None) 
-        global status
-        if activity is not None and not self.done: 
-            if activity == 'checking existing file':
-                self.activity = 'disk check'
-            elif activity == 'connecting to peers':
-                self.activity = 'connecting'
+        try:
+            # Percent done
+            fractionDone = dict.get('fractionDone', None)
+            # Estimated time remaining
+            timeEst = dict.get('timeEst', None)
+            # What the torrent is doing
+            activity = dict.get('activity', None) 
+            global status
+            # If torrent is not done non-download tasks
+            if activity is not None and not self.done: 
+                if activity == 'checking existing file':
+                    self.activity = 'disk check'
+                elif activity == 'connecting to peers':
+                    self.activity = 'connecting'
+                else:
+                    self.activity = activity
+            # Otherwise, if downloading
+            elif timeEst is not None: 
+                # Set display to time remaing for download to complete
+                self.activity = fmttime(timeEst, 1)
+            # If a task is partially done
+            if fractionDone is not None: 
+                # Display task and percent done
+                self.myinfo['status'] = '%s %.0f%%' % (self.activity, fractionDone * 100)
+            # Otherwise, for non-partial tasks
             else:
-                self.activity = activity
-        elif timeEst is not None: 
-            self.activity = fmttime(timeEst, 1)
-        if fractionDone is not None: 
-            self.myinfo['status'] = '%s %.0f%%' % (self.activity, fractionDone * 100)
-        else:
-            self.myinfo['status'] = self.activity
-        if self.activity != 'checking existing file' and self.checking:
-            # we finished checking our files. 
-            filecheck.release()
-            self.checking = 0
-            self.myinfo['checking'] = 0
-        if dict.has_key('upRate'):
-            self.myinfo['uprate'] = dict['upRate']
-        if dict.has_key('downRate'):
-            self.myinfo['downrate'] = dict['downRate']
-        if dict.has_key('upTotal'):
-            self.myinfo['uptotal'] = dict['upTotal']
-        if dict.has_key('downTotal'):
-            self.myinfo['downtotal'] = dict['downTotal']
+                # Display task without percent done
+                self.myinfo['status'] = self.activity
+            # If we think we're checking but we're actually done
+            if self.activity != 'checking existing file' and self.activity != 'disk check' and self.checking:
+                # we finished checking our files, so release the filecheck lock
+                filecheck.release()
+                # and tell ourselves we're not checking anymore
+                self.checking = 0
+                self.myinfo['checking'] = 0
+            # Record upload rate from torrent thread to our own variables
+            if dict.has_key('upRate'):
+                self.myinfo['uprate'] = dict['upRate']
+            # Record download rate from torrent thread to our own variables
+            if dict.has_key('downRate'):
+                self.myinfo['downrate'] = dict['downRate']
+            # Record upload total from torrent thread to our own variables
+            if dict.has_key('upTotal'):
+                self.myinfo['uptotal'] = dict['upTotal']
+            # Record download total from torrent thread to our own variables
+            if dict.has_key('downTotal'):
+                self.myinfo['downtotal'] = dict['downTotal']
+        except:
+            # Print a stack trace
+            traceback.print_exc()
+            # and log it
+            log(traceback.format_exc())
+            # But keep on going
+            pass
+
 
 if __name__ == '__main__':
+    def kill_torrents():
+        # Kill all torrents
+        for file, threadinfo in threads.items(): 
+            status = 'Killing torrent %s' % file
+            # If the thread is still active
+            if threadinfo.get('thread'):
+                # Set the kill event; tells thread that it should die
+                threadinfo['kill'].set() 
+                # Attach to thread until it stops
+                threadinfo['thread'].join() 
+            # Remove thread from list of active threads
+            del threads[file]
+        # Kill display thread by setting even that tells it that it should die
+        displaykiller.set()
+        # And attach to the thread until it exits
+        displaythread.join()
+        # Kill slice calcuation thread by setting event that tells thread to die
+        calckiller.set()
+        # And attach to thread until it exits
+        calcslicethread.join()
+        # and exit
     if (len(argv) < 2):
         print """Usage: btlaunchmany.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)
+   --global_max_upload_rate <rate> - combined maximum upload rate in kB/s (optional)
+   --logfile <filename> - file to which to log errors and debugging
+   --display_interval display_rate - number of seconds between display updates
+   --debug_level <#> - level of verbosity for debugging
 """
         exit(-1)
+
+    # We need to determine the global_upload_rate 
+    # So parse the command line after the torrent directory URL
+     
+    logfilename = None
+    # If we have global maximum upload rate
+    for i in xrange(4):
+        if len(argv) > 3:
+            if argv[2] == "--global_max_upload_rate":
+                # convert the rate to an integer representing KB/s
+                global_max_upload_rate = locale.atoi(argv[3]) * 1024
+                # Remove --global_max_upload_rate rate from the argument list
+                # so that download.py (the bittorrent downloader, which is used by
+                # btlaunchmany to do the actual downloads) doesn't get this
+                # parameter, which it doesn't understand
+                del argv[3]
+                del argv[2]
+            elif argv[2] == "--logfile":
+                logfilename = argv[3]
+                del argv[3]
+                del argv[2]
+            elif argv[2] == "--display_interval":
+                display_interval = locale.atoi(argv[3])
+                del argv[3]
+                del argv[2]
+            elif argv[2] == "--debug_level":
+                debug_level = locale.atoi(argv[3])
+                del argv[3]
+                del argv[2]
+
+    try:
+        if logfilename != None:
+            print logfilename
+            logfile = open(logfilename, 'a')
+    except EnvironmentError:
+        print("btlaunchmany.py died trying to open the logfile")
+        sys.exit(1)
+    log("bittorrent started")
+    log("Parameters: global_max_upload_rate: %s, logfile: %s, interval %s" % (global_max_upload_rate, logfilename, display_interval))
+
+
+
     try:
+        # Add event that allows us to cancel updating display when
+        # we want to exit this program
         displaykiller = Event()
+        # Create display updating thread and start it
         displaythread = Thread(target = display_thread, name = 'display', args = [displaykiller])
         displaythread.start()
+        # Add event that allows us to cancel global slice calculation when
+        # we want to exit this program
+        calckiller = Event()
+        # Create global slice calculation thread ands start it
+        calcslicethread = Thread(target = calc_slice, name='calc_slice', args = [calckiller])
+        calcslicethread.start()
+        # Execute the main action loop, which checks for new torrent files and
+        # initiates a bittorrent transfer for it or removes a bittorrent
+        # transfer thread for files that have been removed
         dropdir_mainloop(argv[1], argv[2:])
+        # Never exits, unless there is an exception
+    # Until interrupted by Ctrl-C or kill -INT
     except KeyboardInterrupt: 
         print '^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()
+        # kill off torrents
+        kill_torrents()
+        # and exit
+    # Or there is an uncaught exception
     except:
+        # print a stack trace
         traceback.print_exc()
+        # and log it
+        log(traceback.format_exc())
+        # kill off torrents
+        kill_torrents()
+        # and exit
diff -Naur bittorrent_3.4.2-11.1/debian/bittorrent-downloader.bittorrent.1 bittorrent-3.4.2/debian/bittorrent-downloader.bittorrent.1
--- bittorrent_3.4.2-11.1/debian/bittorrent-downloader.bittorrent.1	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/debian/bittorrent-downloader.bittorrent.1	2008-06-01 02:00:45.000000000 -0400
@@ -117,6 +117,16 @@
 .B \-\-rarest_first_priority_cutoff \fInum\fP
 the number of peers which need to have a piece before other partials take
 priority over rarest first (default 3)
+.TP
+.B \-\-min_outgoing_port \fIportnum\fP
+set \fIportnum\fP as the minimum port from which we are allowed to connect
+(default 1024).  Useful to set this for strict (outgoing blocking)
+firewalls that allow bittorrent out only from specific ports.
+.TP
+.B \-\-max_outgoing_port \fIportnum\fP
+set \fIportnum\fP as the maximum port from which we are allowed to
+connect (default 65535).  Useful to set this for strict (outgoing blocking)
+firewalls that allow bittorrent out only from specific ports.
 
 .SH SEE ALSO
 .BR bttrack (1),
diff -Naur bittorrent_3.4.2-11.1/debian/changelog bittorrent-3.4.2/debian/changelog
--- bittorrent_3.4.2-11.1/debian/changelog	2008-05-31 19:44:44.000000000 -0400
+++ bittorrent-3.4.2/debian/changelog	2008-07-01 10:55:23.000000000 -0400
@@ -1,3 +1,26 @@
+bittorrent (3.4.2-11.2dfd06~005) unstable; urgency=low
+	
+  * Modify global upload throttle so that it uses full bandwidth, except
+    for a small cushion for inactive torrents to start downloading
+
+ -- Daniel Dickinson <cshore@fionavar.ca>  Mon, 30 Jun 2008 22:46:45 -0500	
+
+bittorrent (3.4.2-11.2dfd05~015) unstable; urgency=low
+	
+  * Add global upload rate throttle that keeps a cushion for new
+    sessions sessions under max to grow, but otherwise allocates full max
+    global upload rate to sessions that want to upload at a rate greater
+    than the per-session maximum upload rate (calculated as global
+    maximum divided by number of active uploads) (Closes: #481276)
+  * Fix deadfile handling and checking locking.  (Closes: #478758)
+  * Fix exception handling (or rather lack thereof)
+  * Comment a bunch of code
+  * Change startup disk check to be one torrent at a time. (Closes: #482478)
+  * Add outgoing port range limiting in order to play well with strict 
+    firewalls.  (Closes: #481276)
+
+ -- Daniel Dickinson <cshore@fionavar.ca>  Sun, 01 Jun 2008 02:09:55 -0500
+	
 bittorrent (3.4.2-11.1) unstable; urgency=low
 
   * Non-maintainer upload.

--- End Message ---
--- Begin Message ---
Version: 3.4.2-12+rm

Dear submitter,

as the package bittorrent has just been removed from the Debian archive
unstable we hereby close the associated bug reports.  We are sorry
that we couldn't deal with your issue properly.

For details on the removal, please see https://bugs.debian.org/936210

The version of this package that was in Debian prior to this removal
can still be found using http://snapshot.debian.org/.

This message was generated automatically; if you believe that there is
a problem with it please contact the archive administrators by mailing
ftpmaster@ftp-master.debian.org.

Debian distribution maintenance software
pp.
Scott Kitterman (the ftpmaster behind the curtain)

--- End Message ---

Reply to: