module.exports = RarityMap /** * Mapping of torrent pieces to their respective availability in the torrent swarm. Used * by the torrent manager for implementing the rarest piece first selection strategy. */ function RarityMap (torrent) { var self = this self._torrent = torrent self._numPieces = torrent.pieces.length self._pieces = [] self._onWire = function (wire) { self.recalculate() self._initWire(wire) } self._onWireHave = function (index) { self._pieces[index] += 1 } self._onWireBitfield = function () { self.recalculate() } self._torrent.wires.forEach(function (wire) { self._initWire(wire) }) self._torrent.on('wire', self._onWire) self.recalculate() } /** * 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) { if (!pieceFilterFunc) pieceFilterFunc = trueFn var candidates = [] var min = Infinity for (var i = 0; i < this._numPieces; ++i) { if (!pieceFilterFunc(i)) continue var availability = this._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 } } RarityMap.prototype.destroy = function () { var self = this self._torrent.removeListener('wire', self._onWire) self._torrent.wires.forEach(function (wire) { self._cleanupWireEvents(wire) }) self._torrent = null self._pieces = null self._onWire = null self._onWireHave = null self._onWireBitfield = null } RarityMap.prototype._initWire = function (wire) { var self = this wire._onClose = function () { self._cleanupWireEvents(wire) for (var i = 0; i < this._numPieces; ++i) { self._pieces[i] -= wire.peerPieces.get(i) } } wire.on('have', self._onWireHave) wire.on('bitfield', self._onWireBitfield) wire.once('close', wire._onClose) } /** * Recalculates piece availability across all peers in the torrent. */ RarityMap.prototype.recalculate = function () { var i for (i = 0; i < this._numPieces; ++i) { this._pieces[i] = 0 } var numWires = this._torrent.wires.length for (i = 0; i < numWires; ++i) { var wire = this._torrent.wires[i] for (var j = 0; j < this._numPieces; ++j) { this._pieces[j] += wire.peerPieces.get(j) } } } RarityMap.prototype._cleanupWireEvents = function (wire) { wire.removeListener('have', this._onWireHave) wire.removeListener('bitfield', this._onWireBitfield) if (wire._onClose) wire.removeListener('close', wire._onClose) wire._onClose = null } function trueFn () { return true }