class TorrentManager

Attributes

peerClient[R]

Public Class Methods

new(peerClient, torrentFileDir, monthlyResetDay) click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 8
def initialize(peerClient, torrentFileDir, monthlyResetDay)
  @peerClient = peerClient
  @cachedTorrentData = nil
  @cachedTorrentDataMutex = Mutex.new
  @cachedAt = nil
  @cacheLifetime = 2
  @torrentFileDir = torrentFileDir
  @peerClientStopped = false
  @usageTracker = UsageTracker.new(monthlyResetDay)
  # Start a thread to keep track of usage.
  startUsageTrackerThread
  startTorrentDataThread
end

Public Instance Methods

applyTorrentSettings(infoHash) click to toggle source

Update the torrent settings (upload rate limit, etc) from database values

# File lib/quartz_flow/torrent_manager.rb, line 246
def applyTorrentSettings(infoHash)
  asciiInfoHash = QuartzTorrent::bytesToHex(infoHash)
  helper = SettingsHelper.new

  # Set limits based on per-torrent settings if they exist, otherwise to default global limits if they exist.
  uploadRateLimit = to_i(helper.get(:uploadRateLimit, :unfiltered, asciiInfoHash))
  uploadRateLimit = to_i(helper.get(:defaultUploadRateLimit, :unfiltered)) if ! uploadRateLimit

  downloadRateLimit = to_i(helper.get(:downloadRateLimit, :unfiltered, asciiInfoHash))
  downloadRateLimit = to_i(helper.get(:defaultDownloadRateLimit, :unfiltered)) if ! downloadRateLimit

  ratio = helper.get(:ratio, :filter, asciiInfoHash)
  ratio = helper.get(:defaultRatio, :filter) if ! ratio

  uploadDuration = helper.get(:uploadDuration, :unfiltered, asciiInfoHash)
  uploadDuration = helper.get(:defaultUploadDuration, :unfiltered) if ! uploadDuration
  uploadDuration = uploadDuration.to_i if uploadDuration

  paused = helper.get(:paused, :filter, asciiInfoHash)

  bytesDownloaded = helper.get(:bytesDownloaded, :filter, asciiInfoHash)
  bytesUploaded = helper.get(:bytesUploaded, :filter, asciiInfoHash)

  @peerClient.setUploadRateLimit infoHash, uploadRateLimit
  @peerClient.setDownloadRateLimit infoHash, downloadRateLimit
  @peerClient.setUploadRatio infoHash, ratio
  @peerClient.setUploadDuration infoHash, uploadDuration
  @peerClient.setPaused infoHash, paused
  @peerClient.adjustBytesDownloaded infoHash, bytesDownloaded if bytesDownloaded
  @peerClient.adjustBytesUploaded infoHash, bytesUploaded if bytesUploaded
end
currentPeriodUsage(periodType) click to toggle source

Get the usage for the current period of the specified type. periodType should be one of :daily or :monthly.

# File lib/quartz_flow/torrent_manager.rb, line 280
def currentPeriodUsage(periodType)
  @usageTracker.currentUsage(periodType).value
end
downloadTorrentFile(url) click to toggle source

Download a .torrent file from a specified URL. Return the path to the downloaded .torrent file.

# File lib/quartz_flow/torrent_manager.rb, line 149
def downloadTorrentFile(url)
  # open-uri doesn't handle [ and ] properly
  encodedSourcePath = URI.escape(url, /[\[\]]/)

  path = nil
  open(encodedSourcePath) do |f|
    uriPath = f.base_uri.path
    raise "The file '#{uriPath}' doesn't have the .torrent extension" if uriPath !~ /.torrent$/
    path = @torrentFileDir + File::SEPARATOR + File.basename(uriPath)
    File.open(path, "w"){ |outfile| outfile.write(f.read) }
  end
  path
end
loadMagnet(path) click to toggle source

Load a magnet link in a file

# File lib/quartz_flow/torrent_manager.rb, line 171
def loadMagnet(path)
  raw = nil
  File.open(path, "r"){ |infile| raw = infile.read }
  QuartzTorrent::MagnetURI.new(raw)
end
removeTorrent(infoHash, deleteFiles) click to toggle source

Remove the specified torrent. Pass the infoHash as an ascii string, not binary.

# File lib/quartz_flow/torrent_manager.rb, line 211
def removeTorrent(infoHash, deleteFiles)
  infoHashBytes = QuartzTorrent::hexToBytes(infoHash)
  @peerClient.removeTorrent infoHashBytes, deleteFiles

  # Remove torrent from torrent dir
  Dir.new(@torrentFileDir).each do |e|
    if e =~ /\.torrent$/
      path = @torrentFileDir + File::SEPARATOR + e
      metainfo = QuartzTorrent::Metainfo.createFromFile(path)
      if metainfo.infoHash == infoHashBytes
        FileUtils.rm path
        break
      end
    end
  end

  # Remove torrent settings
  helper = SettingsHelper.new
  helper.deleteForOwner infoHash
 
  # Remove magnet file if it exists
  magnetFile = @torrentFileDir + File::SEPARATOR + infoHash + ".magnet"
  FileUtils.rm magnetFile if File.exists?(magnetFile)
