--- 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 <csh...@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 <csh...@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 <csh...@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 <csh...@fionavar.ca> Sun, 01 Jun 2008 02:09:55 -0500
+
bittorrent (3.4.2-11.1) unstable; urgency=low
* Non-maintainer upload.
--- End Message ---