mirror of
https://github.com/ruby/ruby.git
synced 2025-08-24 05:25:34 +02:00

This still allocates a ton (a string for each line, plus a bunch of
splits into arrays), but it helps a bit when Bundler has to go through
dependency resolution.
```
==> memprof.after.txt <==
Total allocated: 194.14 MB (2317172 objects)
Total retained: 60.81 MB (593164 objects)
==> memprof.before.txt <==
Total allocated: 211.97 MB (2404890 objects)
Total retained: 62.85 MB (640342 objects)
```
c68b41b0e5
122 lines
3.8 KiB
Ruby
122 lines
3.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../vendored_fileutils"
|
|
|
|
module Bundler
|
|
class CompactIndexClient
|
|
class Updater
|
|
class MisMatchedChecksumError < Error
|
|
def initialize(path, server_checksum, local_checksum)
|
|
@path = path
|
|
@server_checksum = server_checksum
|
|
@local_checksum = local_checksum
|
|
end
|
|
|
|
def message
|
|
"The checksum of /#{@path} does not match the checksum provided by the server! Something is wrong " \
|
|
"(local checksum is #{@local_checksum.inspect}, was expecting #{@server_checksum.inspect})."
|
|
end
|
|
end
|
|
|
|
def initialize(fetcher)
|
|
@fetcher = fetcher
|
|
end
|
|
|
|
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
|
|
|
|
def etag_for(path)
|
|
sum = checksum_for_file(path)
|
|
sum ? %("#{sum}") : nil
|
|
end
|
|
|
|
def slice_body(body, range)
|
|
body.byteslice(range)
|
|
end
|
|
|
|
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
|
|
|
|
private
|
|
|
|
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
|
|
end
|
|
end
|
|
end
|