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.