| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- # Written by Bram Cohen
- # see LICENSE.txt for license information
- from random import randrange, shuffle
- from BitTornado.clock import clock
- try:
- True
- except:
- True = 1
- False = 0
- class PiecePicker:
- def __init__(self, numpieces,
- rarest_first_cutoff = 1, rarest_first_priority_cutoff = 3,
- priority_step = 20):
- self.rarest_first_cutoff = rarest_first_cutoff
- self.rarest_first_priority_cutoff = rarest_first_priority_cutoff + priority_step
- self.priority_step = priority_step
- self.cutoff = rarest_first_priority_cutoff
- self.numpieces = numpieces
- self.started = []
- self.totalcount = 0
- self.numhaves = [0] * numpieces
- self.priority = [1] * numpieces
- self.removed_partials = {}
- self.crosscount = [numpieces]
- self.crosscount2 = [numpieces]
- self.has = [0] * numpieces
- self.numgot = 0
- self.done = False
- self.seed_connections = {}
- self.past_ips = {}
- self.seed_time = None
- self.superseed = False
- self.seeds_connected = 0
- self._init_interests()
- def _init_interests(self):
- self.interests = [[] for x in xrange(self.priority_step)]
- self.level_in_interests = [self.priority_step] * self.numpieces
- interests = range(self.numpieces)
- shuffle(interests)
- self.pos_in_interests = [0] * self.numpieces
- for i in xrange(self.numpieces):
- self.pos_in_interests[interests[i]] = i
- self.interests.append(interests)
- def got_have(self, piece):
- self.totalcount+=1
- numint = self.numhaves[piece]
- self.numhaves[piece] += 1
- self.crosscount[numint] -= 1
- if numint+1==len(self.crosscount):
- self.crosscount.append(0)
- self.crosscount[numint+1] += 1
- if not self.done:
- numintplus = numint+self.has[piece]
- self.crosscount2[numintplus] -= 1
- if numintplus+1 == len(self.crosscount2):
- self.crosscount2.append(0)
- self.crosscount2[numintplus+1] += 1
- numint = self.level_in_interests[piece]
- self.level_in_interests[piece] += 1
- if self.superseed:
- self.seed_got_haves[piece] += 1
- numint = self.level_in_interests[piece]
- self.level_in_interests[piece] += 1
- elif self.has[piece] or self.priority[piece] == -1:
- return
- if 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-=1
- numint = self.numhaves[piece]
- self.numhaves[piece] -= 1
- self.crosscount[numint] -= 1
- self.crosscount[numint-1] += 1
- if not self.done:
- numintplus = numint+self.has[piece]
- self.crosscount2[numintplus] -= 1
- self.crosscount2[numintplus-1] += 1
- numint = self.level_in_interests[piece]
- self.level_in_interests[piece] -= 1
- if self.superseed:
- numint = self.level_in_interests[piece]
- self.level_in_interests[piece] -= 1
- elif self.has[piece] or self.priority[piece] == -1:
- return
- self._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_interests
- p = parray[piece]
- assert l1[p] == piece
- q = l1[-1]
- l1[p] = q
- parray[q] = p
- del 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] = piece
- parray[piece] = newp
- def got_seed(self):
- self.seeds_connected += 1
- self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)
- def became_seed(self):
- self.got_seed()
- self.totalcount -= self.numpieces
- self.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]
- if self.interests:
- del self.interests[0]
- del self.crosscount[0]
- if not self.done:
- del self.crosscount2[0]
- def lost_seed(self):
- self.seeds_connected -= 1
- self.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] == piece
- q = l[-1]
- l[p] = q
- self.pos_in_interests[q] = p
- del l[-1]
- try:
- self.started.remove(piece)
- if keep_partial:
- self.removed_partials[piece] = 1
- except ValueError:
- pass
- def complete(self, piece):
- assert not self.has[piece]
- self.has[piece] = 1
- self.numgot += 1
- if self.numgot == self.numpieces:
- self.done = True
- self.crosscount2 = self.crosscount
- else:
- numhaves = self.numhaves[piece]
- self.crosscount2[numhaves] -= 1
- if numhaves+1 == len(self.crosscount2):
- self.crosscount2.append(0)
- self.crosscount2[numhaves+1] += 1
- self._remove_from_interests(piece)
- def next(self, haves, wantfunc, complete_first = False):
- cutoff = self.numgot < self.rarest_first_cutoff
- complete_first = (complete_first or cutoff) and not haves.complete()
- best = None
- bestnum = 2 ** 30
- for i in self.started:
- if haves[i] and wantfunc(i):
- if self.level_in_interests[i] < bestnum:
- best = i
- bestnum = self.level_in_interests[i]
- if best is not None:
- if complete_first or (cutoff and len(self.interests) > self.cutoff):
- return best
- if 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 j
- if best is not None:
- return best
- return None
- def am_I_complete(self):
- return self.done
-
- def 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]] = i
- try:
- self.started.remove(piece)
- except:
- pass
- def set_priority(self, piece, p):
- if self.superseed:
- return False # don't muck with this if you're a superseed
- oldp = self.priority[piece]
- if oldp == p:
- return False
- self.priority[piece] = p
- if p == -1:
- # when setting priority -1,
- # make sure to cancel any downloads for this piece
- if not self.has[piece]:
- self._remove_from_interests(piece, True)
- return True
- if oldp == -1:
- level = self.numhaves[piece] + (self.priority_step * p)
- self.level_in_interests[piece] = level
- if self.has[piece]:
- return True
- while len(self.interests) < level+1:
- self.interests.append([])
- l2 = self.interests[level]
- parray = self.pos_in_interests
- 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] = piece
- parray[piece] = newp
- if self.removed_partials.has_key(piece):
- del self.removed_partials[piece]
- self.started.append(piece)
- # now go to downloader and try requesting more
- return True
- numint = self.level_in_interests[piece]
- newint = numint + ((p - oldp) * self.priority_step)
- self.level_in_interests[piece] = newint
- if self.has[piece]:
- return False
- while len(self.interests) < newint+1:
- self.interests.append([])
- self._shift_over(piece, self.interests[numint], self.interests[newint])
- return False
- def is_blocked(self, piece):
- return self.priority[piece] < 0
- def set_superseed(self):
- assert self.done
- self.superseed = True
- self.seed_got_haves = [0] * self.numpieces
- self._init_interests() # assume everyone is disconnected
- def next_have(self, connection, looser_upload):
- if self.seed_time is None:
- self.seed_time = clock()
- return None
- if clock() < self.seed_time+10: # wait 10 seconds after seeing the first peers
- return None # to give time to grab have lists
- if not connection.upload.super_seeding:
- return None
- olddl = self.seed_connections.get(connection)
- if olddl is None:
- ip = connection.get_ip()
- olddl = self.past_ips.get(ip)
- if olddl is not None: # peer reconnected
- self.seed_connections[connection] = olddl
- if not looser_upload:
- self.seed_got_haves[olddl] -= 1 # penalize
- if olddl is not None:
- if looser_upload:
- num = 1 # send a new have even if it hasn't spread that piece elsewhere
- else:
- num = 2
- if self.seed_got_haves[olddl] < num:
- return None
- if not connection.upload.was_ever_interested: # it never downloaded it?
- connection.upload.skipped_count += 1
- if connection.upload.skipped_count >= 3: # probably another stealthed seed
- return -1 # signal to close it
- for 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 effort
- if 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 this
- self.seed_connections[connection] = piece
- connection.upload.seed_have_list.append(piece)
- return piece
- return -1 # something screwy; terminate connection
- def lost_peer(self, connection):
- olddl = self.seed_connections.get(connection)
- if olddl is None:
- return
- del self.seed_connections[connection]
- self.past_ips[connection.get_ip()] = olddl
- if self.seed_got_haves[olddl] == 1:
- self.seed_got_haves[olddl] = 0
|