aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/source_info_cache.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rubygems/source_info_cache.rb')
-rw-r--r--lib/rubygems/source_info_cache.rb232
1 files changed, 232 insertions, 0 deletions
diff --git a/lib/rubygems/source_info_cache.rb b/lib/rubygems/source_info_cache.rb
new file mode 100644
index 0000000000..0498e895a4
--- /dev/null
+++ b/lib/rubygems/source_info_cache.rb
@@ -0,0 +1,232 @@
+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
+