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.
- data/bin/quartztorrent_download +127 -0
- data/bin/quartztorrent_download_curses +841 -0
- data/bin/quartztorrent_magnet_from_torrent +32 -0
- data/bin/quartztorrent_show_info +62 -0
- data/lib/quartz_torrent.rb +2 -0
- data/lib/quartz_torrent/bitfield.rb +314 -0
- data/lib/quartz_torrent/blockstate.rb +354 -0
- data/lib/quartz_torrent/classifiedpeers.rb +95 -0
- data/lib/quartz_torrent/extension.rb +37 -0
- data/lib/quartz_torrent/filemanager.rb +543 -0
- data/lib/quartz_torrent/formatter.rb +92 -0
- data/lib/quartz_torrent/httptrackerclient.rb +121 -0
- data/lib/quartz_torrent/interruptiblesleep.rb +27 -0
- data/lib/quartz_torrent/log.rb +132 -0
- data/lib/quartz_torrent/magnet.rb +92 -0
- data/lib/quartz_torrent/memprofiler.rb +27 -0
- data/lib/quartz_torrent/metainfo.rb +221 -0
- data/lib/quartz_torrent/metainfopiecestate.rb +265 -0
- data/lib/quartz_torrent/peer.rb +145 -0
- data/lib/quartz_torrent/peerclient.rb +1627 -0
- data/lib/quartz_torrent/peerholder.rb +123 -0
- data/lib/quartz_torrent/peermanager.rb +170 -0
- data/lib/quartz_torrent/peermsg.rb +502 -0
- data/lib/quartz_torrent/peermsgserialization.rb +102 -0
- data/lib/quartz_torrent/piecemanagerrequestmetadata.rb +12 -0
- data/lib/quartz_torrent/rate.rb +58 -0
- data/lib/quartz_torrent/ratelimit.rb +48 -0
- data/lib/quartz_torrent/reactor.rb +949 -0
- data/lib/quartz_torrent/regionmap.rb +124 -0
- data/lib/quartz_torrent/semaphore.rb +43 -0
- data/lib/quartz_torrent/trackerclient.rb +271 -0
- data/lib/quartz_torrent/udptrackerclient.rb +70 -0
- data/lib/quartz_torrent/udptrackermsg.rb +250 -0
- data/lib/quartz_torrent/util.rb +100 -0
- metadata +195 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module QuartzTorrent
|
2
|
+
|
3
|
+
# Utility class used for debugging memory leaks. It can be used to count the number of reachable
|
4
|
+
# instances of selected classes.
|
5
|
+
class MemProfiler
|
6
|
+
def initialize
|
7
|
+
@classes = []
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add a class to the list of classes we count the reachable instances of.
|
11
|
+
def trackClass(clazz)
|
12
|
+
@classes.push clazz
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a hashtable keyed by class where the value is the number of that class of object still reachable.
|
16
|
+
def getCounts
|
17
|
+
result = {}
|
18
|
+
@classes.each do |c|
|
19
|
+
count = 0
|
20
|
+
ObjectSpace.each_object(c){ count += 1 }
|
21
|
+
result[c] = count
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'quartz_torrent/log'
|
2
|
+
require 'bencode'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
module QuartzTorrent
|
6
|
+
|
7
|
+
# Torrent metainfo structure. This is what's usually found in .torrent files. This class
|
8
|
+
# generally follows the structure of the metadata format.
|
9
|
+
class Metainfo
|
10
|
+
|
11
|
+
# If 'v' is null, throw an exception. Otherwise return 'v'.
|
12
|
+
def self.valueOrException(v, msg)
|
13
|
+
if ! v
|
14
|
+
LogManager.getLogger("metainfo").error msg
|
15
|
+
raise "Invalid torrent metainfo"
|
16
|
+
end
|
17
|
+
v
|
18
|
+
end
|
19
|
+
|
20
|
+
# Information about a file contained in the torrent.
|
21
|
+
class FileInfo
|
22
|
+
def initialize(length = nil, path = nil)
|
23
|
+
@length = length
|
24
|
+
@path = path
|
25
|
+
end
|
26
|
+
|
27
|
+
# Relative path to the file. For a single-file torrent this is simply the name of the file. For a multi-file torrent,
|
28
|
+
# this is the directory names from the torrent and the filename separated by the file separator.
|
29
|
+
attr_accessor :path
|
30
|
+
# Length of the file.
|
31
|
+
attr_accessor :length
|
32
|
+
|
33
|
+
# Create a FileInfo object from a bdecoded structure.
|
34
|
+
def self.createFromBdecode(bdecode)
|
35
|
+
result = FileInfo.new
|
36
|
+
result.length = Metainfo.valueOrException(bdecode['length'], "Torrent metainfo listed multiple files, and one is missing the length property.")
|
37
|
+
path = Metainfo.valueOrException(bdecode['path'], "Torrent metainfo listed multiple files, and one is missing the path property.")
|
38
|
+
|
39
|
+
result.path = ""
|
40
|
+
path.each do |part|
|
41
|
+
result.path << File::SEPARATOR if result.path.length > 0
|
42
|
+
result.path << part
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# The 'info' property of the torrent metainfo.
|
50
|
+
class Info
|
51
|
+
def initialize
|
52
|
+
@pieceLen = nil
|
53
|
+
@pieces = nil
|
54
|
+
@private = nil
|
55
|
+
# Suggested file or directory name
|
56
|
+
@name = nil
|
57
|
+
# List of file info for files in the torrent. These include the directory name if this is a
|
58
|
+
# multi-file download. For a single-file download the
|
59
|
+
@files = []
|
60
|
+
@logger = LogManager.getLogger("metainfo")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Array of FileInfo objects
|
64
|
+
attr_accessor :files
|
65
|
+
# Suggested file or directory name
|
66
|
+
attr_accessor :name
|
67
|
+
# Length of each piece in bytes. The last piece may be shorter than this.
|
68
|
+
attr_accessor :pieceLen
|
69
|
+
# Array of SHA1 digests of all peices. These digests are in binary format.
|
70
|
+
attr_accessor :pieces
|
71
|
+
# True if no external peer source is allowed.
|
72
|
+
attr_accessor :private
|
73
|
+
|
74
|
+
# Total length of the torrent data in bytes.
|
75
|
+
def dataLength
|
76
|
+
files.reduce(0){ |memo,f| memo + f.length}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Create a FileInfo object from a bdecoded structure.
|
80
|
+
def self.createFromBdecode(infoDict)
|
81
|
+
result = Info.new
|
82
|
+
result.pieceLen = infoDict['piece length']
|
83
|
+
result.private = infoDict['private']
|
84
|
+
result.pieces = parsePieces(Metainfo.valueOrException(infoDict['pieces'], "Torrent metainfo is missing the pieces property."))
|
85
|
+
result.name = Metainfo.valueOrException(infoDict['name'], "Torrent metainfo is missing the name property.")
|
86
|
+
|
87
|
+
if infoDict.has_key? 'files'
|
88
|
+
# This is a multi-file torrent
|
89
|
+
infoDict['files'].each do |file|
|
90
|
+
result.files.push FileInfo.createFromBdecode(file)
|
91
|
+
result.files.last.path = result.name + File::SEPARATOR + result.files.last.path
|
92
|
+
end
|
93
|
+
else
|
94
|
+
# This is a single-file torrent
|
95
|
+
length = Metainfo.valueOrException(infoDict['length'], "Torrent metainfo listed a single file, but it is missing the length property.")
|
96
|
+
result.files.push FileInfo.new(length, result.name)
|
97
|
+
end
|
98
|
+
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
102
|
+
# BEncode this info and return the result.
|
103
|
+
def bencode
|
104
|
+
hash = {}
|
105
|
+
|
106
|
+
raise "Cannot encode Info object with nil pieceLen" if ! @pieceLen
|
107
|
+
raise "Cannot encode Info object with nil name" if ! @name
|
108
|
+
raise "Cannot encode Info object with nil pieces" if ! @pieces
|
109
|
+
raise "Cannot encode Info object with nil files or empty files" if ! @files || @files.empty?
|
110
|
+
|
111
|
+
hash['piece length'] = @pieceLen
|
112
|
+
hash['private'] = @private if @private
|
113
|
+
hash['name'] = @name
|
114
|
+
hash['pieces'] = @pieces.join
|
115
|
+
|
116
|
+
if @files.length > 1
|
117
|
+
# This is a multi-file torrent
|
118
|
+
# When we loaded the torrent, we prepended the 'name' element of the info hash to the path. We need to remove this
|
119
|
+
# name element to end up with the same result.
|
120
|
+
hash['files'] = @files.collect{ |file| {'length' => file.length, 'path' => file.path.split(File::SEPARATOR).drop(1) } }
|
121
|
+
else
|
122
|
+
hash['length'] = @files.first.length
|
123
|
+
end
|
124
|
+
hash.bencode
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
# Parse the pieces of the torrent out of the metainfo.
|
129
|
+
def self.parsePieces(p)
|
130
|
+
# Break into 20-byte portions.
|
131
|
+
if p.length % 20 != 0
|
132
|
+
@logger.error "Torrent metainfo contained a pieces property that was not a multiple of 20 bytes long."
|
133
|
+
raise "Invalid torrent metainfo"
|
134
|
+
end
|
135
|
+
|
136
|
+
result = []
|
137
|
+
index = 0
|
138
|
+
while index < p.length
|
139
|
+
result.push p[index,20].unpack("a20")[0]
|
140
|
+
index += 20
|
141
|
+
end
|
142
|
+
result
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def initialize
|
147
|
+
@info = nil
|
148
|
+
@announce = nil
|
149
|
+
@announceList = nil
|
150
|
+
@creationDate = nil
|
151
|
+
@comment = nil
|
152
|
+
@createdBy = nil
|
153
|
+
@encoding = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
# A Metainfo::Info object
|
157
|
+
attr_accessor :info
|
158
|
+
|
159
|
+
# A 20-byte SHA1 hash of the value of the info key from the metainfo. This is neede when connecting
|
160
|
+
# to the tracker or to a peer.
|
161
|
+
attr_accessor :infoHash
|
162
|
+
|
163
|
+
# Announce URL of the tracker
|
164
|
+
attr_accessor :announce
|
165
|
+
attr_accessor :announceList
|
166
|
+
|
167
|
+
# Creation date as a ruby Time object
|
168
|
+
attr_accessor :creationDate
|
169
|
+
# Comment
|
170
|
+
attr_accessor :comment
|
171
|
+
# Created By
|
172
|
+
attr_accessor :createdBy
|
173
|
+
# The string encoding format used to generate the pieces part of the info dictionary in the .torrent metafile
|
174
|
+
attr_accessor :encoding
|
175
|
+
|
176
|
+
# Create a Metainfo object from the passed bencoded string.
|
177
|
+
def self.createFromString(data)
|
178
|
+
logger = LogManager.getLogger("metainfo")
|
179
|
+
|
180
|
+
decoded = data.bdecode
|
181
|
+
logger.debug "Decoded torrent metainfo: #{decoded.inspect}"
|
182
|
+
result = Metainfo.new
|
183
|
+
result.createdBy = decoded['created by']
|
184
|
+
result.comment = decoded['comment']
|
185
|
+
result.creationDate = decoded['creation date']
|
186
|
+
if result.creationDate
|
187
|
+
if !result.creationDate.is_a?(Integer)
|
188
|
+
if result.creationDate =~ /^\d+$/
|
189
|
+
result.creationDate = result.creationDate.to_i
|
190
|
+
else
|
191
|
+
logger.warn "Torrent metainfo contained invalid date: '#{result.creationDate.class}'"
|
192
|
+
result.creationDate = nil
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
result.creationDate = Time.at(result.creationDate) if result.creationDate
|
197
|
+
end
|
198
|
+
result.encoding = decoded['encoding']
|
199
|
+
result.announce = decoded['announce'].strip
|
200
|
+
result.announceList = decoded['announce-list']
|
201
|
+
result.info = Info.createFromBdecode(decoded['info'])
|
202
|
+
result.infoHash = Digest::SHA1.digest( decoded['info'].bencode )
|
203
|
+
|
204
|
+
result
|
205
|
+
end
|
206
|
+
|
207
|
+
# Create a Metainfo object from the passed IO.
|
208
|
+
def self.createFromIO(io)
|
209
|
+
self.createFromString(io.read)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Create a Metainfo object from the named file.
|
213
|
+
def self.createFromFile(path)
|
214
|
+
result =
|
215
|
+
File.open(path,"rb") do |io|
|
216
|
+
result = self.createFromIO(io)
|
217
|
+
end
|
218
|
+
result
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'quartz_torrent/util'
|
2
|
+
require 'quartz_torrent/filemanager'
|
3
|
+
require 'quartz_torrent/metainfo'
|
4
|
+
require "quartz_torrent/piecemanagerrequestmetadata.rb"
|
5
|
+
require "quartz_torrent/peerholder.rb"
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
# This class is used when we don't have the info struct for the torrent (no .torrent file) and must
|
10
|
+
# download it piece by piece from peers. It keeps track of the pieces we have.
|
11
|
+
#
|
12
|
+
# When a piece is requested from a peer and that peer responds with a reject saying it doesn't have
|
13
|
+
# that metainfo piece, we take a simple approach and mark that peer as bad, and don't request any more
|
14
|
+
# pieces from that peer even though they may have other pieces. This simplifies the code.
|
15
|
+
module QuartzTorrent
|
16
|
+
class MetainfoPieceState
|
17
|
+
BlockSize = 16384
|
18
|
+
|
19
|
+
# Create a new MetainfoPieceState that can be used to manage downloading the metainfo
|
20
|
+
# for a torrent. The metainfo is stored in a file under baseDirectory named <infohash>.info,
|
21
|
+
# where <infohash> is infoHash hex-encoded. The parameter metainfoSize should be the size of
|
22
|
+
# the metainfo, and info can be used to pass in the complete metainfo Info object if it is available. This
|
23
|
+
# is needed for when other peers request the metainfo from us.
|
24
|
+
def initialize(baseDirectory, infoHash, metainfoSize = nil, info = nil)
|
25
|
+
|
26
|
+
@logger = LogManager.getLogger("metainfo_piece_state")
|
27
|
+
|
28
|
+
@requestTimeout = 5
|
29
|
+
@baseDirectory = baseDirectory
|
30
|
+
@infoFileName = MetainfoPieceState.generateInfoFileName(infoHash)
|
31
|
+
|
32
|
+
path = infoFilePath
|
33
|
+
|
34
|
+
completed = MetainfoPieceState.downloaded(baseDirectory, infoHash)
|
35
|
+
metainfoSize = File.size(path) if ! metainfoSize && completed
|
36
|
+
|
37
|
+
if !completed && info
|
38
|
+
File.open(path, "wb") do |file|
|
39
|
+
bencoded = info.bencode
|
40
|
+
metainfoSize = bencoded.length
|
41
|
+
file.write bencoded
|
42
|
+
# Sanity check
|
43
|
+
testInfoHash = Digest::SHA1.digest( bencoded )
|
44
|
+
raise "The computed infoHash #{QuartzTorrent.bytesToHex(testInfoHash)} doesn't match the original infoHash #{QuartzTorrent.bytesToHex(infoHash)}" if testInfoHash != infoHash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
raise "Unless the metainfo has already been successfully downloaded or the torrent file is available, the metainfoSize is needed" if ! metainfoSize
|
49
|
+
|
50
|
+
# We use the PieceManager to manage the pieces of the metainfo file. The PieceManager is designed
|
51
|
+
# for the pieces and blocks of actual torrent data, so we need to build a fake metainfo object that
|
52
|
+
# describes our one metainfo file itself so that we can store the pieces if it on disk.
|
53
|
+
# In this case we map metainfo pieces to 'torrent' pieces, and our blocks are the full length of the
|
54
|
+
# metainfo piece.
|
55
|
+
torrinfo = Metainfo::Info.new
|
56
|
+
torrinfo.pieceLen = BlockSize
|
57
|
+
torrinfo.files = []
|
58
|
+
torrinfo.files.push Metainfo::FileInfo.new(metainfoSize, @infoFileName)
|
59
|
+
|
60
|
+
|
61
|
+
@pieceManager = PieceManager.new(baseDirectory, torrinfo)
|
62
|
+
@pieceManagerRequests = {}
|
63
|
+
|
64
|
+
@numPieces = metainfoSize/BlockSize
|
65
|
+
@numPieces += 1 if (metainfoSize%BlockSize) != 0
|
66
|
+
@completePieces = Bitfield.new(@numPieces)
|
67
|
+
@completePieces.setAll if info || completed
|
68
|
+
|
69
|
+
@lastPieceLength = metainfoSize - (@numPieces-1)*BlockSize
|
70
|
+
|
71
|
+
@badPeers = PeerHolder.new
|
72
|
+
@requestedPieces = Bitfield.new(@numPieces)
|
73
|
+
@requestedPieces.clearAll
|
74
|
+
|
75
|
+
@metainfoLength = metainfoSize
|
76
|
+
|
77
|
+
# Time at which the piece in requestedPiece was requested. Used for timeouts.
|
78
|
+
@pieceRequestTime = []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Check if the metainfo has already been downloaded successfully during a previous session.
|
82
|
+
# Returns the completed, Metainfo::Info object if it is complete, and nil otherwise.
|
83
|
+
def self.downloaded(baseDirectory, infoHash)
|
84
|
+
logger = LogManager.getLogger("metainfo_piece_state")
|
85
|
+
infoFileName = generateInfoFileName(infoHash)
|
86
|
+
path = "#{baseDirectory}#{File::SEPARATOR}#{infoFileName}"
|
87
|
+
|
88
|
+
result = nil
|
89
|
+
if File.exists?(path)
|
90
|
+
File.open(path, "rb") do |file|
|
91
|
+
bencoded = file.read
|
92
|
+
# Sanity check
|
93
|
+
testInfoHash = Digest::SHA1.digest( bencoded )
|
94
|
+
if testInfoHash == infoHash
|
95
|
+
result = Metainfo::Info.createFromBdecode(bencoded.bdecode)
|
96
|
+
else
|
97
|
+
logger.info "the computed SHA1 hash doesn't match the specified infoHash in #{path}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
logger.info "the metainfo file #{path} doesn't exist"
|
102
|
+
end
|
103
|
+
result
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_accessor :infoFileName
|
107
|
+
attr_accessor :metainfoLength
|
108
|
+
|
109
|
+
# Return the number of bytes of the metainfo that we have downloaded so far.
|
110
|
+
def metainfoCompletedLength
|
111
|
+
num = @completePieces.countSet
|
112
|
+
# Last block may be smaller
|
113
|
+
extra = 0
|
114
|
+
if @completePieces.set?(@completePieces.length-1)
|
115
|
+
num -= 1
|
116
|
+
extra = @lastPieceLength
|
117
|
+
end
|
118
|
+
num*BlockSize + extra
|
119
|
+
end
|
120
|
+
|
121
|
+
def pieceCompleted?(pieceIndex)
|
122
|
+
@completePieces.set? pieceIndex
|
123
|
+
end
|
124
|
+
|
125
|
+
# Do we have all the pieces of the metadata?
|
126
|
+
def complete?
|
127
|
+
@completePieces.allSet?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the completed metainfo. Raises an exception if it's not yet complete.
|
131
|
+
def completedMetainfo
|
132
|
+
raise "Metadata is not yet complete" if ! complete?
|
133
|
+
end
|
134
|
+
|
135
|
+
def savePiece(pieceIndex, data)
|
136
|
+
id = @pieceManager.writeBlock pieceIndex, 0, data
|
137
|
+
@pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:write, pieceIndex)
|
138
|
+
id
|
139
|
+
end
|
140
|
+
|
141
|
+
def readPiece(pieceIndex)
|
142
|
+
length = BlockSize
|
143
|
+
length = @lastPieceLength if pieceIndex == @numPieces - 1
|
144
|
+
id = @pieceManager.readBlock pieceIndex, 0, length
|
145
|
+
#result = manager.nextResult
|
146
|
+
@pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:read, pieceIndex)
|
147
|
+
id
|
148
|
+
end
|
149
|
+
|
150
|
+
# Check the results of savePiece and readPiece. This method returns a list
|
151
|
+
# of the PieceManager results.
|
152
|
+
def checkResults
|
153
|
+
results = []
|
154
|
+
while true
|
155
|
+
result = @pieceManager.nextResult
|
156
|
+
break if ! result
|
157
|
+
|
158
|
+
results.push result
|
159
|
+
|
160
|
+
metaData = @pieceManagerRequests.delete(result.requestId)
|
161
|
+
if ! metaData
|
162
|
+
@logger.error "Can't find metadata for PieceManager request #{result.requestId}"
|
163
|
+
next
|
164
|
+
end
|
165
|
+
|
166
|
+
if metaData.type == :write
|
167
|
+
if result.successful?
|
168
|
+
@completePieces.set(metaData.data)
|
169
|
+
else
|
170
|
+
@requestedPieces.clear(metaData.data)
|
171
|
+
@pieceRequestTime[metaData.data] = nil
|
172
|
+
@logger.error "Writing metainfo piece failed: #{result.error}"
|
173
|
+
end
|
174
|
+
elsif metaData.type == :read
|
175
|
+
if ! result.successful?
|
176
|
+
@logger.error "Reading metainfo piece failed: #{result.error}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
results
|
181
|
+
end
|
182
|
+
|
183
|
+
def findRequestablePieces
|
184
|
+
piecesRequired = []
|
185
|
+
|
186
|
+
removeOldRequests
|
187
|
+
|
188
|
+
@numPieces.times do |pieceIndex|
|
189
|
+
piecesRequired.push pieceIndex if ! @completePieces.set?(pieceIndex) && ! @requestedPieces.set?(pieceIndex)
|
190
|
+
end
|
191
|
+
|
192
|
+
piecesRequired
|
193
|
+
end
|
194
|
+
|
195
|
+
def findRequestablePeers(classifiedPeers)
|
196
|
+
result = []
|
197
|
+
|
198
|
+
classifiedPeers.establishedPeers.each do |peer|
|
199
|
+
result.push peer if ! @badPeers.findByAddr(peer.trackerPeer.ip, peer.trackerPeer.port)
|
200
|
+
end
|
201
|
+
|
202
|
+
result
|
203
|
+
end
|
204
|
+
|
205
|
+
# Set whether the piece with the passed pieceIndex is requested or not.
|
206
|
+
def setPieceRequested(pieceIndex, bool)
|
207
|
+
if bool
|
208
|
+
@requestedPieces.set pieceIndex
|
209
|
+
@pieceRequestTime[pieceIndex] = Time.new
|
210
|
+
else
|
211
|
+
@requestedPieces.clear pieceIndex
|
212
|
+
@pieceRequestTime[pieceIndex] = nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def markPeerBad(peer)
|
217
|
+
@badPeers.add peer
|
218
|
+
end
|
219
|
+
|
220
|
+
# Flush all pieces to disk
|
221
|
+
def flush
|
222
|
+
id = @pieceManager.flush
|
223
|
+
@pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:flush, nil)
|
224
|
+
@pieceManager.wait
|
225
|
+
end
|
226
|
+
|
227
|
+
# Wait for the next a pending request to complete.
|
228
|
+
def wait
|
229
|
+
@pieceManager.wait
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.generateInfoFileName(infoHash)
|
233
|
+
"#{QuartzTorrent.bytesToHex(infoHash)}.info"
|
234
|
+
end
|
235
|
+
|
236
|
+
# Remove the metainfo file
|
237
|
+
def remove
|
238
|
+
path = infoFilePath
|
239
|
+
FileUtils.rm path
|
240
|
+
end
|
241
|
+
|
242
|
+
# Stop the underlying PieceManager.
|
243
|
+
def stop
|
244
|
+
@pieceManager.stop
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
# Remove any pending requests after a timeout.
|
249
|
+
def removeOldRequests
|
250
|
+
now = Time.new
|
251
|
+
@requestedPieces.length.times do |i|
|
252
|
+
if @requestedPieces.set? i
|
253
|
+
if now - @pieceRequestTime[i] > @requestTimeout
|
254
|
+
@requestedPieces.clear i
|
255
|
+
@pieceRequestTime[i] = nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def infoFilePath
|
262
|
+
"#{@baseDirectory}#{File::SEPARATOR}#{@infoFileName}"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|