class Bundler::CompactIndexClient::Updater

Public Class Methods

new(fetcher) click to toggle source
# File bundler/compact_index_client/updater.rb, line 21
def initialize(fetcher)
  @fetcher = fetcher
end

Public Instance Methods

checksum_for_file(path) click to toggle source
# File bundler/compact_index_client/updater.rb, line 92
def checksum_for_file(path)
  return nil unless path.file?
  # This must use File.read instead of Digest.file().hexdigest
  # because we need to preserve \n line endings on windows when calculating
  # the checksum
  SharedHelpers.filesystem_access(path, :read) do
    File.open(path, "rb") do |f|
      digest = SharedHelpers.digest(:MD5).new
      buf = String.new(:capacity => 16_384, :encoding => Encoding::BINARY)
      digest << buf while f.read(16_384, buf)
      digest.hexdigest
    end
  end
end
etag_for(path) click to toggle source
# File bundler/compact_index_client/updater.rb, line 83
def etag_for(path)
  sum = checksum_for_file(path)
  sum ? %("#{sum}") : nil
end
slice_body(body, range) click to toggle source
# File bundler/compact_index_client/updater.rb, line 88
def slice_body(body, range)
  body.byteslice(range)
end
update(local_path, remote_path, retrying = nil) click to toggle source
# File bundler/compact_index_client/updater.rb, line 25
def update(local_path, remote_path, retrying = nil)
  headers = {}

  local_temp_path = local_path.sub(/$/, ".#{$$}")
  local_temp_path = local_temp_path.sub(/$/, ".retrying") if retrying
  local_temp_path = local_temp_path.sub(/$/, ".tmp")

  # first try to fetch any new bytes on the existing file
  if retrying.nil? && local_path.file?
    copy_file local_path, local_temp_path

    headers["If-None-Match"] = etag_for(local_temp_path)
    headers["Range"] =
      if local_temp_path.size.nonzero?
        # Subtract a byte to ensure the range won't be empty.
        # Avoids 416 (Range Not Satisfiable) responses.
        "bytes=#{local_temp_path.size - 1}-"
      else
        "bytes=#{local_temp_path.size}-"
      end
  end

  response = @fetcher.call(remote_path, headers)
  return nil if response.is_a?(Net::HTTPNotModified)

  content = response.body

  etag = (response["ETag"] || "").gsub(%r{\AW/}, "")
  correct_response = SharedHelpers.filesystem_access(local_temp_path) do
    if response.is_a?(Net::HTTPPartialContent) && local_temp_path.size.nonzero?
      local_temp_path.open("a") {|f| f << slice_body(content, 1..-1) }

      etag_for(local_temp_path) == etag
    else
      local_temp_path.open("wb") {|f| f << content }

      etag.length.zero? || etag_for(local_temp_path) == etag
    end
  end

  if correct_response
    SharedHelpers.filesystem_access(local_path) do
      FileUtils.mv(local_temp_path, local_path)
    end
    return nil
  end

  if retrying
    raise MisMatchedChecksumError.new(remote_path, etag, etag_for(local_temp_path))
  end

  update(local_path, remote_path, :retrying)
rescue Zlib::GzipFile::Error
  raise Bundler::HTTPError
ensure
  FileUtils.remove_file(local_temp_path) if File.exist?(local_temp_path)
end

Private Instance Methods

copy_file(source, dest) click to toggle source
# File bundler/compact_index_client/updater.rb, line 109
def copy_file(source, dest)
  SharedHelpers.filesystem_access(source, :read) do
    File.open(source, "r") do |s|
      SharedHelpers.filesystem_access(dest, :write) do
        File.open(dest, "wb", s.stat.mode) do |f|
          IO.copy_stream(s, f)
        end
      end
    end
  end
end