diff options
Diffstat (limited to 'lib/bundler/compact_index_client/cache.rb')
| -rw-r--r-- | lib/bundler/compact_index_client/cache.rb | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb new file mode 100644 index 0000000000..3bae6c9efd --- /dev/null +++ b/lib/bundler/compact_index_client/cache.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "rubygems/resolver/api_set/gem_parser" + +module Bundler + class CompactIndexClient + class Cache + attr_reader :directory + + def initialize(directory, fetcher = nil) + @directory = Pathname.new(directory).expand_path + @updater = Updater.new(fetcher) if fetcher + @mutex = Thread::Mutex.new + @endpoints = Set.new + + @info_root = mkdir("info") + @special_characters_info_root = mkdir("info-special-characters") + @info_etag_root = mkdir("info-etags") + end + + def names + fetch("names", names_path, names_etag_path) + end + + def versions + fetch("versions", versions_path, versions_etag_path) + end + + def info(name, remote_checksum = nil) + path = info_path(name) + + if remote_checksum && remote_checksum != SharedHelpers.checksum_for_file(path, :MD5) + fetch("info/#{name}", path, info_etag_path(name)) + else + Bundler::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum is nil" : "versions index checksum matches local"})" } + read(path) + end + end + + def reset! + @mutex.synchronize { @endpoints.clear } + end + + private + + def names_path = directory.join("names") + def names_etag_path = directory.join("names.etag") + def versions_path = directory.join("versions") + def versions_etag_path = directory.join("versions.etag") + + def info_path(name) + name = name.to_s + # TODO: converge this into the info_root by hashing all filenames like info_etag_path + if /[^a-z0-9_-]/.match?(name) + name += "-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}" + @special_characters_info_root.join(name) + else + @info_root.join(name) + end + end + + def info_etag_path(name) + name = name.to_s + @info_etag_root.join("#{name}-#{SharedHelpers.digest(:MD5).hexdigest(name).downcase}") + end + + def mkdir(name) + directory.join(name).tap do |dir| + SharedHelpers.filesystem_access(dir) do + FileUtils.mkdir_p(dir) + end + end + end + + def fetch(remote_path, path, etag_path) + if already_fetched?(remote_path) + Bundler::CompactIndexClient.debug { "already fetched #{remote_path}" } + else + Bundler::CompactIndexClient.debug { "fetching #{remote_path}" } + @updater&.update(remote_path, path, etag_path) + end + + read(path) + end + + def already_fetched?(remote_path) + @mutex.synchronize { !@endpoints.add?(remote_path) } + end + + def read(path) + return unless path.file? + SharedHelpers.filesystem_access(path, :read, &:read) + end + end + end +end |
