Blame | Last modification | View Log | Download
# Written by Bram Cohen# see LICENSE.txt for license informationfrom random import randrange, shufflefrom BitTornado.clock import clocktry:Trueexcept:True = 1False = 0class PiecePicker:def __init__(self, numpieces,rarest_first_cutoff = 1, rarest_first_priority_cutoff = 3,priority_step = 20):self.rarest_first_cutoff = rarest_first_cutoffself.rarest_first_priority_cutoff = rarest_first_priority_cutoff + priority_stepself.priority_step = priority_stepself.cutoff = rarest_first_priority_cutoffself.numpieces = numpiecesself.started = []self.totalcount = 0self.numhaves = [0] * numpiecesself.priority = [1] * numpiecesself.removed_partials = {}self.crosscount = [numpieces]self.crosscount2 = [numpieces]self.has = [0] * numpiecesself.numgot = 0self.done = Falseself.seed_connections = {}self.past_ips = {}self.seed_time = Noneself.superseed = Falseself.seeds_connected = 0self._init_interests()def _init_interests(self):self.interests = [[] for x in xrange(self.priority_step)]self.level_in_interests = [self.priority_step] * self.numpiecesinterests = range(self.numpieces)shuffle(interests)self.pos_in_interests = [0] * self.numpiecesfor i in xrange(self.numpieces):self.pos_in_interests[interests[i]] = iself.interests.append(interests)def got_have(self, piece):self.totalcount+=1numint = self.numhaves[piece]self.numhaves[piece] += 1self.crosscount[numint] -= 1if numint+1==len(self.crosscount):self.crosscount.append(0)self.crosscount[numint+1] += 1if not self.done:numintplus = numint+self.has[piece]self.crosscount2[numintplus] -= 1if numintplus+1 == len(self.crosscount2):self.crosscount2.append(0)self.crosscount2[numintplus+1] += 1numint = self.level_in_interests[piece]self.level_in_interests[piece] += 1if self.superseed:self.seed_got_haves[piece] += 1numint = self.level_in_interests[piece]self.level_in_interests[piece] += 1elif self.has[piece] or self.priority[piece] == -1:returnif numint == len(self.interests) - 1:self.interests.append([])self._shift_over(piece, self.interests[numint], self.interests[numint + 1])def lost_have(self, piece):self.totalcount-=1numint = self.numhaves[piece]self.numhaves[piece] -= 1self.crosscount[numint] -= 1self.crosscount[numint-1] += 1if not self.done:numintplus = numint+self.has[piece]self.crosscount2[numintplus] -= 1self.crosscount2[numintplus-1] += 1numint = self.level_in_interests[piece]self.level_in_interests[piece] -= 1if self.superseed:numint = self.level_in_interests[piece]self.level_in_interests[piece] -= 1elif self.has[piece] or self.priority[piece] == -1:returnself._shift_over(piece, self.interests[numint], self.interests[numint - 1])def _shift_over(self, piece, l1, l2):assert self.superseed or (not self.has[piece] and self.priority[piece] >= 0)parray = self.pos_in_interestsp = parray[piece]assert l1[p] == pieceq = l1[-1]l1[p] = qparray[q] = pdel l1[-1]newp = randrange(len(l2)+1)if newp == len(l2):parray[piece] = len(l2)l2.append(piece)else:old = l2[newp]parray[old] = len(l2)l2.append(old)l2[newp] = pieceparray[piece] = newpdef got_seed(self):self.seeds_connected += 1self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)def became_seed(self):self.got_seed()self.totalcount -= self.numpiecesself.numhaves = [i-1 for i in self.numhaves]if self.superseed or not self.done:self.level_in_interests = [i-1 for i in self.level_in_interests]del self.interests[0]del self.crosscount[0]if not self.done:del self.crosscount2[0]def lost_seed(self):self.seeds_connected -= 1self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)def requested(self, piece):if piece not in self.started:self.started.append(piece)def _remove_from_interests(self, piece, keep_partial = False):l = self.interests[self.level_in_interests[piece]]p = self.pos_in_interests[piece]assert l[p] == pieceq = l[-1]l[p] = qself.pos_in_interests[q] = pdel l[-1]try:self.started.remove(piece)if keep_partial:self.removed_partials[piece] = 1except ValueError:passdef complete(self, piece):assert not self.has[piece]self.has[piece] = 1self.numgot += 1if self.numgot == self.numpieces:self.done = Trueself.crosscount2 = self.crosscountelse:numhaves = self.numhaves[piece]self.crosscount2[numhaves] -= 1if numhaves+1 == len(self.crosscount2):self.crosscount2.append(0)self.crosscount2[numhaves+1] += 1self._remove_from_interests(piece)def next(self, haves, wantfunc, complete_first = False):cutoff = self.numgot < self.rarest_first_cutoffcomplete_first = (complete_first or cutoff) and not haves.complete()best = Nonebestnum = 2 ** 30for i in self.started:if haves[i] and wantfunc(i):if self.level_in_interests[i] < bestnum:best = ibestnum = self.level_in_interests[i]if best is not None:if complete_first or (cutoff and len(self.interests) > self.cutoff):return bestif haves.complete():r = [ (0, min(bestnum,len(self.interests))) ]elif cutoff and len(self.interests) > self.cutoff:r = [ (self.cutoff, min(bestnum,len(self.interests))),(0, self.cutoff) ]else:r = [ (0, min(bestnum,len(self.interests))) ]for lo,hi in r:for i in xrange(lo,hi):for j in self.interests[i]:if haves[j] and wantfunc(j):return jif best is not None:return bestreturn Nonedef am_I_complete(self):return self.donedef bump(self, piece):l = self.interests[self.level_in_interests[piece]]pos = self.pos_in_interests[piece]del l[pos]l.append(piece)for i in range(pos,len(l)):self.pos_in_interests[l[i]] = itry:self.started.remove(piece)except:passdef set_priority(self, piece, p):if self.superseed:return False # don't muck with this if you're a superseedoldp = self.priority[piece]if oldp == p:return Falseself.priority[piece] = pif p == -1:# when setting priority -1,# make sure to cancel any downloads for this pieceif not self.has[piece]:self._remove_from_interests(piece, True)return Trueif oldp == -1:level = self.numhaves[piece] + (self.priority_step * p)self.level_in_interests[piece] = levelif self.has[piece]:return Truewhile len(self.interests) < level+1:self.interests.append([])l2 = self.interests[level]parray = self.pos_in_interestsnewp = randrange(len(l2)+1)if newp == len(l2):parray[piece] = len(l2)l2.append(piece)else:old = l2[newp]parray[old] = len(l2)l2.append(old)l2[newp] = pieceparray[piece] = newpif self.removed_partials.has_key(piece):del self.removed_partials[piece]self.started.append(piece)# now go to downloader and try requesting morereturn Truenumint = self.level_in_interests[piece]newint = numint + ((p - oldp) * self.priority_step)self.level_in_interests[piece] = newintif self.has[piece]:return Falsewhile len(self.interests) < newint+1:self.interests.append([])self._shift_over(piece, self.interests[numint], self.interests[newint])return Falsedef is_blocked(self, piece):return self.priority[piece] < 0def set_superseed(self):assert self.doneself.superseed = Trueself.seed_got_haves = [0] * self.numpiecesself._init_interests() # assume everyone is disconnecteddef next_have(self, connection, looser_upload):if self.seed_time is None:self.seed_time = clock()return Noneif clock() < self.seed_time+10: # wait 10 seconds after seeing the first peersreturn None # to give time to grab have listsif not connection.upload.super_seeding:return Noneolddl = self.seed_connections.get(connection)if olddl is not None:ip = connection.get_ip()olddl = self.past_ips.get(ip)if olddl is not None: # peer reconnectedself.seed_connections[connection] = olddlif olddl is not None:if looser_upload:num = 1 # send a new have even if it hasn't spread that piece elsewhereelse:num = 2if self.seed_got_haves[olddl] < num:return Noneif not connection.upload.was_ever_interested: # it never downloaded it?connection.upload.skipped_count += 1if connection.upload.skipped_count >= 3: # probably another stealthed seedreturn -1 # signal to close itfor tier in self.interests:for piece in tier:if not connection.download.have[piece]:seedint = self.level_in_interests[piece]self.level_in_interests[piece] += 1 # tweak it up one, so you don't duplicate effortif seedint == len(self.interests) - 1:self.interests.append([])self._shift_over(piece,self.interests[seedint], self.interests[seedint + 1])self.seed_got_haves[piece] = 0 # reset thisself.seed_connections[connection] = piececonnection.upload.seed_have_list.append(piece)return piecereturn -1 # something screwy; terminate connectiondef lost_peer(self, connection):olddl = self.seed_connections.get(connection)if olddl is None:returndel self.seed_connections[connection]self.past_ips[connection.get_ip()] = olddlif self.seed_got_haves[olddl] == 1:self.seed_got_haves[olddl] = 0