Package: bittorrent
Version: 3.4.2-11.1
Followup-For: Bug #488900

This fixes a couple of minor issues discovered while using btlaunchmany.  The 
statistics which get sent to the log file were not correct when the display was 
displayed less often than once per second (display_interval > 1).  Also the 
global_cap would sometimes be exceeded due to an error in the calculation of 
the per torrent cushion (the cushion was supposed to be divided in half and 
among split among all torrents and and the other half among those using less 
than an equal share of the bandwidth, but it was getting added and subtracted 
incorrectly).

-- 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-02 17:47:00.000000000 -0400
@@ -12,134 +12,378 @@
 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
+
+                # number active torrents greater then equal slice of bw
+                num_over_even = 0
+
+                # number of active torrents less then equal slice of bw
+                num_under_even = 0
+
+                # Only do this if there are active uploads
+                if active_uploads > 1:
+                    for file, threadinfo in threads.items():
+                        # Get the upload rate for this torrent
+                        uprate = threadinfo.get('uprate', 0)
+                    
+                        # If upload rate greater than equal portion of bw
+                        if uprate > totalup / active_uploads:
+                            num_over_even += 1
+                        # If upload rate less than equal portion of bw
+                        elif uprate < totalup / active_uploads:
+                            num_under_even += 1
+
+                # 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 upload rate greater than equal portion of bw
+                            if uprate > totalup / active_uploads:
+                                # cushion is reduced by half cushion / num_over
+                                active_cushion_slice = cushion_slice - cushion_slice / num_over_even
+                            # If upload rate less than equal portion of bw
+                            elif uprate < totalup / active_uploads:
+                                # cushion is increased by half cusion / num_und
+                                active_cushion_slice = cushion_slice + cushion_slice / num_under_even
+
+                        # 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 +401,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-02 17:10:06.000000000 -0400
@@ -1,3 +1,34 @@
+bittorrent (3.4.2-11.2dfd07~001) unstable; urgency=low
+
+  * Fix global upload throttle so it doesn't allocate more bandwith (because
+    it was adding to cushion without decreasing active bandwidth)
+  * Fix statistics in log when display interval is not 1 second
+
+ -- Daniel Dickinson <[EMAIL PROTECTED]>  Wed, 02 Jul 2008 17:10:00 -0500
+
+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 <[EMAIL PROTECTED]>  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 <[EMAIL PROTECTED]>  Sun, 01 Jun 2008 02:09:55 -0500
+	
 bittorrent (3.4.2-11.1) unstable; urgency=low
 
   * Non-maintainer upload.

Reply via email to