Class: QuartzTorrent::MetainfoPieceState
- Inherits:
-
Object
- Object
- QuartzTorrent::MetainfoPieceState
- Defined in:
- lib/quartz_torrent/metainfopiecestate.rb
Constant Summary collapse
- BlockSize =
16384
Instance Attribute Summary collapse
-
#infoFileName ⇒ Object
Returns the value of attribute infoFileName.
-
#metainfoLength ⇒ Object
Returns the value of attribute metainfoLength.
Class Method Summary collapse
-
.downloaded(baseDirectory, infoHash) ⇒ Object
Check if the metainfo has already been downloaded successfully during a previous session.
-
.generateInfoFileName(infoHash) ⇒ Object
Return the name of the file where this class will store the Torrent Info struct.
Instance Method Summary collapse
-
#checkResults ⇒ Object
Check the results of savePiece and readPiece.
-
#complete? ⇒ Boolean
Do we have all the pieces of the metadata?.
-
#completedMetainfo ⇒ Object
Get the completed metainfo.
-
#findRequestablePeers(classifiedPeers) ⇒ Object
Return a list of peers from whom we can request pieces.
-
#findRequestablePieces ⇒ Object
Return a list of torrent pieces that can still be requested.
-
#flush ⇒ Object
Flush all pieces to disk.
-
#initialize(baseDirectory, infoHash, metainfoSize = nil, info = nil) ⇒ MetainfoPieceState
constructor
Create a new MetainfoPieceState that can be used to manage downloading the metainfo for a torrent.
-
#markPeerBad(peer) ⇒ Object
Mark the specified peer as ‘bad’.
-
#metainfoCompletedLength ⇒ Object
Return the number of bytes of the metainfo that we have downloaded so far.
-
#pieceCompleted?(pieceIndex) ⇒ Boolean
Return true if the specified piece is completed.
-
#readPiece(pieceIndex) ⇒ Object
Read a piece from disk.
-
#remove ⇒ Object
Remove the metainfo file.
-
#savePiece(pieceIndex, data) ⇒ Object
Save the specified piece to disk asynchronously.
-
#setPieceRequested(pieceIndex, bool) ⇒ Object
Set whether the piece with the passed pieceIndex is requested or not.
-
#stop ⇒ Object
Stop the underlying PieceManager.
-
#wait ⇒ Object
Wait for the next a pending request to complete.
Constructor Details
#initialize(baseDirectory, infoHash, metainfoSize = nil, info = nil) ⇒ MetainfoPieceState
Create a new MetainfoPieceState that can be used to manage downloading the metainfo for a torrent. The metainfo is stored in a file under baseDirectory named <infohash>.info, where <infohash> is infoHash hex-encoded. The parameter metainfoSize should be the size of the metainfo, and info can be used to pass in the complete metainfo Info object if it is available. This is needed for when other peers request the metainfo from us.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 24 def initialize(baseDirectory, infoHash, = nil, info = nil) @logger = LogManager.getLogger("metainfo_piece_state") @requestTimeout = 5 @baseDirectory = baseDirectory @infoFileName = MetainfoPieceState.generateInfoFileName(infoHash) path = infoFilePath completed = MetainfoPieceState.downloaded(baseDirectory, infoHash) = File.size(path) if ! && completed if !completed && info File.open(path, "wb") do |file| bencoded = info.bencode = bencoded.length file.write bencoded # Sanity check testInfoHash = Digest::SHA1.digest( bencoded ) raise "The computed infoHash #{QuartzTorrent.bytesToHex(testInfoHash)} doesn't match the original infoHash #{QuartzTorrent.bytesToHex(infoHash)}" if testInfoHash != infoHash end end raise "Unless the metainfo has already been successfully downloaded or the torrent file is available, the metainfoSize is needed" if ! # We use the PieceManager to manage the pieces of the metainfo file. The PieceManager is designed # for the pieces and blocks of actual torrent data, so we need to build a fake metainfo object that # describes our one metainfo file itself so that we can store the pieces if it on disk. # In this case we map metainfo pieces to 'torrent' pieces, and our blocks are the full length of the # metainfo piece. torrinfo = Metainfo::Info.new torrinfo.pieceLen = BlockSize torrinfo.files = [] torrinfo.files.push Metainfo::FileInfo.new(, @infoFileName) @pieceManager = PieceManager.new(baseDirectory, torrinfo) @pieceManagerRequests = {} @numPieces = /BlockSize @numPieces += 1 if (%BlockSize) != 0 @completePieces = Bitfield.new(@numPieces) @completePieces.setAll if info || completed @lastPieceLength = - (@numPieces-1)*BlockSize @badPeers = PeerHolder.new @requestedPieces = Bitfield.new(@numPieces) @requestedPieces.clearAll @metainfoLength = # Time at which the piece in requestedPiece was requested. Used for timeouts. @pieceRequestTime = [] end |
Instance Attribute Details
#infoFileName ⇒ Object
Returns the value of attribute infoFileName.
106 107 108 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 106 def infoFileName @infoFileName end |
#metainfoLength ⇒ Object
Returns the value of attribute metainfoLength.
107 108 109 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 107 def @metainfoLength end |
Class Method Details
.downloaded(baseDirectory, infoHash) ⇒ Object
Check if the metainfo has already been downloaded successfully during a previous session. Returns the completed, Metainfo::Info object if it is complete, and nil otherwise.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 83 def self.downloaded(baseDirectory, infoHash) logger = LogManager.getLogger("metainfo_piece_state") infoFileName = generateInfoFileName(infoHash) path = "#{baseDirectory}#{File::SEPARATOR}#{infoFileName}" result = nil if File.exists?(path) File.open(path, "rb") do |file| bencoded = file.read # Sanity check testInfoHash = Digest::SHA1.digest( bencoded ) if testInfoHash == infoHash result = Metainfo::Info.createFromBdecode(BEncode.load(bencoded, {:ignore_trailing_junk => 1})) else logger.info "the computed SHA1 hash doesn't match the specified infoHash in #{path}" end end else logger.info "the metainfo file #{path} doesn't exist" end result end |
.generateInfoFileName(infoHash) ⇒ Object
Return the name of the file where this class will store the Torrent Info struct.
246 247 248 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 246 def self.generateInfoFileName(infoHash) "#{QuartzTorrent.bytesToHex(infoHash)}.info" end |
Instance Method Details
#checkResults ⇒ Object
Check the results of savePiece and readPiece. This method returns a list of the PieceManager results.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 160 def checkResults results = [] while true result = @pieceManager.nextResult break if ! result results.push result = @pieceManagerRequests.delete(result.requestId) if ! @logger.error "Can't find metadata for PieceManager request #{result.requestId}" next end if .type == :write if result.successful? @completePieces.set(.data) else @requestedPieces.clear(.data) @pieceRequestTime[.data] = nil @logger.error "Writing metainfo piece failed: #{result.error}" end elsif .type == :read if ! result.successful? @logger.error "Reading metainfo piece failed: #{result.error}" end end end results end |
#complete? ⇒ Boolean
Do we have all the pieces of the metadata?
131 132 133 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 131 def complete? @completePieces.allSet? end |
#completedMetainfo ⇒ Object
Get the completed metainfo. Raises an exception if it’s not yet complete.
136 137 138 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 136 def completedMetainfo raise "Metadata is not yet complete" if ! complete? end |
#findRequestablePeers(classifiedPeers) ⇒ Object
Return a list of peers from whom we can request pieces. These are peers for whom we have an established connection, and are not marked as bad. See markPeerBad.
206 207 208 209 210 211 212 213 214 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 206 def findRequestablePeers(classifiedPeers) result = [] classifiedPeers.establishedPeers.each do |peer| result.push peer if ! @badPeers.findByAddr(peer.trackerPeer.ip, peer.trackerPeer.port) end result end |
#findRequestablePieces ⇒ Object
Return a list of torrent pieces that can still be requested. These are pieces that are not completed and are not requested.
192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 192 def findRequestablePieces piecesRequired = [] removeOldRequests @numPieces.times do |pieceIndex| piecesRequired.push pieceIndex if ! @completePieces.set?(pieceIndex) && ! @requestedPieces.set?(pieceIndex) end piecesRequired end |
#flush ⇒ Object
Flush all pieces to disk
234 235 236 237 238 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 234 def flush id = @pieceManager.flush @pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:flush, nil) @pieceManager.wait end |
#markPeerBad(peer) ⇒ Object
Mark the specified peer as ‘bad’. We won’t try requesting pieces from this peer. Used, for example, when a peer rejects our request for a metadata piece.
229 230 231 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 229 def markPeerBad(peer) @badPeers.add peer end |
#metainfoCompletedLength ⇒ Object
Return the number of bytes of the metainfo that we have downloaded so far.
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 110 def num = @completePieces.countSet # Last block may be smaller extra = 0 if @completePieces.set?(@completePieces.length-1) num -= 1 extra = @lastPieceLength end num*BlockSize + extra end |
#pieceCompleted?(pieceIndex) ⇒ Boolean
Return true if the specified piece is completed. The piece is specified by index.
122 123 124 125 126 127 128 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 122 def pieceCompleted?(pieceIndex) if pieceIndex >= 0 && pieceIndex < @completePieces.length @completePieces.set? pieceIndex else false end end |
#readPiece(pieceIndex) ⇒ Object
Read a piece from disk. This method is asynchronous; it returns a handle that can be later used to retreive the result.
149 150 151 152 153 154 155 156 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 149 def readPiece(pieceIndex) length = BlockSize length = @lastPieceLength if pieceIndex == @numPieces - 1 id = @pieceManager.readBlock pieceIndex, 0, length #result = manager.nextResult @pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:read, pieceIndex) id end |
#remove ⇒ Object
Remove the metainfo file
251 252 253 254 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 251 def remove path = infoFilePath FileUtils.rm path end |
#savePiece(pieceIndex, data) ⇒ Object
Save the specified piece to disk asynchronously.
141 142 143 144 145 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 141 def savePiece(pieceIndex, data) id = @pieceManager.writeBlock pieceIndex, 0, data @pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:write, pieceIndex) id end |
#setPieceRequested(pieceIndex, bool) ⇒ Object
Set whether the piece with the passed pieceIndex is requested or not.
217 218 219 220 221 222 223 224 225 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 217 def setPieceRequested(pieceIndex, bool) if bool @requestedPieces.set pieceIndex @pieceRequestTime[pieceIndex] = Time.new else @requestedPieces.clear pieceIndex @pieceRequestTime[pieceIndex] = nil end end |
#stop ⇒ Object
Stop the underlying PieceManager.
257 258 259 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 257 def stop @pieceManager.stop end |
#wait ⇒ Object
Wait for the next a pending request to complete.
241 242 243 |
# File 'lib/quartz_torrent/metainfopiecestate.rb', line 241 def wait @pieceManager.wait end |