PiecePicker.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.1 (the License). You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License. You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10. # Written by Bram Cohen and Greg Hazel
  11. import array
  12. import random
  13. import itertools
  14. def resolve_typecode(n):
  15. if n < 32768:
  16. return 'h'
  17. return 'l'
  18. class PieceBuckets(object):
  19. """A PieceBuckets object is an array of arrays. ith bucket contains
  20. pieces that have i known instances within the network. Pieces
  21. within each bucket are randomly ordered."""
  22. def __init__(self, typecode):
  23. self.typecode = typecode
  24. # [[piece]]
  25. self.buckets = []
  26. # {piece: (bucket, bucketpos)}
  27. self.place_in_buckets = {}
  28. def get_position(self, piece): # returns which bucket piece is in.
  29. return self.place_in_buckets[piece][0]
  30. def __contains__(self, piece):
  31. return piece in self.place_in_buckets
  32. def add(self, piece, bucketindex):
  33. assert not self.place_in_buckets.has_key(piece)
  34. while len(self.buckets) <= bucketindex:
  35. self.buckets.append(array.array(self.typecode))
  36. bucket = self.buckets[bucketindex]
  37. # randomly swap piece with piece already in bucket...
  38. newspot = random.randrange(len(bucket) + 1)
  39. if newspot == len(bucket):
  40. bucket.append(piece)
  41. else:
  42. tomove = bucket[newspot]
  43. self.place_in_buckets[tomove] = (bucketindex, len(bucket))
  44. bucket.append(tomove)
  45. bucket[newspot] = piece
  46. self.place_in_buckets[piece] = (bucketindex, newspot)
  47. def remove(self, piece):
  48. bucketindex, bucketpos = self.place_in_buckets.pop(piece)
  49. bucket = self.buckets[bucketindex]
  50. tomove = bucket[-1]
  51. if tomove != piece:
  52. bucket[bucketpos] = tomove
  53. self.place_in_buckets[tomove] = (bucketindex, bucketpos)
  54. del bucket[-1]
  55. while len(self.buckets) > 0 and len(self.buckets[-1]) == 0:
  56. del self.buckets[-1]
  57. return bucketindex
  58. # to be removed
  59. def bump(self, piece):
  60. bucketindex, bucketpos = self.place_in_buckets[piece]
  61. bucket = self.buckets[bucketindex]
  62. tomove = bucket[-1]
  63. if tomove != piece:
  64. bucket[bucketpos] = tomove
  65. self.place_in_buckets[tomove] = (bucketindex, bucketpos)
  66. bucket[-1] = piece
  67. self.place_in_buckets[piece] = (bucketindex, len(bucket)-1)
  68. def prepend_bucket(self):
  69. # it' possible we had everything to begin with
  70. if len(self.buckets) == 0:
  71. return
  72. self.buckets.insert(0, array.array(self.typecode))
  73. # bleh.
  74. for piece in self.place_in_buckets:
  75. index, pos = self.place_in_buckets[piece]
  76. self.place_in_buckets[piece] = (index + 1, pos)
  77. def popleft_bucket(self):
  78. # it' possible we had everything to begin with
  79. if len(self.buckets) == 0:
  80. return
  81. self.buckets.pop(0)
  82. # bleh.
  83. for piece in self.place_in_buckets:
  84. index, pos = self.place_in_buckets[piece]
  85. self.place_in_buckets[piece] = (index - 1, pos)
  86. class PiecePicker(object):
  87. def __init__(self, config, numpieces, not_have):
  88. self.config = config
  89. self.numpieces = numpieces
  90. self.typecode = resolve_typecode(numpieces)
  91. self.piece_bucketss = [PieceBuckets(self.typecode)]
  92. self.scrambled = array.array(self.typecode)
  93. self.numgot = self.numpieces
  94. for i in not_have:
  95. self.scrambled.append(i)
  96. self.piece_bucketss[0].add(i, 0)
  97. self.numgot -= 1
  98. random.shuffle(self.scrambled)
  99. def get_distributed_copies(self):
  100. base = 0
  101. for i, bucket in enumerate(self.piece_bucketss[0].buckets):
  102. l = len(bucket)
  103. if l == 0:
  104. # the whole bucket is full. keep going
  105. continue
  106. base = i + 1
  107. # remove the fractional size of this bucket, and stop
  108. base -= (float(l) / float(self.numpieces))
  109. break
  110. return base
  111. def set_priority(self, pieces, priority):
  112. while len(self.piece_bucketss) <= priority:
  113. self.piece_bucketss.append(PieceBuckets(self.typecode))
  114. for piece in pieces:
  115. for p in self.piece_bucketss:
  116. if piece in p:
  117. self.piece_bucketss[priority].add(piece, p.remove(piece))
  118. break
  119. else:
  120. assert False
  121. def got_have_all(self):
  122. for p in self.piece_bucketss:
  123. p.prepend_bucket()
  124. def got_have(self, piece):
  125. for p in self.piece_bucketss:
  126. if piece in p:
  127. p.add(piece, p.remove(piece) + 1)
  128. return
  129. def lost_have_all(self):
  130. for p in self.piece_bucketss:
  131. p.popleft_bucket()
  132. def lost_have(self, piece):
  133. for p in self.piece_bucketss:
  134. if piece in p:
  135. p.add(piece, p.remove(piece) - 1)
  136. return
  137. def complete(self, piece):
  138. self.numgot += 1
  139. if self.numgot < self.config['rarest_first_cutoff']:
  140. self.scrambled.remove(piece)
  141. else:
  142. self.scrambled = None
  143. for p in self.piece_bucketss:
  144. if piece in p:
  145. p.remove(piece)
  146. break
  147. else:
  148. assert False
  149. def from_behind(self, haves, bans):
  150. for piece_buckets in self.piece_bucketss:
  151. for i in xrange(len(piece_buckets.buckets) - 1, 0, -1):
  152. for j in piece_buckets.buckets[i]:
  153. if haves[j] and j not in bans:
  154. return j
  155. return None
  156. def next(self, haves, tiebreaks, bans, suggests):
  157. """returns next piece to download.
  158. @param haves: set of pieces the remote peer has.
  159. @param tiebreaks: pieces with active (started) requests
  160. @param bans: pieces not to pick.
  161. @param suggests: set of suggestions made by the remote peer.
  162. """
  163. # first few pieces are provided in random rather than rarest-first
  164. if self.numgot < self.config['rarest_first_cutoff']:
  165. for i in itertools.chain(tiebreaks, self.scrambled):
  166. if haves[i] and i not in bans:
  167. return i
  168. return None
  169. # from highest priority to lowest priority piece buckets...
  170. for k in xrange(len(self.piece_bucketss) - 1, -1, -1):
  171. piece_buckets = self.piece_bucketss[k]
  172. # Of the same priority, a suggestion is taken first.
  173. for s in suggests:
  174. if s not in bans and haves[s] and s in piece_buckets:
  175. return s
  176. bestnum = None
  177. best = None
  178. rarity_of_started = [(piece_buckets.get_position(i), i)
  179. for i in tiebreaks if i not in bans and haves[i] and
  180. i in piece_buckets]
  181. if rarity_of_started:
  182. bestnum = min(rarity_of_started)[0] # smallest bucket index
  183. best = random.choice([j for (i, j) in rarity_of_started
  184. if i == bestnum]) # random pick of those in smallest bkt
  185. for i in xrange(1, len(piece_buckets.buckets)):
  186. if bestnum == i: # if best of started is also rarest...
  187. return best
  188. for j in piece_buckets.buckets[i]:
  189. if haves[j] and j not in bans:
  190. return j # return first found.
  191. return None
  192. # to be removed
  193. def bump(self, piece):
  194. for p in self.piece_bucketss:
  195. if piece in p:
  196. p.bump(piece)
  197. break
  198. else:
  199. assert False