Class: QuartzTorrent::BlockState

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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

#blockSizeObject (readonly)

Get the block size



95
96
97
# File 'lib/quartz_torrent/blockstate.rb', line 95

def blockSize
  @blockSize
end

#totalLengthObject (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)?

Returns:

  • (Boolean)


193
194
195
# File 'lib/quartz_torrent/blockstate.rb', line 193

def blockCompleted?(blockInfo)
  @completeBlocks.set? blockInfo.blockIndex
end

#completeBlockBitfieldObject

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

#completedLengthObject

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

#completePieceBitfieldObject

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)?

Returns:

  • (Boolean)


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