quartz_torrent 0.0.2 → 0.0.3

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Jeffrey A. Williams
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  QuartzTorrent -- A Ruby Bittorrent Library
2
2
  ==========================================
3
3
 
4
+ [![Gem Version](https://badge.fury.io/rb/quartz_torrent.png)](http://badge.fury.io/rb/quartz\_torrent)
5
+
4
6
  Like the title says, a bittorrent library implemented in pure ruby. Currently
5
7
  the library works, but is still alpha.
6
8
 
@@ -52,6 +52,17 @@ puts "]"
52
52
  # Length of the Bitfield in bits.
53
53
  attr_reader :length
54
54
 
55
+ # Adjust the length of the bitfield. This might be necessary if we had to unserialize the bitfield from
56
+ # a serialized array of bytes.
57
+ def length=(l)
58
+ byteLen = 0
59
+ byteLen = (l-1)/8+1 if l > 0
60
+
61
+ raise "Length adjustment would change size of underlying array" if byteLen != byteLength
62
+
63
+ @length = l
64
+ end
65
+
55
66
 
56
67
  # Length of the Bitfield in bytes.
57
68
  def byteLength
@@ -1,13 +1,15 @@
1
1
  require 'quartz_torrent/peermsg'
2
2
  module QuartzTorrent
3
- # This class contains constants that represent our numbering of the Bittorrent extensions we support.
3
+ # This class contains constants that represent our numbering of the Bittorrent peer-protocol extensions we support.
4
4
  # It also has some utility methods related to extensions.
5
5
  class Extension
6
6
 
7
+ # The metadata extension (BEP 9)
7
8
  MetadataExtensionId = 1
8
9
 
9
- # Parameter info should be the metadata info struct. It is used to determine the size to send
10
- # when negotiating the metadata extension.
10
+ # Create an ExtendedHandshake object based on the passed Torrent metadata info struct.
11
+ # @param info The torrent metadata info struct. It is used to determine the size to send
12
+ # when negotiating the metadata extension.
11
13
  def self.createExtendedHandshake(info)
12
14
  msg = ExtendedHandshake.new
13
15
 
@@ -26,6 +28,8 @@ module QuartzTorrent
26
28
  msg
27
29
  end
28
30
 
31
+ # Get the class to use to serialize and unserialize the specified Bittorent extension. Returns nil if we don't support that extension.
32
+ # @param info The name of a bittorrent extension as specified in the BEP, for example 'ut_metadata'.
29
33
  def self.peerMsgClassForExtensionName(info)
30
34
  if info == 'ut_metadata'
31
35
  ExtendedMetaInfo
@@ -2,8 +2,11 @@ module QuartzTorrent
2
2
  # Class that can be used to format different quantities into
3
3
  # human readable strings.
4
4
  class Formatter
5
+ # Number of bytes in a Kilobyte.
5
6
  Kb = 1024
7
+ # Number of bytes in a Megabyte.
6
8
  Meg = 1024*Kb
9
+ # Number of bytes in a Gigabyte.
7
10
  Gig = 1024*Meg
8
11
 
9
12
  # Format a size in bytes.
@@ -20,6 +20,7 @@ module QuartzTorrent
20
20
 
21
21
  attr_reader :raw
22
22
 
23
+ # Returns true if the passed string is a magnet URI, false otherwise.
23
24
  def self.magnetURI?(str)
24
25
  str =~ @@regex
25
26
  end
@@ -118,6 +118,7 @@ module QuartzTorrent
118
118
  num*BlockSize + extra
119
119
  end
120
120
 
121
+ # Return true if the specified piece is completed. The piece is specified by index.
121
122
  def pieceCompleted?(pieceIndex)
122
123
  @completePieces.set? pieceIndex
123
124
  end
@@ -132,12 +133,15 @@ module QuartzTorrent
132
133
  raise "Metadata is not yet complete" if ! complete?
133
134
  end
134
135
 
136
+ # Save the specified piece to disk asynchronously.
135
137
  def savePiece(pieceIndex, data)
136
138
  id = @pieceManager.writeBlock pieceIndex, 0, data
137
139
  @pieceManagerRequests[id] = PieceManagerRequestMetadata.new(:write, pieceIndex)
138
140
  id
139
141
  end
140
142
 
143
+ # Read a piece from disk. This method is asynchronous; it returns a handle that can be later
144
+ # used to retreive the result.
141
145
  def readPiece(pieceIndex)
142
146
  length = BlockSize
143
147
  length = @lastPieceLength if pieceIndex == @numPieces - 1
@@ -180,6 +184,7 @@ module QuartzTorrent
180
184
  results
181
185
  end
182
186
 
187
+ # Return a list of torrent pieces that can still be requested. These are pieces that are not completed and are not requested.
183
188
  def findRequestablePieces
184
189
  piecesRequired = []
185
190
 
@@ -192,6 +197,8 @@ module QuartzTorrent
192
197
  piecesRequired
193
198
  end
194
199
 
200
+ # Return a list of peers from whom we can request pieces. These are peers for whom we have an established connection, and
201
+ # are not marked as bad. See markPeerBad.
195
202
  def findRequestablePeers(classifiedPeers)
196
203
  result = []
197
204
 
@@ -213,6 +220,8 @@ module QuartzTorrent
213
220
  end
214
221
  end
215
222
 
223
+ # Mark the specified peer as 'bad'. We won't try requesting pieces from this peer. Used, for example, when
224
+ # a peer rejects our request for a metadata piece.
216
225
  def markPeerBad(peer)
217
226
  @badPeers.add peer
218
227
  end
@@ -229,6 +238,7 @@ module QuartzTorrent
229
238
  @pieceManager.wait
230
239
  end
231
240
 
241
+ # Return the name of the file where this class will store the Torrent Info struct.
232
242
  def self.generateInfoFileName(infoHash)
233
243
  "#{QuartzTorrent.bytesToHex(infoHash)}.info"
234
244
  end
@@ -2,9 +2,11 @@ require 'quartz_torrent/rate'
2
2
  require 'quartz_torrent/peermsgserialization'
3
3
 
4
4
  module QuartzTorrent
5
+ # This class represents a torrent peer.
5
6
  class Peer
6
7
  @@stateChangeListeners = []
7
8
 
9
+ # Create a new Peer using the information from the passed TrackerPeer object.
8
10
  def initialize(trackerPeer)
9
11
  @trackerPeer = trackerPeer
10
12
  @amChoked = true
@@ -86,6 +88,7 @@ module QuartzTorrent
86
88
  # A PeerWireMessageSerializer that can unserialize and serialize messages to and from this peer.
87
89
  attr_accessor :peerMsgSerializer
88
90
 
91
+ # Return a string representation of the peer.
89
92
  def to_s
90
93
  @trackerPeer.to_s
91
94
  end
@@ -95,6 +98,7 @@ module QuartzTorrent
95
98
  @@stateChangeListeners.push l
96
99
  end
97
100
 
101
+ # Equate peers.
98
102
  def eql?(o)
99
103
  o.is_a?(Peer) && trackerPeer.eql?(o.trackerPeer)
100
104
  end
@@ -4,7 +4,6 @@ require "quartz_torrent/peermsg.rb"
4
4
  require "quartz_torrent/reactor.rb"
5
5
  require "quartz_torrent/util.rb"
6
6
  require "quartz_torrent/classifiedpeers.rb"
7
- require "quartz_torrent/classifiedpeers.rb"
8
7
  require "quartz_torrent/peerholder.rb"
9
8
  require "quartz_torrent/peermanager.rb"
10
9
  require "quartz_torrent/blockstate.rb"
@@ -38,6 +37,8 @@ module QuartzTorrent
38
37
  @peerManager = PeerManager.new
39
38
  @pieceManagerRequestMetadata = {}
40
39
  @pieceManagerMetainfoRequestMetadata = {}
40
+ @bytesDownloadedDataOnly = 0
41
+ @bytesUploadedDataOnly = 0
41
42
  @bytesDownloaded = 0
42
43
  @bytesUploaded = 0
43
44
  @magnet = nil
@@ -72,6 +73,8 @@ module QuartzTorrent
72
73
  # Metadata associated with outstanding requests to the PieceManager responsible for the pieces of the torrent metainfo.
73
74
  attr_accessor :pieceManagerMetainfoRequestMetadata
74
75
  attr_accessor :peerChangeListener
76
+ attr_accessor :bytesDownloadedDataOnly
77
+ attr_accessor :bytesUploadedDataOnly
75
78
  attr_accessor :bytesDownloaded
76
79
  attr_accessor :bytesUploaded
77
80
  attr_accessor :state
@@ -143,6 +146,10 @@ module QuartzTorrent
143
146
  # After we have completed downloading a torrent, we will continue to upload until we have
144
147
  # uploaded ratio * torrent_size bytes. If nil, no limit on upload.
145
148
  attr_accessor :ratio
149
+ attr_accessor :bytesUploadedDataOnly
150
+ attr_accessor :bytesDownloadedDataOnly
151
+ attr_accessor :bytesUploaded
152
+ attr_accessor :bytesDownloaded
146
153
 
147
154
  # Update the data in this TorrentDataDelegate from the torrentData
148
155
  # object that it was created from. TODO: What if that torrentData is now gone?
@@ -160,6 +167,8 @@ module QuartzTorrent
160
167
  def fillFrom(torrentData)
161
168
  @infoHash = torrentData.infoHash
162
169
  @info = torrentData.info
170
+ @bytesUploadedDataOnly = torrentData.bytesUploadedDataOnly
171
+ @bytesDownloadedDataOnly = torrentData.bytesDownloadedDataOnly
163
172
  @bytesUploaded = torrentData.bytesUploaded
164
173
  @bytesDownloaded = torrentData.bytesDownloaded
165
174
 
@@ -296,7 +305,7 @@ module QuartzTorrent
296
305
  @logger.warn "Asked to set download rate limit for a non-existent torrent #{QuartzTorrent.bytesToHex(infoHash)}"
297
306
  return
298
307
  end
299
-
308
+
300
309
  if bytesPerSecond
301
310
  if ! torrentData.downRateLimit
302
311
  torrentData.downRateLimit = RateLimit.new(bytesPerSecond, 2*bytesPerSecond, 0)
@@ -306,6 +315,13 @@ module QuartzTorrent
306
315
  else
307
316
  torrentData.downRateLimit = nil
308
317
  end
318
+
319
+ torrentData.peers.all.each do |peer|
320
+ withPeersIo(peer, "setting download rate limit") do |io|
321
+ io.readRateLimit = torrentData.downRateLimit
322
+ end
323
+ end
324
+
309
325
  end
310
326
 
311
327
  # Set the upload rate limit. Pass nil as the bytesPerSecond to disable the limit.
@@ -325,6 +341,12 @@ module QuartzTorrent
325
341
  else
326
342
  torrentData.upRateLimit = nil
327
343
  end
344
+
345
+ torrentData.peers.all.each do |peer|
346
+ withPeersIo(peer, "setting upload rate limit") do |io|
347
+ io.writeRateLimit = torrentData.upRateLimit
348
+ end
349
+ end
328
350
  end
329
351
 
330
352
  # Set the upload ratio. Pass nil to disable
@@ -503,7 +525,10 @@ module QuartzTorrent
503
525
  close
504
526
  return
505
527
  end
528
+
506
529
  peer.updateUploadRate msg
530
+ torrentData = @torrentData[peer.infoHash]
531
+ torrentData.bytesDownloaded += msg.length if torrentData
507
532
  @logger.debug "Peer #{peer} upload rate: #{peer.uploadRate.value} data only: #{peer.uploadRateDataOnly.value}"
508
533
  end
509
534
 
@@ -801,7 +826,7 @@ module QuartzTorrent
801
826
  end
802
827
 
803
828
  if torrentData.state == :uploading && (torrentData.state != :paused) && torrentData.ratio
804
- if torrentData.bytesUploaded >= torrentData.ratio*torrentData.blockState.totalLength
829
+ if torrentData.bytesUploadedDataOnly >= torrentData.ratio*torrentData.blockState.totalLength
805
830
  @logger.info "Pausing torrent due to upload ratio limit." if torrentData.metainfoPieceState.complete?
806
831
  setPaused(infoHash, true)
807
832
  return
@@ -978,7 +1003,7 @@ module QuartzTorrent
978
1003
  peer.requestedBlocks.delete blockInfo.blockIndex
979
1004
  # Block is marked as not requested when hash is confirmed
980
1005
 
981
- torrentData.bytesDownloaded += msg.data.length
1006
+ torrentData.bytesDownloadedDataOnly += msg.data.length
982
1007
  id = torrentData.pieceManager.writeBlock(msg.pieceIndex, msg.blockOffset, msg.data)
983
1008
  torrentData.pieceManagerRequestMetadata[id] = PieceManagerRequestMetadata.new(:write, msg)
984
1009
  end
@@ -1011,6 +1036,7 @@ module QuartzTorrent
1011
1036
  end
1012
1037
 
1013
1038
  peer.bitfield = msg.bitfield
1039
+ peer.bitfield.length = torrentData.info.pieces.length
1014
1040
 
1015
1041
  if ! torrentData.blockState
1016
1042
  @logger.warn "Bitfield: no blockstate yet."
@@ -1100,8 +1126,9 @@ module QuartzTorrent
1100
1126
  msg.pieceIndex = readRequestMetadata.requestMsg.pieceIndex
1101
1127
  msg.blockOffset = readRequestMetadata.requestMsg.blockOffset
1102
1128
  msg.data = result.data
1129
+ @logger.debug "Sending block to #{peer}: piece #{msg.pieceIndex} offset #{msg.blockOffset} length #{msg.data.length}"
1103
1130
  sendMessageToPeer msg, io, peer
1104
- torrentData.bytesUploaded += msg.data.length
1131
+ torrentData.bytesUploadedDataOnly += msg.data.length
1105
1132
  @logger.debug "Sending piece to peer"
1106
1133
  end
1107
1134
  else
@@ -1313,12 +1340,14 @@ module QuartzTorrent
1313
1340
 
1314
1341
  def sendMessageToPeer(msg, io, peer)
1315
1342
  peer.updateDownloadRate(msg)
1343
+ torrentData = @torrentData[peer.infoHash]
1344
+ torrentData.bytesUploaded += msg.length if torrentData
1345
+
1316
1346
  begin
1317
1347
  peer.peerMsgSerializer.serializeTo(msg, io)
1318
1348
  rescue
1319
1349
  @logger.warn "Sending message to peer #{peer} failed: #{$!.message}"
1320
1350
  end
1321
- msg.serializeTo io
1322
1351
  end
1323
1352
 
1324
1353
  # Update our internal peer list for this torrent from the tracker client
@@ -1602,8 +1631,8 @@ module QuartzTorrent
1602
1631
  result = TrackerDynamicRequestParams.new(dataLength)
1603
1632
  if torrentData && torrentData.blockState
1604
1633
  result.left = torrentData.blockState.totalLength - torrentData.blockState.completedLength
1605
- result.downloaded = torrentData.bytesDownloaded
1606
- result.uploaded = torrentData.bytesUploaded
1634
+ result.downloaded = torrentData.bytesDownloadedDataOnly
1635
+ result.uploaded = torrentData.bytesUploadedDataOnly
1607
1636
  end
1608
1637
  result
1609
1638
  end
@@ -2,6 +2,7 @@ require 'quartz_torrent/peer.rb'
2
2
  require 'quartz_torrent/util'
3
3
 
4
4
  module QuartzTorrent
5
+ # A container class for holding torrent peers. Allows lookup by different properties.
5
6
  class PeerHolder
6
7
  def initialize
7
8
  @peersById = {}
@@ -10,20 +11,24 @@ module QuartzTorrent
10
11
  @log = LogManager.getLogger("peerholder")
11
12
  end
12
13
 
14
+ # Find a peer by its trackerpeer's peerid. This is the id returned by the tracker, and may be nil.
13
15
  def findById(peerId)
14
16
  @peersById[peerId]
15
17
  end
16
18
 
19
+ # Find a peer by its IP address and port.
17
20
  def findByAddr(ip, port)
18
21
  @peersByAddr[ip + port.to_s]
19
22
  end
20
23
 
24
+ # Find all peers related to the torrent with the passed infoHash.
21
25
  def findByInfoHash(infoHash)
22
26
  l = @peersByInfoHash[infoHash]
23
27
  l = [] if ! l
24
28
  l
25
29
  end
26
30
 
31
+ # Add a peer to the PeerHolder.
27
32
  def add(peer)
28
33
  raise "Peer must have it's infoHash set." if ! peer.infoHash
29
34
 
@@ -44,7 +49,7 @@ module QuartzTorrent
44
49
  @peersByInfoHash.pushToList(peer.infoHash, peer)
45
50
  end
46
51
 
47
- # This peer, which previously had no id, has finished handshaking and now has an ID.
52
+ # Set the id for a peer. This peer, which previously had no id, has finished handshaking and now has an ID.
48
53
  def idSet(peer)
49
54
  @peersById.each do |e|
50
55
  return if e.eql?(peer)
@@ -52,6 +57,7 @@ module QuartzTorrent
52
57
  @peersById.pushToList(peer.trackerPeer.id, peer)
53
58
  end
54
59
 
60
+ # Delete the specified peer from the PeerHolder.
55
61
  def delete(peer)
56
62
  @peersByAddr.delete byAddrKey(peer)
57
63
 
@@ -82,14 +88,17 @@ module QuartzTorrent
82
88
  end
83
89
  end
84
90
 
91
+ # Return the list of all peers.
85
92
  def all
86
93
  @peersByAddr.values
87
94
  end
88
95
 
96
+ # Return the number of peers in the holder.
89
97
  def size
90
98
  @peersByAddr.size
91
99
  end
92
100
 
101
+ # Output a string representation of the PeerHolder, for debugging purposes.
93
102
  def to_s(infoHash = nil)
94
103
  def makeFlags(peer)
95
104
  s = "["
@@ -87,12 +87,15 @@ module QuartzTorrent
87
87
  MessageCancel = 8
88
88
  MessageExtended = 20
89
89
 
90
+ # Create a new PeerWireMessage with the specified message id.
90
91
  def initialize(messageId)
91
92
  @messageId = messageId
92
93
  end
93
94
 
95
+ # Get the message id of this message (the message type from the peer wire protocol).
94
96
  attr_reader :messageId
95
97
 
98
+ # Serialize this message to the passed io
96
99
  def serializeTo(io)
97
100
  io.write [payloadLength+1].pack("N")
98
101
  io.write [@messageId].pack("C")
@@ -108,6 +111,7 @@ module QuartzTorrent
108
111
  payloadLength + 5
109
112
  end
110
113
 
114
+ # Unserialize the message from the passed string.
111
115
  def unserialize(payload)
112
116
  raise "Subclasses of PeerWireMessage must implement unserialize but #{self.class} didn't"
113
117
  end
@@ -21,7 +21,7 @@ module QuartzTorrent
21
21
  def serverInit(metainfo, addr, port)
22
22
  end
23
23
 
24
- # Event handler. The current io is ready for reading
24
+ # Event handler: The current io is ready for reading.
25
25
  # If you will write to the same io from both this handler and the timerExpired handler,
26
26
  # you must make sure to perform all writing at once in this handler. If not then
27
27
  # the writes from the timer handler may be interleaved.
@@ -43,19 +43,22 @@ module QuartzTorrent
43
43
  def recvData(metainfo)
44
44
  end
45
45
 
46
- # Event handler. A timer has expired
46
+ # Event handler: a timer has expired.
47
+ # @param metainfo The metainfo associated with the timer, that was passed to scheduleTimer.
47
48
  def timerExpired(metainfo)
48
49
  end
49
50
 
50
- # Event handler. Error during read, or write. Connection errors are reported separately in connectError
51
+ # Event handler: an error occurred during read or write. Connection errors are reported separately in connectError
52
+ # @param metainfo The metainfo associated with the io.
51
53
  def error(metainfo, details)
52
54
  end
53
55
 
54
- # Event handler. Error during connection, or connection timed out.
56
+ # Event handler: an error occurred during connection, or connection timed out.
57
+ # @param metainfo The metainfo associated with the io, as passed to the connect method.
55
58
  def connectError(metainfo, details)
56
59
  end
57
60
 
58
- # Event handler. This is called for events added using addUserEvent to the reactor.
61
+ # Event handler: this is called for events added using addUserEvent to the reactor.
59
62
  def userEvent(event)
60
63
  end
61
64
 
@@ -63,6 +66,12 @@ module QuartzTorrent
63
66
  attr_accessor :reactor
64
67
 
65
68
  # Schedule a timer.
69
+ # @param duration The duration of the timer in seconds
70
+ # @param metainfo The metainfo to associate with the timer
71
+ # @param recurring If true when the timer duration expires, the timer will be rescheduled. If false the timer
72
+ # will not be rescheduled.
73
+ # @param immed If true then the timer will expire immediately (the next pass through the event loop). If the timer
74
+ # is also recurring it will then be rescheduled according to it's duratoin.
66
75
  def scheduleTimer(duration, metainfo = nil, recurring = true, immed = false)
67
76
  @reactor.scheduleTimer(duration, metainfo, recurring, immed) if @reactor
68
77
  end
@@ -73,48 +82,52 @@ module QuartzTorrent
73
82
  @reactor.cancelTimer(timerInfo) if @reactor
74
83
  end
75
84
 
85
+ # Create a TCP connection to the specified host and port. Associate the passed metainfo with the IO representing the connection.
76
86
  def connect(addr, port, metainfo, timeout = nil)
77
87
  @reactor.connect(addr, port, metainfo, timeout) if @reactor
78
88
  end
79
89
 
80
- # Write data to the current io
90
+ # Write data to the current io.
81
91
  def write(data)
82
92
  @reactor.write(data) if @reactor
83
93
  end
84
94
 
85
- # Read len bytes from the current io
95
+ # Read len bytes from the current io. This is meant to be called from one of the event handler methods.
86
96
  def read(len)
87
97
  result = ''
88
98
  result = @reactor.read(len) if @reactor
89
99
  result
90
100
  end
91
101
 
92
- # Shutdown the reactor
102
+ # Shutdown the reactor.
93
103
  def stopReactor
94
104
  @reactor.stop if @reactor
95
105
  end
96
106
 
97
- # Check if stop has been called on the reactor
107
+ # Check if stop has been called on the reactor.
98
108
  def stopped?
99
109
  @stopped
100
110
  end
101
111
 
102
- # Close the current io
112
+ # Close the current io. This is meant to be called from one of the event handler methods.
103
113
  def close(io = nil)
104
114
  @reactor.close(io) if @reactor
105
115
  end
106
116
 
117
+ # Return the current IO object. This is meant to be called from one of the event handler methods.
118
+ # The returned object is actually an IoFacade, a wrapper around the IO object.
107
119
  def currentIo
108
120
  result = nil
109
121
  result = @reactor.currentIo if @reactor
110
122
  result
111
123
  end
112
124
 
113
- # Find an io by metainfo
125
+ # Find an io by metainfo.
114
126
  def findIoByMetainfo(metainfo)
115
127
  @reactor.findIoByMetainfo metainfo if metainfo && @reactor
116
128
  end
117
-
129
+
130
+ # Set the metainfo for the current io. This is meant to be called from one of the event handler methods.
118
131
  def setMetaInfo(metainfo)
119
132
  @reactor.setMetaInfo metainfo if @reactor
120
133
  end
@@ -130,6 +143,8 @@ module QuartzTorrent
130
143
  end
131
144
  end
132
145
 
146
+ # Simple class used to buffer output for an IO until it's ready for writing. This is not part of the
147
+ # public API; it's used internally by the IOInfo class.
133
148
  class OutputBuffer
134
149
  # Create a new OutputBuffer for the specified IO. The parameter seekable should be
135
150
  # true or false. If true, then this output buffer will support seek
@@ -144,14 +159,17 @@ module QuartzTorrent
144
159
  end
145
160
  end
146
161
 
162
+ # Is the buffer empty?
147
163
  def empty?
148
164
  @buffer.length == 0
149
165
  end
150
166
 
167
+ # Number of bytes in the buffer.
151
168
  def size
152
169
  @buffer.length
153
170
  end
154
171
 
172
+ # Append data to the buffer.
155
173
  def append(data)
156
174
  if ! @seekable
157
175
  @buffer << data
@@ -208,6 +226,7 @@ module QuartzTorrent
208
226
  hash.delete @io
209
227
  end
210
228
 
229
+ # Read `length` bytes.
211
230
  def read(length)
212
231
  data = ''
213
232
  while data.length < length
@@ -249,6 +268,7 @@ module QuartzTorrent
249
268
  data
250
269
  end
251
270
 
271
+ # Write data to the IO.
252
272
  def write(data)
253
273
  # Issue: what about write, read, read on files opened for read/write? Write should happen at offset X, but reads moved to offset N. Since writes
254
274
  # are buffered, write may happen after read which means write will happen at the wrong offset N. Can fix by always seeking (if needed) before writes to the
@@ -259,37 +279,61 @@ module QuartzTorrent
259
279
  data.length
260
280
  end
261
281
 
282
+ # Seek on the io.
283
+ # @param amount amount to seek.
284
+ # @param whence one of the whence constants from IO::seek.
262
285
  def seek(amount, whence)
263
286
  @io.seek amount, whence if @ioInfo.seekable?
264
287
  end
265
288
 
289
+ # Flush data.
266
290
  def flush
267
291
  @io.flush
268
292
  end
269
293
 
294
+ # Close the io.
270
295
  def close
271
296
  @io.close
272
297
  end
273
298
 
299
+ # Check if the io is closed.
274
300
  def closed?
275
301
  @io.closed?
276
302
  end
303
+
304
+ def readRateLimit=(rate)
305
+ raise "The argument must be a RateLimit" if ! rate.nil? && ! rate.is_a?(RateLimit)
306
+ @ioInfo.readRateLimit = rate
307
+ end
308
+
309
+ def writeRateLimit=(rate)
310
+ raise "The argument must be a RateLimit" if ! rate.nil? && !rate.is_a?(RateLimit)
311
+ @ioInfo.writeRateLimit = rate
312
+ end
313
+
314
+ attr_accessor :writeRateLimit
277
315
  end
278
316
 
279
- # An IoFacade that doesn't allow reading.
317
+ # An IoFacade that doesn't allow reading. This is not part of the public API.
280
318
  class WriteOnlyIoFacade < IoFacade
319
+ # Create a new WriteOnlyIoFacade that delegates to the passed IOInfo object.
281
320
  def initialize(ioInfo, logger = nil, readError = "Reading is not allowed for this IO")
282
321
  super(ioInfo, logger)
283
322
  @readError = readError
284
323
  end
285
324
 
325
+ # Raise an exception.
286
326
  def read(length)
287
327
  raise @readError
288
328
  end
289
329
  end
290
330
 
291
- # An IO and associated meta-information used by the Reactor.
331
+ # An IO and associated meta-information used by the Reactor. This is not part of the public API.
292
332
  class IOInfo
333
+ # Create a new IOInfo object that operates on the passed IO object.
334
+ # @param io An IO object
335
+ # @param metainfo The metainfo to associate with the IO.
336
+ # @param seekable Whether the IO is seekable or not.
293
337
  def initialize(io, metainfo, seekable = false)
294
338
  @io = io
295
339
  @metainfo = metainfo
@@ -315,6 +359,8 @@ module QuartzTorrent
315
359
  attr_accessor :useErrorhandler
316
360
  attr_accessor :readRateLimit
317
361
  attr_accessor :writeRateLimit
362
+
363
+ # Is the IO seekable.
318
364
  def seekable?
319
365
  @seekable
320
366
  end
@@ -583,13 +629,15 @@ module QuartzTorrent
583
629
  end
584
630
 
585
631
  # Meant to be called from the handler. Sets the max rate at which the current io can read.
586
- def setReadRateLimit(rateLimit)
587
- @currentIoInfo.readRateLimit = rateLimit
632
+ def setReadRateLimit(rate)
633
+ raise "The argument must be a RateLimit" if ! rate.nil? && !rate.is_a?(RateLimit)
634
+ @currentIoInfo.readRateLimit = rate
588
635
  end
589
636
 
590
637
  # Meant to be called from the handler. Sets the max rate at which the current io can be written to.
591
- def setWriteRateLimit(rateLimit)
592
- @currentIoInfo.writeRateLimit = rateLimit
638
+ def setWriteRateLimit(rate)
639
+ raise "The argument must be a RateLimit" if ! rate.nil? && !rate.is_a?(RateLimit)
640
+ @currentIoInfo.writeRateLimit = rate
593
641
  end
594
642
 
595
643
  # Meant to be called from the handler. Find an IO by metainfo. The == operator is used to
@@ -653,18 +701,6 @@ module QuartzTorrent
653
701
  rescue
654
702
  # Exception occurred. Probably EINTR.
655
703
  @logger.warn "Select raised exception; will retry. Reason: #{$!}" if @logger
656
- @logger.warn "IOs at time of failure:"
657
- @logger.warn "Readset:"
658
- readset.each do |io|
659
- ioInfo = @ioInfo[io]
660
- puts " #{ioInfo.metainfo}: #{(io.closed?) ? "closed" : "not closed"}"
661
- end
662
- @logger.warn "Writeset:"
663
- writeset.each do |io|
664
- ioInfo = @ioInfo[io]
665
- puts " #{ioInfo.metainfo}: #{(io.closed?) ? "closed" : "not closed"}"
666
- end
667
-
668
704
  end
669
705
  end
670
706
 
@@ -1,4 +1,6 @@
1
1
  class Array
2
+ # Perform a binary search for a value in the array between the index low and high. This method expects a block. The
3
+ # block is passed a value v, and should return true if the target value is >= v, and false otherwise.
2
4
  def binsearch(low = nil, high = nil)
3
5
  return nil if length == 0
4
6
  result = binsearch_index(low, high){ |x| yield x if !x.nil?}
@@ -6,6 +8,8 @@ class Array
6
8
  result
7
9
  end
8
10
 
11
+ # Perform a binary search for an index in the array between the index low and high. This method expects a block. The
12
+ # block is passed a value v, and should return true if the target value is >= v, and false otherwise.
9
13
  def binsearch_index(low = nil, high = nil)
10
14
  return nil if length == 0
11
15
  low = 0 if !low
@@ -35,17 +35,21 @@ module QuartzTorrent
35
35
  end
36
36
  end
37
37
 
38
+ # Hash code of this TrackerPeer.
38
39
  def hash
39
40
  @hash
40
41
  end
41
42
 
43
+ # Equate to another TrackerPeer.
42
44
  def eql?(o)
43
45
  o.ip == @ip && o.port == @port
44
46
  end
45
47
 
46
- # Ip address, a string in dotted-quad notation
48
+ # IP address, a string in dotted-quad notation
47
49
  attr_accessor :ip
50
+ # TCP port
48
51
  attr_accessor :port
52
+ # Peer Id. This may be nil.
49
53
  attr_accessor :id
50
54
 
51
55
  def to_s
@@ -64,8 +68,11 @@ module QuartzTorrent
64
68
  @left = 0
65
69
  end
66
70
  end
71
+ # Number of bytes uploaded
67
72
  attr_accessor :uploaded
73
+ # Number of bytes downloaded
68
74
  attr_accessor :downloaded
75
+ # Number of bytes left to download before torrent is completed
69
76
  attr_accessor :left
70
77
  end
71
78
 
@@ -98,6 +105,9 @@ module QuartzTorrent
98
105
  class TrackerClient
99
106
  include QuartzTorrent
100
107
 
108
+ # Create a new TrackerClient
109
+ # @param announceUrl The announce URL of the tracker
110
+ # @param infoHash The infoHash of the torrent we're tracking
101
111
  def initialize(announceUrl, infoHash, dataLength = 0, maxErrors = 20)
102
112
  @peerId = "-QR0001-" # Azureus style
103
113
  @peerId << Process.pid.to_s
@@ -126,10 +136,13 @@ module QuartzTorrent
126
136
  # with up-to-date information.
127
137
  attr_accessor :dynamicRequestParamsBuilder
128
138
 
139
+ # Return true if this TrackerClient is started, false otherwise.
129
140
  def started?
130
141
  @started
131
142
  end
132
143
 
144
+ # Return the list of peers that the TrackerClient knows about. This list grows over time
145
+ # as more peers are reported from the tracker.
133
146
  def peers
134
147
  result = nil
135
148
  @peersMutex.synchronize do
@@ -153,7 +166,8 @@ module QuartzTorrent
153
166
  @errors
154
167
  end
155
168
 
156
- # Create a new TrackerClient using the passed information.
169
+ # Create a new TrackerClient using the passed information. This is a factory method that will return
170
+ # a tracker that talks the protocol specified in the URL.
157
171
  def self.create(announceUrl, infoHash, dataLength = 0, start = true)
158
172
  result = nil
159
173
  if announceUrl =~ /udp:\/\//
@@ -166,6 +180,7 @@ module QuartzTorrent
166
180
  result
167
181
  end
168
182
 
183
+ # Create a new TrackerClient using the passed Metainfo object.
169
184
  def self.createFromMetainfo(metainfo, start = true)
170
185
  create(metainfo.announce, metainfo.infoHash, metainfo.info.dataLength, start)
171
186
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quartz_torrent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -164,6 +164,7 @@ files:
164
164
  - lib/quartz_torrent/magnet.rb
165
165
  - lib/quartz_torrent/udptrackerclient.rb
166
166
  - README.md
167
+ - LICENSE
167
168
  - .yardopts
168
169
  - bin/quartztorrent_download
169
170
  - bin/quartztorrent_download_curses