class QuartzTorrent::HttpTrackerDriver

A tracker driver that uses the HTTP protocol. This is the classic BitTorrent tracker protocol.

Public Class Methods

new(announceUrl, infoHash) click to toggle source
Calls superclass method QuartzTorrent::TrackerDriver::new
# File lib/quartz_torrent/httptrackerdriver.rb, line 9
def initialize(announceUrl, infoHash)
  super()
  @startSent = false
  @logger = LogManager.getLogger("http_tracker_client")
  @announceUrl = announceUrl
  @infoHash = infoHash
end

Public Instance Methods

request(event = nil) click to toggle source
Request a list of peers from the tracker and return it as a TrackerResponse.   
Event, if specified, may be set to :started, :stopped, or :completed.
This is used to notify the tracker that this is the first request,
that we are shutting down, or that we have the full torrent respectively.
Not specifying the event just means this is a regular poll.

def getPeers(event = nil)

# File lib/quartz_torrent/httptrackerdriver.rb, line 23
def request(event = nil)

  uri = URI(@announceUrl)
  
  dynamicParams = @dynamicRequestParamsBuilder.call

  params = {}
  params['info_hash'] = CGI.escape(@infoHash)
  params['peer_id'] = dynamicParams.peerId
  params['port'] = dynamicParams.port
  params['uploaded'] = dynamicParams.uploaded.to_s
  params['downloaded'] = dynamicParams.downloaded.to_s
  params['left'] = dynamicParams.left.to_s
  params['compact'] = "1"
  params['no_peer_id'] = "1"
  if ! @startSent
    event = :started  
    @startSent = true
  end
  params['event'] = event.to_s if event
  

  @logger.debug "Request parameters: "
  params.each do |k,v|
    @logger.debug "  #{k}: #{v}"
  end

  query = ""
  params.each do |k,v|
    query  << "&" if query.length > 0
    query  << "#{k}=#{v}"
  end
  uri.query = query
  
  begin
    res = Net::HTTP.get_response(uri)
  rescue Timeout::Error
    return TrackerResponse.new(false, "Tracker request timed out", []) 
  end
  @logger.debug "Tracker response code: #{res.code}"
  @logger.debug "Tracker response body: #{res.body}"
  result = buildTrackerResponse(res)
  @logger.debug "TrackerResponse: #{result.inspect}"
  result 
end

Protected Instance Methods

decodePeers(peersProp) click to toggle source
# File lib/quartz_torrent/httptrackerdriver.rb, line 70
def decodePeers(peersProp)
  peers = []
  if peersProp.is_a?(String)
    # Compact format: 4byte IP followed by 2byte port, in network byte order
    index = 0
    while index + 6 <= peersProp.length
      ip = peersProp[index,4].unpack("CCCC").join('.')
      port = peersProp[index+4,2].unpack("n").first
      peers.push TrackerPeer.new(ip, port)
      index += 6
    end
  else 
    # Non-compact format
    peersProp.each do |peer|
      ip = peer['ip'] 
      port = peer['port'] 
      if ip && port
        peers.push TrackerPeer.new(ip, port)
      end
    end
    #raise "Non-compact peer format not implemented"
  end
  peers
end

Private Instance Methods

buildTrackerResponse(netHttpResponse) click to toggle source
# File lib/quartz_torrent/httptrackerdriver.rb, line 96
def buildTrackerResponse(netHttpResponse)
  error = nil
  peers = []
  interval = nil
  success = netHttpResponse.code.to_i >= 200 && netHttpResponse.code.to_i < 300
  if success
    begin
      decoded = netHttpResponse.body.bdecode
    rescue
      error = "Tracker netHttpResponse body was not a valid bencoded string"
      success = false
      return self
    end

    if decoded.has_key? 'peers'
      peers = decodePeers(decoded['peers'])
    else
      error = "Tracker netHttpResponse didn't contain a peers property"
      success = false
    end

    if decoded.has_key? 'interval'
      interval = decoded['interval'].to_i
    end
  else
    error = netHttpResponse.body
  end
  
  result = TrackerResponse.new(success, error, peers)
  result.interval = interval if interval
  result
end