mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
233 lines
6 KiB
Ruby
233 lines
6 KiB
Ruby
|
require 'fileutils'
|
||
|
|
||
|
require 'rubygems'
|
||
|
require 'rubygems/source_info_cache_entry'
|
||
|
require 'rubygems/user_interaction'
|
||
|
|
||
|
# SourceInfoCache stores a copy of the gem index for each gem source.
|
||
|
#
|
||
|
# There are two possible cache locations, the system cache and the user cache:
|
||
|
# * The system cache is prefered if it is writable or can be created.
|
||
|
# * The user cache is used otherwise
|
||
|
#
|
||
|
# Once a cache is selected, it will be used for all operations.
|
||
|
# SourceInfoCache will not switch between cache files dynamically.
|
||
|
#
|
||
|
# Cache data is a Hash mapping a source URI to a SourceInfoCacheEntry.
|
||
|
#
|
||
|
#--
|
||
|
# To keep things straight, this is how the cache objects all fit together:
|
||
|
#
|
||
|
# Gem::SourceInfoCache
|
||
|
# @cache_data = {
|
||
|
# source_uri => Gem::SourceInfoCacheEntry
|
||
|
# @size => source index size
|
||
|
# @source_index => Gem::SourceIndex
|
||
|
# ...
|
||
|
# }
|
||
|
#
|
||
|
class Gem::SourceInfoCache
|
||
|
|
||
|
include Gem::UserInteraction
|
||
|
|
||
|
@cache = nil
|
||
|
@system_cache_file = nil
|
||
|
@user_cache_file = nil
|
||
|
|
||
|
def self.cache
|
||
|
return @cache if @cache
|
||
|
@cache = new
|
||
|
@cache.refresh if Gem.configuration.update_sources
|
||
|
@cache
|
||
|
end
|
||
|
|
||
|
def self.cache_data
|
||
|
cache.cache_data
|
||
|
end
|
||
|
|
||
|
# Search all source indexes for +pattern+.
|
||
|
def self.search(pattern, platform_only = false)
|
||
|
cache.search pattern, platform_only
|
||
|
end
|
||
|
|
||
|
# Search all source indexes for +pattern+. Only returns gems matching
|
||
|
# Gem.platforms when +only_platform+ is true. See #search_with_source.
|
||
|
def self.search_with_source(pattern, only_platform = false)
|
||
|
cache.search_with_source(pattern, only_platform)
|
||
|
end
|
||
|
|
||
|
def initialize # :nodoc:
|
||
|
@cache_data = nil
|
||
|
@cache_file = nil
|
||
|
@dirty = false
|
||
|
end
|
||
|
|
||
|
# The most recent cache data.
|
||
|
def cache_data
|
||
|
return @cache_data if @cache_data
|
||
|
cache_file # HACK writable check
|
||
|
|
||
|
begin
|
||
|
# Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
|
||
|
data = File.open cache_file, 'rb' do |fp| fp.read end
|
||
|
@cache_data = Marshal.load data
|
||
|
|
||
|
@cache_data.each do |url, sice|
|
||
|
next unless sice.is_a?(Hash)
|
||
|
update
|
||
|
cache = sice['cache']
|
||
|
size = sice['size']
|
||
|
if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
|
||
|
new_sice = Gem::SourceInfoCacheEntry.new cache, size
|
||
|
@cache_data[url] = new_sice
|
||
|
else # irreperable, force refetch.
|
||
|
reset_cache_for(url)
|
||
|
end
|
||
|
end
|
||
|
@cache_data
|
||
|
rescue => e
|
||
|
if Gem.configuration.really_verbose then
|
||
|
say "Exception during cache_data handling: #{ex.class} - #{ex}"
|
||
|
say "Cache file was: #{cache_file}"
|
||
|
say "\t#{e.backtrace.join "\n\t"}"
|
||
|
end
|
||
|
reset_cache_data
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def reset_cache_for(url)
|
||
|
say "Reseting cache for #{url}" if Gem.configuration.really_verbose
|
||
|
|
||
|
sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
|
||
|
sice.refresh url # HACK may be unnecessary, see ::cache and #refresh
|
||
|
|
||
|
@cache_data[url] = sice
|
||
|
@cache_data
|
||
|
end
|
||
|
|
||
|
def reset_cache_data
|
||
|
@cache_data = {}
|
||
|
end
|
||
|
|
||
|
# The name of the cache file to be read
|
||
|
def cache_file
|
||
|
return @cache_file if @cache_file
|
||
|
@cache_file = (try_file(system_cache_file) or
|
||
|
try_file(user_cache_file) or
|
||
|
raise "unable to locate a writable cache file")
|
||
|
end
|
||
|
|
||
|
# Write the cache to a local file (if it is dirty).
|
||
|
def flush
|
||
|
write_cache if @dirty
|
||
|
@dirty = false
|
||
|
end
|
||
|
|
||
|
# Refreshes each source in the cache from its repository.
|
||
|
def refresh
|
||
|
Gem.sources.each do |source_uri|
|
||
|
cache_entry = cache_data[source_uri]
|
||
|
if cache_entry.nil? then
|
||
|
cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
|
||
|
cache_data[source_uri] = cache_entry
|
||
|
end
|
||
|
|
||
|
update if cache_entry.refresh source_uri
|
||
|
end
|
||
|
|
||
|
flush
|
||
|
end
|
||
|
|
||
|
# Searches all source indexes for +pattern+.
|
||
|
def search(pattern, platform_only = false)
|
||
|
cache_data.map do |source_uri, sic_entry|
|
||
|
next unless Gem.sources.include? source_uri
|
||
|
sic_entry.source_index.search pattern, platform_only
|
||
|
end.flatten.compact
|
||
|
end
|
||
|
|
||
|
# Searches all source indexes for +pattern+. If +only_platform+ is true,
|
||
|
# only gems matching Gem.platforms will be selected. Returns an Array of
|
||
|
# pairs containing the Gem::Specification found and the source_uri it was
|
||
|
# found at.
|
||
|
def search_with_source(pattern, only_platform = false)
|
||
|
results = []
|
||
|
|
||
|
cache_data.map do |source_uri, sic_entry|
|
||
|
next unless Gem.sources.include? source_uri
|
||
|
|
||
|
sic_entry.source_index.search(pattern, only_platform).each do |spec|
|
||
|
results << [spec, source_uri]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
results
|
||
|
end
|
||
|
|
||
|
# Mark the cache as updated (i.e. dirty).
|
||
|
def update
|
||
|
@dirty = true
|
||
|
end
|
||
|
|
||
|
# The name of the system cache file.
|
||
|
def system_cache_file
|
||
|
self.class.system_cache_file
|
||
|
end
|
||
|
|
||
|
# The name of the system cache file. (class method)
|
||
|
def self.system_cache_file
|
||
|
@system_cache_file ||= File.join(Gem.dir, "source_cache")
|
||
|
end
|
||
|
|
||
|
# The name of the user cache file.
|
||
|
def user_cache_file
|
||
|
self.class.user_cache_file
|
||
|
end
|
||
|
|
||
|
# The name of the user cache file. (class method)
|
||
|
def self.user_cache_file
|
||
|
@user_cache_file ||=
|
||
|
ENV['GEMCACHE'] || File.join(Gem.user_home, ".gem", "source_cache")
|
||
|
end
|
||
|
|
||
|
# Write data to the proper cache.
|
||
|
def write_cache
|
||
|
open cache_file, "wb" do |f|
|
||
|
f.write Marshal.dump(cache_data)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Set the source info cache data directly. This is mainly used for unit
|
||
|
# testing when we don't want to read a file system to grab the cached source
|
||
|
# index information. The +hash+ should map a source URL into a
|
||
|
# SourceInfoCacheEntry.
|
||
|
def set_cache_data(hash)
|
||
|
@cache_data = hash
|
||
|
update
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
# Determine if +fn+ is a candidate for a cache file. Return fn if
|
||
|
# it is. Return nil if it is not.
|
||
|
def try_file(fn)
|
||
|
return fn if File.writable?(fn)
|
||
|
return nil if File.exist?(fn)
|
||
|
dir = File.dirname(fn)
|
||
|
unless File.exist? dir then
|
||
|
begin
|
||
|
FileUtils.mkdir_p(dir)
|
||
|
rescue RuntimeError
|
||
|
return nil
|
||
|
end
|
||
|
end
|
||
|
if File.writable?(dir)
|
||
|
File.open(fn, "wb") { |f| f << Marshal.dump({}) }
|
||
|
return fn
|
||
|
end
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|