end
setTorrentPaused(infoHash, val) click to toggle source

Pause or unpause the specified torrent. Store the pause state in the database.

# File lib/quartz_flow/torrent_manager.rb, line 237
def setTorrentPaused(infoHash, val)
  infoHashBytes = QuartzTorrent::hexToBytes(infoHash)
  @peerClient.setPaused infoHashBytes, val

  helper = SettingsHelper.new    
  helper.set :paused, val, infoHash
end
simplifiedTorrentData(fields, where) click to toggle source

Return the torrent data as a hash. If ‘fields` is non-null, then only those fields are returned. If `where` is non-null, it should be a hash of fields for which the values must match to be returned.

# File lib/quartz_flow/torrent_manager.rb, line 68
def simplifiedTorrentData(fields, where)
  result = {}

  fieldsHash = {}
  fields.each{ |e| fieldsHash[e] = true } if fields

  if where
    w = {}
    where.each do |k,v|
      w[k.to_sym] = v
    end
    where = w
  end

  torrentData.each do |k,d|
    h = d.to_h

    asciiInfoHash = QuartzTorrent::bytesToHex(h[:infoHash])
    h[:infoHash] = asciiInfoHash
    h[:downloadRate] = QuartzTorrent::Formatter.formatSpeed(h[:downloadRate])
    h[:uploadRate] = QuartzTorrent::Formatter.formatSpeed(h[:uploadRate])
    h[:downloadRateDataOnly] = QuartzTorrent::Formatter.formatSpeed(h[:downloadRateDataOnly])
    h[:uploadRateDataOnly] = QuartzTorrent::Formatter.formatSpeed(h[:uploadRateDataOnly])
    h[:dataLength] = QuartzTorrent::Formatter.formatSize(h[:dataLength])
    h[:completedBytes] = QuartzTorrent::Formatter.formatSize(h[:completedBytes])
    # Sort peers
    h[:peers].sort! do |a,b|
      c = (b[:uploadRate].to_i <=> a[:uploadRate].to_i)
      c = (b[:downloadRate].to_i <=> a[:downloadRate].to_i) if c == 0
      c
    end
    # Format peer rates
    h[:peers].each do |p| 
      p[:uploadRate] = QuartzTorrent::Formatter.formatSpeed(p[:uploadRate])
      p[:downloadRate] = QuartzTorrent::Formatter.formatSpeed(p[:downloadRate])
    end
    if h[:info] 
      h[:info][:files].each do |file|
        file[:length] = QuartzTorrent::Formatter.formatSize(file[:length])
      end
    end
    h[:uploadRateLimit] = QuartzTorrent::Formatter.formatSpeed(h[:uploadRateLimit])
    h[:downloadRateLimit] = QuartzTorrent::Formatter.formatSize(h[:downloadRateLimit])
    h[:bytesUploaded] = QuartzTorrent::Formatter.formatSize(h[:bytesUploaded])
    h[:bytesDownloaded] = QuartzTorrent::Formatter.formatSize(h[:bytesDownloaded])
    h[:uploadDuration] = QuartzTorrent::Formatter.formatTime(h[:uploadDuration]) if h[:uploadDuration]

    h[:completePieces] = d.completePieceBitfield ? d.completePieceBitfield.countSet : 0
    h[:totalPieces] = d.completePieceBitfield ? d.completePieceBitfield.length : 0

    # Only send 8 alarms
    h[:alarms] = h[:alarms].first(8)

    if where
      matches = true
      where.each do |k,v|
        if h[k] != v
          matches = false
          break
        end
      end
      next if ! matches
    end

    if fields
      newHash = {}
      h.each do |k,v|
        if fieldsHash.has_key?(k)
          newHash[k] = v
        end
      end
      h = newHash
    end

    result[asciiInfoHash] = h
  end
  result
end
startExistingTorrents() click to toggle source

Start torrents that already exist in the torrent file directory

# File lib/quartz_flow/torrent_manager.rb, line 44
def startExistingTorrents
  Dir.new(@torrentFileDir).each do |e|
    path = @torrentFileDir + File::SEPARATOR + e
    if e =~ /\.torrent$/
      puts "Starting .torrent '#{path}'"
      begin
        startTorrentFile(path)
      rescue
        puts "  Starting .torrent '#{path}' failed: #{$!}"
      end
    elsif e =~ /\.magnet$/   
      magnet = loadMagnet(path)
      puts "Starting magnet '#{magnet.raw}'"
      begin
        startMagnet magnet
      rescue
        puts "  Starting magnet '#{magnet.raw}' failed: #{$!}"
      end
    end
  end
end
startMagnet(magnet) click to toggle source

Start running the magnet

# File lib/quartz_flow/torrent_manager.rb, line 204
def startMagnet(magnet)
  startTorrent do
    @peerClient.addTorrentByMagnetURI(magnet)
  end
end
startTorrentFile(path) click to toggle source

Start running the torrent specified by the .torrent file given in path.

# File lib/quartz_flow/torrent_manager.rb, line 187
def startTorrentFile(path)
  startTorrent do
    begin
      metainfo = QuartzTorrent::Metainfo.createFromFile(path)
      @peerClient.addTorrentByMetainfo(metainfo)
    rescue BEncode::DecodeError
      # Delete the file
      begin
        FileUtils.rm path
      rescue
      end
      raise $!
    end
  end
end
stopPeerClient() click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 36
def stopPeerClient
  @peerClient.stop
  @peerClientStopped = true
  stopTorrentDataThread
  stopUsageTrackerThread
end
storeMagnet(magnet) click to toggle source

Store a magnet link in a file in the torrent file directory.

# File lib/quartz_flow/torrent_manager.rb, line 164
def storeMagnet(magnet)
  asciiInfoHash = QuartzTorrent::bytesToHex(magnet.btInfoHash)
  path = @torrentFileDir + File::SEPARATOR + asciiInfoHash + ".magnet"
  File.open(path, "w"){ |outfile| outfile.write(magnet.raw) }
end
storeUploadedTorrentFile(path, name) click to toggle source

Store an uploaded .torrent file in the torrent directory. Return the path to the final .torrent file.

# File lib/quartz_flow/torrent_manager.rb, line 179
def storeUploadedTorrentFile(path, name)
  name += ".torrent" if name !~ /\.torrent$/
  dpath = @torrentFileDir + File::SEPARATOR + name
  FileUtils.mv path, dpath
  dpath
end
torrentData(infoHash = nil) click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 24
def torrentData(infoHash = nil)
  result = nil

  # The first time, we may need to wait for the thread to load the data.
  sleep(0.25) while ! @cachedTorrentData

  @cachedTorrentDataMutex.synchronize do
    result = @cachedTorrentData 
  end
  result
end

Private Instance Methods

startTorrent() { || ... } click to toggle source

Helper for starting torrents. Expects a block that when called will add a torrent to the @peerClient, and return the infoHash.

# File lib/quartz_flow/torrent_manager.rb, line 287
def startTorrent
  raise "Torrent client is shutting down" if @peerClientStopped
  infoHash = yield 

  applyTorrentSettings infoHash
end
startTorrentDataThread() click to toggle source

Start a thread to update torrent data

# File lib/quartz_flow/torrent_manager.rb, line 332
def startTorrentDataThread
  @torrentDataThread = Thread.new do
    QuartzTorrent.initThread("torrent_data_loader")
    
    Thread.current[:stopped] = false

    while ! Thread.current[:stopped]
      begin
        timer = Time.new
        if (! @cachedTorrentData || Time.new - @cachedAt > @cacheLifetime) && ! @peerClientStopped
          data = @peerClient.torrentData
          @cachedTorrentDataMutex.synchronize do
            @cachedTorrentData = data
            @cachedAt = Time.new
          end
        end
        timer = Time.new - timer

        if timer < @cacheLifetime
          sleep @cacheLifetime-timer
        end
      rescue
        puts "Error in torrent data loader thread: #{$!}"
        puts $!.backtrace.join "\n"
      end
    end
  end
end
startUsageTrackerThread() click to toggle source

Start a thread to keep track of usage.

# File lib/quartz_flow/torrent_manager.rb, line 300
def startUsageTrackerThread
  @usageTrackerThread = Thread.new do
    QuartzTorrent.initThread("torrent_usage_tracking")
    
    Thread.current[:stopped] = false
    helper = SettingsHelper.new

    while ! Thread.current[:stopped]
      begin
        sleep 4
        torrentData = @peerClient.torrentData
        usage = 0
        torrentData.each do |k,v|
          usage += v.bytesUploaded + v.bytesDownloaded
          asciiInfoHash = QuartzTorrent::bytesToHex(k)
          helper.set :bytesDownloaded, v.bytesDownloaded, asciiInfoHash
          helper.set :bytesUploaded, v.bytesUploaded, asciiInfoHash
        end
        @usageTracker.update(usage)
      rescue
        puts "Error in usage tracking thread: #{$!}"
        puts $!.backtrace.join "\n"
      end
    end
  end
end
stopTorrentDataThread() click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 361
def stopTorrentDataThread
  @torrentDataThread[:stopped] = true
end
stopUsageTrackerThread() click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 327
def stopUsageTrackerThread
  @usageTrackerThread[:stopped] = true
end
to_i(val) click to toggle source
# File lib/quartz_flow/torrent_manager.rb, line 294
def to_i(val)
  val = val.to_i if val
  val
end