module.exports = RarityMap /** * Mapping of torrent pieces to their respective availability in the swarm. Used by * the torrent manager for implementing the rarest piece first selection strategy. * * @param {Swarm} swarm bittorrent-swarm to track availability * @param {number} numPieces number of pieces in the torrent */ function RarityMap (swarm, numPieces) { var self = this self.pieces = [] self.swarm = swarm self.numPieces = numPieces function initWire (wire) { wire.on('have', function (index) { self.pieces[index] += 1 }) wire.on('bitfield', function () { self.recalculate() }) wire.on('close', function () { for (var i = 0; i < self.numPieces; ++i) { self.pieces[i] -= wire.peerPieces.get(i) } }) } self.swarm.wires.forEach(initWire) self.swarm.on('wire', function (wire) { self.recalculate() initWire(wire) }) self.recalculate() } /** * Recalculates piece availability across all peers in the swarm. */ RarityMap.prototype.recalculate = function () { var self = this for (var i = 0; i < self.numPieces; ++i) { self.pieces[i] = 0 } self.swarm.wires.forEach(function (wire) { for (var i = 0; i < self.numPieces; ++i) { self.pieces[i] += wire.peerPieces.get(i) } }) } /** * Get the index of the rarest piece. Optionally, pass a filter function to exclude * certain pieces (for instance, those that we already have). * * @param {function} pieceFilterFunc * @return {number} index of rarest piece, or -1 */ RarityMap.prototype.getRarestPiece = function (pieceFilterFunc) { var self = this var candidates = [] var min = Infinity pieceFilterFunc = pieceFilterFunc || function () { return true } for (var i = 0; i < self.numPieces; ++i) { if (!pieceFilterFunc(i)) continue var availability = self.pieces[i] if (availability === min) { candidates.push(i) } else if (availability < min) { candidates = [ i ] min = availability } } if (candidates.length > 0) { // if there are multiple pieces with the same availability, choose one randomly return candidates[Math.random() * candidates.length | 0] } else { return -1 } }