ruby/lib/rubygems/source_info_cache.rb

355 строки
7.9 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 false if Gem.configuration.update_sources
@cache
end
def self.cache_data
cache.cache_data
end
##
# The name of the system cache file.
def self.latest_system_cache_file
File.join File.dirname(system_cache_file),
"latest_#{File.basename system_cache_file}"
end
##
# The name of the latest user cache file.
def self.latest_user_cache_file
File.join File.dirname(user_cache_file),
"latest_#{File.basename user_cache_file}"
end
##
# Search all source indexes. See Gem::SourceInfoCache#search.
def self.search(*args)
cache.search(*args)
end
##
# Search all source indexes returning the source_uri. See
# Gem::SourceInfoCache#search_with_source.
def self.search_with_source(*args)
cache.search_with_source(*args)
end
##
# The name of the system cache file. (class method)
def self.system_cache_file
@system_cache_file ||= Gem.default_system_source_cache_dir
end
##
# The name of the user cache file.
def self.user_cache_file
@user_cache_file ||=
ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
end
def initialize # :nodoc:
@cache_data = nil
@cache_file = nil
@dirty = false
@only_latest = true
end
##
# The most recent cache data.
def cache_data
return @cache_data if @cache_data
cache_file # HACK writable check
@only_latest = true
@cache_data = read_cache_data latest_cache_file
@cache_data
end
##
# The name of the cache file.
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
##
# Force cache file to be reset, useful for integration testing of rubygems
def reset_cache_file
@cache_file = nil
end
##
# Write the cache to a local file (if it is dirty).
def flush
write_cache if @dirty
@dirty = false
end
def latest_cache_data
latest_cache_data = {}
cache_data.each do |repo, sice|
latest = sice.source_index.latest_specs
new_si = Gem::SourceIndex.new
new_si.add_specs(*latest)
latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size
latest_cache_data[repo] = latest_sice
end
latest_cache_data
end
##
# The name of the latest cache file.
def latest_cache_file
File.join File.dirname(cache_file), "latest_#{File.basename cache_file}"
end
##
# The name of the latest system cache file.
def latest_system_cache_file
self.class.latest_system_cache_file
end
##
# The name of the latest user cache file.
def latest_user_cache_file
self.class.latest_user_cache_file
end
def read_all_cache_data
if @only_latest then
@only_latest = false
@cache_data = read_cache_data cache_file
end
end
def read_cache_data(file)
# Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
data = open 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, cache_data
end
end
cache_data
rescue => e
if Gem.configuration.really_verbose then
say "Exception during cache_data handling: #{e.class} - #{e}"
say "Cache file was: #{file}"
say "\t#{e.backtrace.join "\n\t"}"
end
{}
end
##
# Refreshes each source in the cache from its repository. If +all+ is
# false, only latest gems are updated.
def refresh(all)
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, all
end
flush
end
def reset_cache_for(url, cache_data)
say "Reseting cache for #{url}" if Gem.configuration.really_verbose
sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh
cache_data[url] = sice
cache_data
end
def reset_cache_data
@cache_data = nil
end
##
# Searches all source indexes. See Gem::SourceIndex#search for details on
# +pattern+ and +platform_only+. If +all+ is set to true, the full index
# will be loaded before searching.
def search(pattern, platform_only = false, all = false)
read_all_cache_data if all
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, all = false)
read_all_cache_data if all
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
##
# 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
##
# The name of the system cache file.
def system_cache_file
self.class.system_cache_file
end
##
# Determine if +path+ is a candidate for a cache file. Returns +path+ if
# it is, nil if not.
def try_file(path)
return path if File.writable? path
return nil if File.exist? path
dir = File.dirname path
unless File.exist? dir then
begin
FileUtils.mkdir_p dir
rescue RuntimeError, SystemCallError
return nil
end
end
if File.writable? dir then
open path, "wb" do |io| io.write Marshal.dump({}) end
return path
end
nil
end
##
# Mark the cache as updated (i.e. dirty).
def update
@dirty = true
end
##
# The name of the user cache file.
def user_cache_file
self.class.user_cache_file
end
##
# Write data to the proper cache.
def write_cache
open cache_file, 'wb' do |io|
io.write Marshal.dump(cache_data)
end
open latest_cache_file, 'wb' do |io|
io.write Marshal.dump(latest_cache_data)
end
end
end