OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / lib / rubygems / source_info_cache.rb
diff --git a/lib/ruby-1.9.1-rc1/lib/rubygems/source_info_cache.rb b/lib/ruby-1.9.1-rc1/lib/rubygems/source_info_cache.rb
new file mode 100644 (file)
index 0000000..fdb30ad
--- /dev/null
@@ -0,0 +1,393 @@
+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 preferred 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
+
+  ##
+  # The singleton Gem::SourceInfoCache.  If +all+ is true, a full refresh will
+  # be performed if the singleton instance is being initialized.
+
+  def self.cache(all = false)
+    return @cache if @cache
+    @cache = new
+    @cache.refresh all 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
+
+  ##
+  # Reset all singletons, discarding any changes.
+
+  def self.reset
+    @cache = nil
+    @system_cache_file = nil
+    @user_cache_file = nil
+  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
+
+  ##
+  # 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
+
+  ##
+  # Merges the complete cache file into this Gem::SourceInfoCache.
+
+  def read_all_cache_data
+    if @only_latest then
+      @only_latest = false
+      all_data = read_cache_data cache_file
+
+      cache_data.update all_data do |source_uri, latest_sice, all_sice|
+        all_sice.source_index.gems.update latest_sice.source_index.gems
+
+        Gem::SourceInfoCacheEntry.new all_sice.source_index, latest_sice.size
+      end
+
+      begin
+        refresh true
+      rescue Gem::RemoteFetcher::FetchError
+      end
+    end
+  end
+
+  ##
+  # Reads cached data from +file+.
+
+  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 Errno::ENOENT
+    {}
+  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
+    @only_latest = true
+  end
+
+  ##
+  # Force cache file to be reset, useful for integration testing of rubygems
+
+  def reset_cache_file
+    @cache_file = 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
+      # TODO - Remove this gunk after 2008/11
+      unless pattern.kind_of?(Gem::Dependency)
+        pattern = Gem::Dependency.new(pattern, Gem::Requirement.default) 
+      end
+      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
+
+      # TODO - Remove this gunk after 2008/11
+      unless pattern.kind_of?(Gem::Dependency)
+        pattern = Gem::Dependency.new(pattern, Gem::Requirement.default) 
+      end
+
+      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
+
+    return path if File.writable? dir
+
+    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 files.
+
+  def write_cache
+    if not File.exist?(cache_file) or not @only_latest then
+      open cache_file, 'wb' do |io|
+        io.write Marshal.dump(cache_data)
+      end
+    end
+
+    open latest_cache_file, 'wb' do |io|
+      io.write Marshal.dump(latest_cache_data)
+    end
+  end
+
+  reset
+
+end
+