123456789_123456789_123456789_123456789_123456789_

Class: Gem::CompactIndexClient::Cache

Relationships & Source Files
Inherits: Object
Defined in: lib/rubygems/compact_index_client/cache.rb

Overview

Calls the Updater to update the cached files on disk, reads the cached files and returns their contents.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(directory, fetcher = nil) ⇒ Cache

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 14

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

Instance Attribute Details

#directory (readonly)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 12

attr_reader :directory

Instance Method Details

#already_fetched?(remote_path) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 113

def already_fetched?(remote_path)
  @mutex.synchronize { !@endpoints.add?(remote_path) }
end

#checksum_for_file(path) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 87

def checksum_for_file(path)
  return unless path.file?
  Gem::CompactIndexClient.filesystem_access(path, :read) do
    Digest::MD5.file(path).hexdigest
  end
end

#fetch(remote_path, path, etag_path) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 102

def fetch(remote_path, path, etag_path)
  if already_fetched?(remote_path)
    Gem::CompactIndexClient.debug { "already fetched #{remote_path}" }
  else
    Gem::CompactIndexClient.debug { "fetching #{remote_path}" }
    @updater&.update(remote_path, path, etag_path)
  end

  read(path)
end

#fetch_info(name)

Fetch a single gem's info file without consulting the versions index, refreshing the cached file with a conditional request.

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 46

def fetch_info(name)
  fetch("info/#{name}", info_path(name), info_etag_path(name))
end

#info(name, remote_checksum = nil)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 33

def info(name, remote_checksum = nil)
  path = info_path(name)

  if remote_checksum && remote_checksum != checksum_for_file(path)
    fetch("info/#{name}", path, info_etag_path(name))
  else
    Gem::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum matches local" : "versions index checksum is nil"})" }
    read(path)
  end
end

#info_etag_path(name) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 72

def info_etag_path(name)
  name = name.to_s
  validate_name!(name)
  @info_etag_root.join("#{name}-#{Digest::MD5.hexdigest(name).downcase}")
end

#info_path(name) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 61

def info_path(name)
  name = name.to_s
  validate_name!(name)
  if /[^a-z0-9_-]/.match?(name)
    name += "-#{Digest::MD5.hexdigest(name).downcase}"
    @special_characters_info_root.join(name)
  else
    @info_root.join(name)
  end
end

#mkdir(name) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 94

def mkdir(name)
  directory.join(name).tap do |dir|
    Gem::CompactIndexClient.filesystem_access(dir) do
      FileUtils.mkdir_p(dir)
    end
  end
end

#names

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 25

def names
  fetch("names", names_path, names_etag_path)
end

#names_etag_path (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 57

def names_etag_path = directory.join("names.etag")

#names_path (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 56

def names_path = directory.join("names")

#read(path) (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 117

def read(path)
  return unless path.file?
  Gem::CompactIndexClient.filesystem_access(path, :read, &:read)
end

#reset!

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 50

def reset!
  @mutex.synchronize { @endpoints.clear }
end

#validate_name!(name) (private)

::Gem names come from the remote index and are not otherwise validated, so refuse anything that would escape the cache directory when used as a path component.

Raises:

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 81

def validate_name!(name)
  return if File.basename(name) == name

  raise Gem::Exception, "malformed gem name: #{name.inspect}"
end

#versions

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 29

def versions
  fetch("versions", versions_path, versions_etag_path)
end

#versions_etag_path (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 59

def versions_etag_path = directory.join("versions.etag")

#versions_path (private)

[ GitHub ]

  
# File 'lib/rubygems/compact_index_client/cache.rb', line 58

def versions_path = directory.join("versions")