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 +20 -0
- data/README.md +2 -0
- data/lib/quartz_torrent/bitfield.rb +11 -0
- data/lib/quartz_torrent/extension.rb +7 -3
- data/lib/quartz_torrent/formatter.rb +3 -0
- data/lib/quartz_torrent/magnet.rb +1 -0
- data/lib/quartz_torrent/metainfopiecestate.rb +10 -0
- data/lib/quartz_torrent/peer.rb +4 -0
- data/lib/quartz_torrent/peerclient.rb +37 -8
- data/lib/quartz_torrent/peerholder.rb +10 -1
- data/lib/quartz_torrent/peermsg.rb +4 -0
- data/lib/quartz_torrent/reactor.rb +66 -30
- data/lib/quartz_torrent/regionmap.rb +4 -0
- data/lib/quartz_torrent/trackerclient.rb +17 -2
- metadata +2 -1
    
        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 | 
            +
            [](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 | 
            -
                #  | 
| 10 | 
            -
                #  | 
| 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.
         | 
| @@ -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
         | 
    
        data/lib/quartz_torrent/peer.rb
    CHANGED
    
    | @@ -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. | 
| 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. | 
| 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. | 
| 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. | 
| 1606 | 
            -
                      result.uploaded = torrentData. | 
| 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 | 
| 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 | 
| 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 | 
| 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 | 
| 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 | 
| 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( | 
| 587 | 
            -
                   | 
| 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( | 
| 592 | 
            -
                   | 
| 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 | 
            -
                #  | 
| 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. | 
| 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
         |