Class: QuartzTorrent::BlockState
- Inherits:
-
Object
- Object
- QuartzTorrent::BlockState
- Defined in:
- lib/quartz_torrent/blockstate.rb
Overview
Any given torrent is broken into pieces. Those pieces are broken into blocks. This class can be used to keep track of which blocks are currently complete, which have been requested but aren’t available yet, and which are missing.
This class only supports one block size.
Instance Attribute Summary collapse
-
#blockSize ⇒ Object
readonly
Get the block size.
-
#totalLength ⇒ Object
readonly
Total length of the torrent in bytes.
Instance Method Summary collapse
-
#blockCompleted?(blockInfo) ⇒ Boolean
Is the specified block completed (downloaded)?.
-
#completeBlockBitfield ⇒ Object
Get a bitfield representing the completed pieces.
-
#completedLength ⇒ Object
Number of bytes we have downloaded and verified.
-
#completePieceBitfield ⇒ Object
Get a bitfield representing the completed pieces.
-
#createBlockinfoByBlockIndex(blockIndex) ⇒ Object
Create a new BlockInfo object using the specified information.
-
#createBlockinfoByPieceAndBlockIndex(pieceIndex, peersWithPiece, blockIndex) ⇒ Object
Create a new BlockInfo object using the specified information.
-
#createBlockinfoByPieceResponse(pieceIndex, offset, length) ⇒ Object
Create a new BlockInfo object using the specified information.
-
#findRequestableBlocks(classifiedPeers, numToReturn = nil) ⇒ Object
Return a list of BlockInfo objects representing blocjs that should be requested from peers.
-
#initialize(info, initialPieceBitfield, blockSize = 16384) ⇒ BlockState
constructor
Create a new BlockState.
-
#pieceCompleted?(pieceIndex) ⇒ Boolean
Is the specified piece completed (all blocks are downloaded)?.
-
#setBlockCompleted(pieceIndex, blockOffset, bool, clearRequested = :clear_requested) ⇒ Object
Mark a block as completed.
-
#setBlockRequested(blockInfo, bool) ⇒ Object
Set whether the block represented by the passed BlockInfo is requested or not.
-
#setPieceCompleted(pieceIndex, bool) ⇒ Object
Set whether the piece is completed or not.
Constructor Details
#initialize(info, initialPieceBitfield, blockSize = 16384) ⇒ BlockState
Create a new BlockState. Parameter ‘info’ should be the Metainfo.info object for the torrent, ‘initialPieceBitfield’ should be the already-existing pieces, and ‘blockSize’ is the size of blocks.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/quartz_torrent/blockstate.rb', line 50 def initialize(info, initialPieceBitfield, blockSize = 16384) raise "Block size cannot be <= 0" if blockSize <= 0 @logger = LogManager.getLogger("blockstate") @numPieces = initialPieceBitfield.length @pieceSize = info.pieceLen @numPieces = info.pieces.length @blocksPerPiece = (@pieceSize/blockSize + (@pieceSize%blockSize == 0 ? 0 : 1)) @blockSize = blockSize # When calculating the number of blocks, the last piece is likely a partial piece... @numBlocks = @blocksPerPiece * (@numPieces-1) lastPieceLen = (info.dataLength - (@numPieces-1)*@pieceSize) @numBlocks += lastPieceLen / @blockSize @numBlocks += 1 if lastPieceLen % @blockSize != 0 @lastBlockLength = (info.dataLength - (@numBlocks-1)*@blockSize) @totalLength = info.dataLength raise "Initial piece bitfield is the wrong length" if initialPieceBitfield.length != @numPieces raise "Piece size is not divisible by block size" if @pieceSize % blockSize != 0 @completePieces = initialPieceBitfield @completeBlocks = Bitfield.new(@numBlocks) blockIndex = 0 initialPieceBitfield.length.times do |pieceIndex| isSet = initialPieceBitfield.set?(pieceIndex) @blocksPerPiece.times do # The last piece may be a smaller number of blocks. break if blockIndex >= @completeBlocks.length if isSet @completeBlocks.set blockIndex else @completeBlocks.clear blockIndex end blockIndex += 1 end end @requestedBlocks = Bitfield.new(@numBlocks) @requestedBlocks.clearAll @currentPieces = [] end |
Instance Attribute Details
#blockSize ⇒ Object (readonly)
Get the block size
95 96 97 |
# File 'lib/quartz_torrent/blockstate.rb', line 95 def blockSize @blockSize end |
#totalLength ⇒ Object (readonly)
Total length of the torrent in bytes.
98 99 100 |
# File 'lib/quartz_torrent/blockstate.rb', line 98 def totalLength @totalLength end |
Instance Method Details
#blockCompleted?(blockInfo) ⇒ Boolean
Is the specified block completed (downloaded)?
193 194 195 |
# File 'lib/quartz_torrent/blockstate.rb', line 193 def blockCompleted?(blockInfo) @completeBlocks.set? blockInfo.blockIndex end |
#completeBlockBitfield ⇒ Object
Get a bitfield representing the completed pieces.
226 227 228 229 230 |
# File 'lib/quartz_torrent/blockstate.rb', line 226 def completeBlockBitfield result = Bitfield.new(@numBlocks) result.copyFrom(@completeBlocks) result end |
#completedLength ⇒ Object
Number of bytes we have downloaded and verified.
233 234 235 236 237 238 239 240 241 242 |
# File 'lib/quartz_torrent/blockstate.rb', line 233 def completedLength num = @completeBlocks.countSet # Last block may be smaller extra = 0 if @completeBlocks.set?(@completeBlocks.length-1) num -= 1 extra = @lastBlockLength end num*@blockSize + extra end |
#completePieceBitfield ⇒ Object
Get a bitfield representing the completed pieces.
219 220 221 222 223 |
# File 'lib/quartz_torrent/blockstate.rb', line 219 def completePieceBitfield result = Bitfield.new(@numPieces) result.copyFrom(@completePieces) result end |
#createBlockinfoByBlockIndex(blockIndex) ⇒ Object
Create a new BlockInfo object using the specified information. The peers list is empty.
252 253 254 255 256 257 258 |
# File 'lib/quartz_torrent/blockstate.rb', line 252 def createBlockinfoByBlockIndex(blockIndex) pieceIndex = blockIndex / @blockSize offset = (blockIndex % @blocksPerPiece)*@blockSize length = @blockSize raise "offset in piece is not divisible by block size" if offset % @blockSize != 0 BlockInfo.new(pieceIndex, offset, length, [], blockIndex) end |
#createBlockinfoByPieceAndBlockIndex(pieceIndex, peersWithPiece, blockIndex) ⇒ Object
Create a new BlockInfo object using the specified information.
261 262 263 264 265 266 267 |
# File 'lib/quartz_torrent/blockstate.rb', line 261 def createBlockinfoByPieceAndBlockIndex(pieceIndex, peersWithPiece, blockIndex) # If this is the very last block, then it might be smaller than the rest. blockSize = @blockSize blockSize = @lastBlockLength if blockIndex == @numBlocks-1 offsetWithinPiece = (blockIndex % @blocksPerPiece)*@blockSize BlockInfo.new(pieceIndex, offsetWithinPiece, blockSize, peersWithPiece, blockIndex) end |
#createBlockinfoByPieceResponse(pieceIndex, offset, length) ⇒ Object
Create a new BlockInfo object using the specified information. The peers list is empty.
245 246 247 248 249 |
# File 'lib/quartz_torrent/blockstate.rb', line 245 def createBlockinfoByPieceResponse(pieceIndex, offset, length) blockIndex = pieceIndex*@blocksPerPiece + offset/@blockSize raise "offset in piece is not divisible by block size" if offset % @blockSize != 0 BlockInfo.new(pieceIndex, offset, length, [], blockIndex) end |
#findRequestableBlocks(classifiedPeers, numToReturn = nil) ⇒ Object
Return a list of BlockInfo objects representing blocjs that should be requested from peers.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/quartz_torrent/blockstate.rb', line 101 def findRequestableBlocks(classifiedPeers, numToReturn = nil) # Have a list of the current pieces we are working on. Each time this method is # called, check the blocks in the pieces in list order to find the blocks to return # for requesting. If a piece is completed, remove it from this list. If we need more blocks # than there are available in the list, add more pieces to the end of the list (in rarest-first # order). result = [] # Update requestable peers to only be those that we can still request pieces from. peersHavingPiece = computePeersHavingPiece(classifiedPeers) requestable = @completeBlocks.union(@requestedBlocks).compliment! rarityOrder = nil currentPiece = 0 while true if currentPiece >= @currentPieces.length # Add more pieces in rarest-first order. If there are no more pieces, break. rarityOrder = computeRarity(classifiedPeers) if ! rarityOrder added = false rarityOrder.each do |pair| pieceIndex = pair[1] peersWithPiece = peersHavingPiece[pieceIndex] if peersWithPiece && peersWithPiece.size > 0 && !@currentPieces.index(pieceIndex) && ! pieceCompleted?(pieceIndex) @logger.debug "Adding piece #{pieceIndex} to the current downloading list" @currentPieces.push pieceIndex added = true break end end if ! added @logger.debug "There are no more pieces to add to the current downloading list" break end end currentPieceIndex = @currentPieces[currentPiece] if pieceCompleted?(currentPieceIndex) @logger.debug "Piece #{currentPieceIndex} complete so removing it from the current downloading list" @currentPieces.delete_at(currentPiece) next end peersWithPiece = peersHavingPiece[currentPieceIndex] if !peersWithPiece || peersWithPiece.size == 0 @logger.debug "No peers have piece #{currentPieceIndex}" currentPiece += 1 next end eachBlockInPiece(currentPieceIndex) do |blockIndex| if requestable.set?(blockIndex) result.push createBlockinfoByPieceAndBlockIndex(currentPieceIndex, peersWithPiece, blockIndex) break if numToReturn && result.size >= numToReturn end end break if numToReturn && result.size >= numToReturn currentPiece += 1 end result end |
#pieceCompleted?(pieceIndex) ⇒ Boolean
Is the specified piece completed (all blocks are downloaded)?
214 215 216 |
# File 'lib/quartz_torrent/blockstate.rb', line 214 def pieceCompleted?(pieceIndex) @completePieces.set? pieceIndex end |
#setBlockCompleted(pieceIndex, blockOffset, bool, clearRequested = :clear_requested) ⇒ Object
Mark a block as completed. If clearRequested is :clear_requested, then the block is also marked as no longer requested. If this block completes the piece and a block is passed, the pieceIndex is yielded to the block.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/quartz_torrent/blockstate.rb', line 177 def setBlockCompleted(pieceIndex, blockOffset, bool, clearRequested = :clear_requested) bi = blockIndexFromPieceAndOffset(pieceIndex, blockOffset) @requestedBlocks.clear bi if clearRequested == :clear_requested if bool @completeBlocks.set bi if allBlocksInPieceCompleted?(pieceIndex) yield pieceIndex if block_given? @completePieces.set pieceIndex end else @completeBlocks.clear bi @completePieces.clear pieceIndex end end |
#setBlockRequested(blockInfo, bool) ⇒ Object
Set whether the block represented by the passed BlockInfo is requested or not.
166 167 168 169 170 171 172 |
# File 'lib/quartz_torrent/blockstate.rb', line 166 def setBlockRequested(blockInfo, bool) if bool @requestedBlocks.set blockInfo.blockIndex else @requestedBlocks.clear blockInfo.blockIndex end end |
#setPieceCompleted(pieceIndex, bool) ⇒ Object
Set whether the piece is completed or not.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/quartz_torrent/blockstate.rb', line 198 def setPieceCompleted(pieceIndex, bool) eachBlockInPiece(pieceIndex) do |blockIndex| if bool @completeBlocks.set blockIndex else @completeBlocks.clear blockIndex end end if bool @completePieces.set pieceIndex else @completePieces.clear pieceIndex end end |