quartz_torrent 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. data/bin/quartztorrent_download +127 -0
  2. data/bin/quartztorrent_download_curses +841 -0
  3. data/bin/quartztorrent_magnet_from_torrent +32 -0
  4. data/bin/quartztorrent_show_info +62 -0
  5. data/lib/quartz_torrent.rb +2 -0
  6. data/lib/quartz_torrent/bitfield.rb +314 -0
  7. data/lib/quartz_torrent/blockstate.rb +354 -0
  8. data/lib/quartz_torrent/classifiedpeers.rb +95 -0
  9. data/lib/quartz_torrent/extension.rb +37 -0
  10. data/lib/quartz_torrent/filemanager.rb +543 -0
  11. data/lib/quartz_torrent/formatter.rb +92 -0
  12. data/lib/quartz_torrent/httptrackerclient.rb +121 -0
  13. data/lib/quartz_torrent/interruptiblesleep.rb +27 -0
  14. data/lib/quartz_torrent/log.rb +132 -0
  15. data/lib/quartz_torrent/magnet.rb +92 -0
  16. data/lib/quartz_torrent/memprofiler.rb +27 -0
  17. data/lib/quartz_torrent/metainfo.rb +221 -0
  18. data/lib/quartz_torrent/metainfopiecestate.rb +265 -0
  19. data/lib/quartz_torrent/peer.rb +145 -0
  20. data/lib/quartz_torrent/peerclient.rb +1627 -0
  21. data/lib/quartz_torrent/peerholder.rb +123 -0
  22. data/lib/quartz_torrent/peermanager.rb +170 -0
  23. data/lib/quartz_torrent/peermsg.rb +502 -0
  24. data/lib/quartz_torrent/peermsgserialization.rb +102 -0
  25. data/lib/quartz_torrent/piecemanagerrequestmetadata.rb +12 -0
  26. data/lib/quartz_torrent/rate.rb +58 -0
  27. data/lib/quartz_torrent/ratelimit.rb +48 -0
  28. data/lib/quartz_torrent/reactor.rb +949 -0
  29. data/lib/quartz_torrent/regionmap.rb +124 -0
  30. data/lib/quartz_torrent/semaphore.rb +43 -0
  31. data/lib/quartz_torrent/trackerclient.rb +271 -0
  32. data/lib/quartz_torrent/udptrackerclient.rb +70 -0
  33. data/lib/quartz_torrent/udptrackermsg.rb +250 -0
  34. data/lib/quartz_torrent/util.rb +100 -0
  35. metadata +195 -0
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ require 'getoptlong'
3
+ require 'quartz_torrent/magnet'
4
+ require 'quartz_torrent/metainfo'
5
+
6
+ include QuartzTorrent
7
+
8
+ def help
9
+ puts "Usage: #{$0} <torrentfile>"
10
+ puts "Output a magnet link based on the information found in torrentfile (which should be a .torrent file)"
11
+ puts
12
+ end
13
+
14
+ opts = GetoptLong.new(
15
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT],
16
+ )
17
+
18
+ opts.each do |opt, arg|
19
+ if opt == '--help'
20
+ help
21
+ end
22
+ end
23
+
24
+ torrent = ARGV[0]
25
+ if ! torrent
26
+ help
27
+ exit 1
28
+ end
29
+
30
+ metainfo = Metainfo.createFromFile(torrent)
31
+ puts MagnetURI.encodeFromMetainfo(metainfo)
32
+
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ require 'getoptlong'
3
+ require 'quartz_torrent'
4
+
5
+ include QuartzTorrent
6
+
7
+ def help
8
+ puts "Usage: #{$0} <torrentfile or infofile>"
9
+ puts "Output information about the specified .torrent or .info file"
10
+ puts
11
+ end
12
+
13
+ opts = GetoptLong.new(
14
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT],
15
+ )
16
+
17
+ opts.each do |opt, arg|
18
+ if opt == '--help'
19
+ help
20
+ end
21
+ end
22
+
23
+ torrent = ARGV[0]
24
+ if ! torrent
25
+ help
26
+ exit 1
27
+ end
28
+
29
+ info = nil
30
+ begin
31
+ metainfo = Metainfo.createFromFile(torrent)
32
+ puts "Info Hash: #{bytesToHex(metainfo.infoHash)}"
33
+ puts "Announce: #{metainfo.announce}" if metainfo.announce
34
+ if metainfo.announceList
35
+ puts "Announce list: "
36
+ metainfo.announceList.each { |e| puts " #{e}" }
37
+ end
38
+ puts "Creation date: #{metainfo.creationDate}" if metainfo.creationDate
39
+ puts "Comment: #{metainfo.comment}" if metainfo.comment
40
+ puts "Created by: #{metainfo.createdBy}" if metainfo.createdBy
41
+ puts "Encoding: #{metainfo.encoding}" if metainfo.encoding
42
+
43
+ info = metainfo.info
44
+ rescue
45
+ # Doesn't seem to be a complete .torrent file. Maybe it is just the info part.
46
+ File.open(torrent, "r") do |file|
47
+ bencoded = file.read
48
+ info = Metainfo::Info.createFromBdecode(bencoded.bdecode)
49
+ end
50
+ end
51
+
52
+ if info
53
+ puts "Info:"
54
+ puts " Name: #{info.name}" if info.name
55
+ puts " Piece Length: #{info.pieceLen}" if info.pieceLen
56
+ puts " Size of pieces array: #{info.pieces.size}" if info.pieces
57
+ puts " Private: #{info.private}" if info.private
58
+ puts " Files (length/path): "
59
+ info.files.each do |file|
60
+ puts " #{file.length} #{file.path}"
61
+ end
62
+ end
@@ -0,0 +1,2 @@
1
+ # Main include file for the quartz torrent library.
2
+ require 'quartz_torrent/peerclient.rb'
@@ -0,0 +1,314 @@
1
+ module QuartzTorrent
2
+
3
+ # A bitfield that allows querying and setting individual bits, as well as serializing and unserializing.
4
+ class Bitfield
5
+ =begin
6
+ # Code used to generate lookup table:
7
+ print "["
8
+ 256.times do |i|
9
+ # count bits set
10
+ o = i
11
+ c = 0
12
+ 8.times do
13
+ c += 1 if i & 1 > 0
14
+ i >>= 1
15
+ end
16
+
17
+ print "," if o > 0
18
+ puts if o > 0 && o % 20 == 0
19
+ print "#{c}"
20
+ end
21
+ puts "]"
22
+
23
+ =end
24
+
25
+ # Lookup table. The value at index i is the number of bits on in byte with value i.
26
+ @@bitsSetInByteLookup =
27
+ [0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,
28
+ 2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,
29
+ 2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,
30
+ 4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,
31
+ 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,
32
+ 3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,
33
+ 4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,
34
+ 3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,
35
+ 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,
36
+ 4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,
37
+ 3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,
38
+ 5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,
39
+ 4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8]
40
+
41
+
42
+ # Create a bitfield of length 'length' in bits.
43
+ def initialize(length)
44
+ @length = length
45
+ if @length == 0
46
+ @data = Array.new(0)
47
+ else
48
+ @data = Array.new((length-1)/8+1, 0)
49
+ end
50
+ end
51
+
52
+ # Length of the Bitfield in bits.
53
+ attr_reader :length
54
+
55
+
56
+ # Length of the Bitfield in bytes.
57
+ def byteLength
58
+ @data.length
59
+ end
60
+
61
+ # Set the bit at index 'bit' to 1.
62
+ def set(bit)
63
+ quotient = bit >> 3
64
+ remainder = bit & 0x7
65
+ mask = 0x80 >> remainder
66
+
67
+ raise "Bit #{bit} out of range of bitfield with length #{length}" if quotient >= @data.length
68
+ @data[quotient] |= mask
69
+ end
70
+
71
+ # Clear the bit at index 'bit' to 0.
72
+ def clear(bit)
73
+ quotient = bit >> 3
74
+ remainder = bit & 0x7
75
+ mask = ~(0x80 >> remainder)
76
+
77
+ raise "Bit #{bit} out of range of bitfield with length #{length}" if quotient >= @data.length
78
+ @data[quotient] &= mask
79
+ end
80
+
81
+ # Returns true if the bit is set, false otherwise.
82
+ def set?(bit)
83
+ #raise "Bit #{bit} out of range of bitfield with length #{length}" if quotient >= @data.length
84
+ (@data[bit >> 3] << (bit & 0x7)) & 0x80 > 0
85
+ end
86
+
87
+ # Are all bits in the Bitfield set?
88
+ def allSet?
89
+ # Check all but last byte quickly
90
+ (@data.length-1).times do |i|
91
+ return false if @data[i] != 0xff
92
+ end
93
+ # Check last byte slowly
94
+ toCheck = @length % 8
95
+ toCheck = 8 if toCheck == 0
96
+ ((@length-toCheck)..(@length-1)).each do |i|
97
+ return false if ! set?(i)
98
+ end
99
+ true
100
+ end
101
+
102
+ # Are all bits in the Bitfield clear?
103
+ def allClear?
104
+ # Check all but last byte quickly
105
+ (@data.length-1).times do |i|
106
+ return false if @data[i] != 0
107
+ end
108
+ # Check last byte slowly
109
+ toCheck = @length % 8
110
+ toCheck = 8 if toCheck == 0
111
+ ((@length-toCheck)..(@length-1)).each do |i|
112
+ return false if set?(i)
113
+ end
114
+ true
115
+ end
116
+
117
+ # Set all bits in the field to 1.
118
+ def setAll
119
+ @data.fill(0xff)
120
+ end
121
+
122
+ # Clear all bits in the field to 0.
123
+ def clearAll
124
+ @data.fill(0x00)
125
+ end
126
+
127
+ # Calculate the union of this bitfield and the passed bitfield, and
128
+ # return the result as a new bitfield.
129
+ def union(bitfield)
130
+ raise "That's not a bitfield" if ! bitfield.is_a?(Bitfield)
131
+ raise "bitfield lengths must be equal" if ! bitfield.length == length
132
+
133
+ result = Bitfield.new(length)
134
+ (@data.length).times do |i|
135
+ result.data[i] = @data[i] | bitfield.data[i]
136
+ end
137
+ result
138
+ end
139
+
140
+ # Calculate the intersection of this bitfield and the passed bitfield, and
141
+ # return the result as a new bitfield.
142
+ def intersection(bitfield)
143
+ raise "That's not a bitfield" if ! bitfield.is_a?(Bitfield)
144
+ raise "bitfield lengths must be equal" if ! bitfield.length == length
145
+
146
+ newbitfield = Bitfield.new(length)
147
+ newbitfield.copyFrom(self)
148
+ newbitfield.intersection!(bitfield)
149
+ end
150
+
151
+ # Update this bitfield to be the intersection of this bitfield and the passed bitfield.
152
+ def intersection!(bitfield)
153
+ raise "That's not a bitfield" if ! bitfield.is_a?(Bitfield)
154
+ raise "bitfield lengths must be equal" if ! bitfield.length == length
155
+
156
+ (@data.length).times do |i|
157
+ @data[i] = @data[i] & bitfield.data[i]
158
+ end
159
+ self
160
+ end
161
+
162
+ # Set the contents of this bitfield to be the same as the passed bitfield. An exception is
163
+ # thrown if the passed bitfield is smaller than this.
164
+ def copyFrom(bitfield)
165
+ raise "Source bitfield is too small (#{bitfield.length} < #{length})" if bitfield.length < length
166
+ (@data.length).times do |i|
167
+ @data[i] = bitfield.data[i]
168
+ end
169
+ end
170
+
171
+ # Calculate the compliment of this bitfield, and
172
+ # return the result as a new bitfield.
173
+ def compliment
174
+ bitfield = Bitfield.new(length)
175
+ bitfield.copyFrom(self)
176
+ bitfield.compliment!
177
+ end
178
+
179
+ # Update this bitfield to be the compliment of itself.
180
+ def compliment!
181
+ @data.collect!{ |e| ~e }
182
+ self
183
+ end
184
+
185
+ # Serialize this bitfield as a string.
186
+ def serialize
187
+ @data.pack "C*"
188
+ end
189
+
190
+ # Unserialize this bitfield from a string.
191
+ def unserialize(s)
192
+ @data = s.unpack "C*"
193
+ end
194
+
195
+ # Return a display string representing the bitfield.
196
+ def to_s(groupsOf = 8)
197
+ groupsOf = 8 if groupsOf == 0
198
+ s = ""
199
+ length.times do |i|
200
+ s << (set?(i) ? "1" : "0")
201
+ s << " " if i % groupsOf == 0
202
+ end
203
+ s
204
+ end
205
+
206
+ # Count the number of bits that are set.
207
+ def countSet
208
+ @data.reduce(0){ |memo, v| memo + @@bitsSetInByteLookup[v] }
209
+ end
210
+
211
+ protected
212
+ def data
213
+ @data
214
+ end
215
+ end
216
+
217
+ # A bitfield that is always empty.
218
+ class EmptyBitfield < Bitfield
219
+ def initialize
220
+ @length = 0
221
+ end
222
+
223
+ # Length of the Bitfield in bits.
224
+ attr_reader :length
225
+
226
+ # Length of the Bitfield in bytes.
227
+ def byteLength
228
+ 0
229
+ end
230
+
231
+ # Set the bit at index 'bit' to 1.
232
+ def set(bit)
233
+ end
234
+
235
+ # Clear the bit at index 'bit' to 0.
236
+ def clear(bit)
237
+ end
238
+
239
+ # Returns true if the bit is set, false otherwise.
240
+ def set?(bit)
241
+ false
242
+ end
243
+
244
+ # Are all bits in the Bitfield set?
245
+ def allSet?
246
+ false
247
+ end
248
+
249
+ # Are all bits in the Bitfield clear?
250
+ def allClear?
251
+ true
252
+ end
253
+
254
+ # Set all bits in the field to 1.
255
+ def setAll
256
+ end
257
+
258
+ # Clear all bits in the field to 0.
259
+ def clearAll
260
+ end
261
+
262
+ # Calculate the union of this bitfield and the passed bitfield, and
263
+ # return the result as a new bitfield.
264
+ def union(bitfield)
265
+ self
266
+ end
267
+
268
+ # Calculate the intersection of this bitfield and the passed bitfield, and
269
+ # return the result as a new bitfield.
270
+ def intersection(bitfield)
271
+ self
272
+ end
273
+
274
+ # Update this bitfield to be the intersection of this bitfield and the passed bitfield.
275
+ def intersection!(bitfield)
276
+ self
277
+ end
278
+
279
+ # Set the contents of this bitfield to be the same as the passed bitfield. An exception is
280
+ # thrown if the passed bitfield is smaller than this.
281
+ def copyFrom(bitfield)
282
+ end
283
+
284
+ # Calculate the compliment of this bitfield, and
285
+ # return the result as a new bitfield.
286
+ def compliment
287
+ self
288
+ end
289
+
290
+ # Update this bitfield to be the compliment of itself.
291
+ def compliment!
292
+ self
293
+ end
294
+
295
+ # Serialize this bitfield as a string.
296
+ def serialize
297
+ ""
298
+ end
299
+
300
+ # Unserialize this bitfield from a string.
301
+ def unserialize(s)
302
+ end
303
+
304
+ # Return a display string representing the bitfield.
305
+ def to_s(groupsOf = 8)
306
+ "empty"
307
+ end
308
+
309
+ # Count the number of bits that are set. Slow: could use lookup table.
310
+ def countSet
311
+ 0
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,354 @@
1
+ require 'quartz_torrent/util'
2
+ require 'quartz_torrent/bitfield'
3
+
4
+ module QuartzTorrent
5
+
6
+ # Information representing a single block (a part of a piece)
7
+ class BlockInfo
8
+ def initialize(pieceIndex, offset, length, peers, blockIndex)
9
+ @pieceIndex = pieceIndex
10
+ @offset = offset
11
+ @length = length
12
+ @peers = peers
13
+ @blockIndex = blockIndex
14
+ end
15
+
16
+ # The index of the piece that this block belongs to.
17
+ attr_accessor :pieceIndex
18
+ # Offset (in bytes) within the piece where the block begins.
19
+ attr_accessor :offset
20
+ # Length of the block in bytes.
21
+ attr_accessor :length
22
+ # A list of peers that have the piece this block belongs to. This block
23
+ # can be requested from these peers.
24
+ attr_accessor :peers
25
+ # Index of the block in units of the blockSize from the BlockState that created
26
+ # this object.
27
+ attr_accessor :blockIndex
28
+
29
+ # Return a new Bittorrent Request message that requests this block.
30
+ def getRequest
31
+ m = Request.new
32
+ m.pieceIndex = @pieceIndex
33
+ m.blockOffset = @offset
34
+ m.blockLength = @length
35
+ m
36
+ end
37
+
38
+ end
39
+
40
+ # Any given torrent is broken into pieces. Those pieces are broken into blocks.
41
+ # This class can be used to keep track of which blocks are currently complete, which
42
+ # have been requested but aren't available yet, and which are missing.
43
+ #
44
+ # This class only supports one block size.
45
+ class BlockState
46
+
47
+ # Create a new BlockState. Parameter 'info' should be the Metainfo.info object
48
+ # for the torrent, 'initialPieceBitfield' should be the already-existing pieces,
49
+ # and 'blockSize' is the size of blocks.
50
+ def initialize(info, initialPieceBitfield, blockSize = 16384)
51
+ raise "Block size cannot be <= 0" if blockSize <= 0
52
+
53
+ @logger = LogManager.getLogger("blockstate")
54
+
55
+ @numPieces = initialPieceBitfield.length
56
+ @pieceSize = info.pieceLen
57
+ @numPieces = info.pieces.length
58
+ @blocksPerPiece = (@pieceSize/blockSize + (@pieceSize%blockSize == 0 ? 0 : 1))
59
+ @blockSize = blockSize
60
+ # When calculating the number of blocks, the last piece is likely a partial piece...
61
+ @numBlocks = @blocksPerPiece * (@numPieces-1)
62
+ lastPieceLen = (info.dataLength - (@numPieces-1)*@pieceSize)
63
+ @numBlocks += lastPieceLen / @blockSize
64
+ @numBlocks += 1 if lastPieceLen % @blockSize != 0
65
+ @lastBlockLength = (info.dataLength - (@numBlocks-1)*@blockSize)
66
+ @totalLength = info.dataLength
67
+
68
+ raise "Initial piece bitfield is the wrong length" if initialPieceBitfield.length != @numPieces
69
+ raise "Piece size is not divisible by block size" if @pieceSize % blockSize != 0
70
+
71
+ @completePieces = initialPieceBitfield
72
+ @completeBlocks = Bitfield.new(@numBlocks)
73
+ blockIndex = 0
74
+ initialPieceBitfield.length.times do |pieceIndex|
75
+ isSet = initialPieceBitfield.set?(pieceIndex)
76
+ @blocksPerPiece.times do
77
+ # The last piece may be a smaller number of blocks.
78
+ break if blockIndex >= @completeBlocks.length
79
+
80
+ if isSet
81
+ @completeBlocks.set blockIndex
82
+ else
83
+ @completeBlocks.clear blockIndex
84
+ end
85
+ blockIndex += 1
86
+ end
87
+ end
88
+
89
+ @requestedBlocks = Bitfield.new(@numBlocks)
90
+ @requestedBlocks.clearAll
91
+ @currentPieces = []
92
+ end
93
+
94
+ # Get the block size
95
+ attr_reader :blockSize
96
+
97
+ # Total length of the torrent in bytes.
98
+ attr_reader :totalLength
99
+
100
+ # Return a list of BlockInfo objects representing blocjs that should be requested from peers.
101
+ def findRequestableBlocks(classifiedPeers, numToReturn = nil)
102
+ # Have a list of the current pieces we are working on. Each time this method is
103
+ # called, check the blocks in the pieces in list order to find the blocks to return
104
+ # for requesting. If a piece is completed, remove it from this list. If we need more blocks
105
+ # than there are available in the list, add more pieces to the end of the list (in rarest-first
106
+ # order).
107
+ result = []
108
+
109
+ # Update requestable peers to only be those that we can still request pieces from.
110
+ peersHavingPiece = computePeersHavingPiece(classifiedPeers)
111
+ requestable = @completeBlocks.union(@requestedBlocks).compliment!
112
+ rarityOrder = nil
113
+
114
+ currentPiece = 0
115
+ while true
116
+ if currentPiece >= @currentPieces.length
117
+ # Add more pieces in rarest-first order. If there are no more pieces, break.
118
+ rarityOrder = computeRarity(classifiedPeers) if ! rarityOrder
119
+ added = false
120
+ rarityOrder.each do |pair|
121
+ pieceIndex = pair[1]
122
+ peersWithPiece = peersHavingPiece[pieceIndex]
123
+ if peersWithPiece && peersWithPiece.size > 0 && !@currentPieces.index(pieceIndex) && ! pieceCompleted?(pieceIndex)
124
+ @logger.debug "Adding piece #{pieceIndex} to the current downloading list"
125
+ @currentPieces.push pieceIndex
126
+ added = true
127
+ break
128
+ end
129
+ end
130
+ if ! added
131
+ @logger.debug "There are no more pieces to add to the current downloading list"
132
+ break
133
+ end
134
+ end
135
+
136
+ currentPieceIndex = @currentPieces[currentPiece]
137
+
138
+ if pieceCompleted?(currentPieceIndex)
139
+ @logger.debug "Piece #{currentPieceIndex} complete so removing it from the current downloading list"
140
+ @currentPieces.delete_at(currentPiece)
141
+ next
142
+ end
143
+
144
+ peersWithPiece = peersHavingPiece[currentPieceIndex]
145
+ if !peersWithPiece || peersWithPiece.size == 0
146
+ @logger.debug "No peers have piece #{currentPieceIndex}"
147
+ currentPiece += 1
148
+ next
149
+ end
150
+
151
+ eachBlockInPiece(currentPieceIndex) do |blockIndex|
152
+ if requestable.set?(blockIndex)
153
+ result.push createBlockinfoByPieceAndBlockIndex(currentPieceIndex, peersWithPiece, blockIndex)
154
+ break if numToReturn && result.size >= numToReturn
155
+ end
156
+ end
157
+
158
+ break if numToReturn && result.size >= numToReturn
159
+ currentPiece += 1
160
+ end
161
+
162
+ result
163
+ end
164
+
165
+ # Set whether the block represented by the passed BlockInfo is requested or not.
166
+ def setBlockRequested(blockInfo, bool)
167
+ if bool
168
+ @requestedBlocks.set blockInfo.blockIndex
169
+ else
170
+ @requestedBlocks.clear blockInfo.blockIndex
171
+ end
172
+ end
173
+
174
+ # Mark a block as completed. If clearRequested is :clear_requested, then the block is also marked
175
+ # as no longer requested. If this block completes the piece and a block is passed, the pieceIndex
176
+ # is yielded to the block.
177
+ def setBlockCompleted(pieceIndex, blockOffset, bool, clearRequested = :clear_requested)
178
+ bi = blockIndexFromPieceAndOffset(pieceIndex, blockOffset)
179
+ @requestedBlocks.clear bi if clearRequested == :clear_requested
180
+ if bool
181
+ @completeBlocks.set bi
182
+ if allBlocksInPieceCompleted?(pieceIndex)
183
+ yield pieceIndex if block_given?
184
+ @completePieces.set pieceIndex
185
+ end
186
+ else
187
+ @completeBlocks.clear bi
188
+ @completePieces.clear pieceIndex
189
+ end
190
+ end
191
+
192
+ # Is the specified block completed (downloaded)?
193
+ def blockCompleted?(blockInfo)
194
+ @completeBlocks.set? blockInfo.blockIndex
195
+ end
196
+
197
+ # Set whether the piece is completed or not.
198
+ def setPieceCompleted(pieceIndex, bool)
199
+ eachBlockInPiece(pieceIndex) do |blockIndex|
200
+ if bool
201
+ @completeBlocks.set blockIndex
202
+ else
203
+ @completeBlocks.clear blockIndex
204
+ end
205
+ end
206
+ if bool
207
+ @completePieces.set pieceIndex
208
+ else
209
+ @completePieces.clear pieceIndex
210
+ end
211
+ end
212
+
213
+ # Is the specified piece completed (all blocks are downloaded)?
214
+ def pieceCompleted?(pieceIndex)
215
+ @completePieces.set? pieceIndex
216
+ end
217
+
218
+ # Get a bitfield representing the completed pieces.
219
+ def completePieceBitfield
220
+ result = Bitfield.new(@numPieces)
221
+ result.copyFrom(@completePieces)
222
+ result
223
+ end
224
+
225
+ # Number of bytes we have downloaded and verified.
226
+ def completedLength
227
+ num = @completeBlocks.countSet
228
+ # Last block may be smaller
229
+ extra = 0
230
+ if @completeBlocks.set?(@completeBlocks.length-1)
231
+ num -= 1
232
+ extra = @lastBlockLength
233
+ end
234
+ num*@blockSize + extra
235
+ end
236
+
237
+ # Create a new BlockInfo object using the specified information. The peers list is empty.
238
+ def createBlockinfoByPieceResponse(pieceIndex, offset, length)
239
+ blockIndex = pieceIndex*@blocksPerPiece + offset/@blockSize
240
+ raise "offset in piece is not divisible by block size" if offset % @blockSize != 0
241
+ BlockInfo.new(pieceIndex, offset, length, [], blockIndex)
242
+ end
243
+
244
+ # Create a new BlockInfo object using the specified information. The peers list is empty.
245
+ def createBlockinfoByBlockIndex(blockIndex)
246
+ pieceIndex = blockIndex / @blockSize
247
+ offset = (blockIndex % @blocksPerPiece)*@blockSize
248
+ length = @blockSize
249
+ raise "offset in piece is not divisible by block size" if offset % @blockSize != 0
250
+ BlockInfo.new(pieceIndex, offset, length, [], blockIndex)
251
+ end
252
+
253
+ # Create a new BlockInfo object using the specified information.
254
+ def createBlockinfoByPieceAndBlockIndex(pieceIndex, peersWithPiece, blockIndex)
255
+ # If this is the very last block, then it might be smaller than the rest.
256
+ blockSize = @blockSize
257
+ blockSize = @lastBlockLength if blockIndex == @numBlocks-1
258
+ offsetWithinPiece = (blockIndex % @blocksPerPiece)*@blockSize
259
+ BlockInfo.new(pieceIndex, offsetWithinPiece, blockSize, peersWithPiece, blockIndex)
260
+ end
261
+
262
+ private
263
+ # Yield to a block each block index in a piece.
264
+ def eachBlockInPiece(pieceIndex)
265
+ (pieceIndex*@blocksPerPiece).upto(pieceIndex*@blocksPerPiece+@blocksPerPiece-1) do |blockIndex|
266
+ break if blockIndex >= @numBlocks
267
+ yield blockIndex
268
+ end
269
+ end
270
+
271
+ def allBlocksInPieceCompleted?(pieceIndex)
272
+ complete = true
273
+ eachBlockInPiece(pieceIndex) do |blockIndex|
274
+ if ! @completeBlocks.set?(blockIndex)
275
+ complete = false
276
+ break
277
+ end
278
+ end
279
+
280
+ complete
281
+ end
282
+
283
+ def blockIndexFromPieceAndOffset(pieceIndex, blockOffset)
284
+ pieceIndex*@blocksPerPiece + blockOffset/@blockSize
285
+ end
286
+
287
+ # Return an array indexed by piece index where each element
288
+ # is a list of peers with that piece.
289
+ def computePeersHavingPiece(classifiedPeers)
290
+ # Make a list of each peer having the specified piece
291
+ peersHavingPiece = Array.new(@numPieces)
292
+ # This first list represents rarity by number if peers having that piece. 1 = rarest.
293
+ classifiedPeers.requestablePeers.each do |peer|
294
+ @numPieces.times do |i|
295
+ if peer.bitfield.set?(i)
296
+ if peersHavingPiece[i]
297
+ peersHavingPiece[i].push peer
298
+ else
299
+ peersHavingPiece[i] = [peer]
300
+ end
301
+ end
302
+ end
303
+ end
304
+ peersHavingPiece
305
+ end
306
+
307
+ # Compute an array representing the relative rarity of each piece of the torrent.
308
+ # The returned array has one entry for each piece of the torrent. Each entry is a two-element
309
+ # array where the first element is the rarity of the piece where lower is more rare (i.e. 0 is rarest
310
+ # and represents 0 peers with that piece), and the second element in the entry is the piece index.
311
+ # The returned array is sorted in order of ascending rarity value (rarest is first), but within each
312
+ # class of the same rarity value the piece indices are randomized. For example, rarity 1 elements are
313
+ # all before rarity 2 elements, but the piece indices with rarity 1 are in a random order.
314
+ def computeRarity(classifiedPeers)
315
+ # 1. Make a list of the rarity of pieces.
316
+ rarity = Array.new(@numPieces,0)
317
+ # This first list represents rarity by number if peers having that piece. 1 = rarest.
318
+ classifiedPeers.requestablePeers.each do |peer|
319
+ @numPieces.times do |i|
320
+ rarity[i] += 1 if peer.bitfield.set?(i)
321
+ end
322
+ end
323
+
324
+ # 2. Make a new list that indexes the first list by order of descending rarity.
325
+ rarityOrder = Array.new(@numPieces)
326
+ # The elements of this second list are pairs. The first element in the pair is the rarity, second is the piece index.
327
+ @numPieces.times do |i|
328
+ rarityOrder[i] = [rarity[i],i]
329
+ end
330
+ rarityOrder.sort!{ |a,b| a[0] <=> b[0] }
331
+
332
+ # 3. Randomize the list order within classes of the same rarity.
333
+ left = 0
334
+ leftVal = rarityOrder[left][0]
335
+ @numPieces.times do |i|
336
+ if rarityOrder[i][0] != leftVal
337
+ # New range
338
+ rangeLen = i-left+1
339
+
340
+ QuartzTorrent.arrayShuffleRange!(rarityOrder, left, rangeLen)
341
+
342
+ left = i+1
343
+ leftVal = rarityOrder[left][0] if left < @numPieces
344
+ end
345
+ end
346
+ QuartzTorrent.arrayShuffleRange!(rarityOrder, left, @numPieces-left) if left < @numPieces
347
+
348
+ rarityOrder
349
+ end
350
+
351
+ end
352
+
353
+ end
354
+