4 require 'rubygems/source_info_cache_entry'
5 require 'rubygems/user_interaction'
8 # SourceInfoCache stores a copy of the gem index for each gem source.
10 # There are two possible cache locations, the system cache and the user cache:
11 # * The system cache is preferred if it is writable or can be created.
12 # * The user cache is used otherwise
14 # Once a cache is selected, it will be used for all operations.
15 # SourceInfoCache will not switch between cache files dynamically.
17 # Cache data is a Hash mapping a source URI to a SourceInfoCacheEntry.
20 # To keep things straight, this is how the cache objects all fit together:
22 # Gem::SourceInfoCache
24 # source_uri => Gem::SourceInfoCacheEntry
25 # @size = source index size
26 # @source_index = Gem::SourceIndex
30 class Gem::SourceInfoCache
32 include Gem::UserInteraction
35 # The singleton Gem::SourceInfoCache. If +all+ is true, a full refresh will
36 # be performed if the singleton instance is being initialized.
38 def self.cache(all = false)
39 return @cache if @cache
41 @cache.refresh all if Gem.configuration.update_sources
50 # The name of the system cache file.
52 def self.latest_system_cache_file
53 File.join File.dirname(system_cache_file),
54 "latest_#{File.basename system_cache_file}"
58 # The name of the latest user cache file.
60 def self.latest_user_cache_file
61 File.join File.dirname(user_cache_file),
62 "latest_#{File.basename user_cache_file}"
66 # Reset all singletons, discarding any changes.
70 @system_cache_file = nil
71 @user_cache_file = nil
75 # Search all source indexes. See Gem::SourceInfoCache#search.
77 def self.search(*args)
82 # Search all source indexes returning the source_uri. See
83 # Gem::SourceInfoCache#search_with_source.
85 def self.search_with_source(*args)
86 cache.search_with_source(*args)
90 # The name of the system cache file. (class method)
92 def self.system_cache_file
93 @system_cache_file ||= Gem.default_system_source_cache_dir
97 # The name of the user cache file.
99 def self.user_cache_file
101 ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
104 def initialize # :nodoc:
112 # The most recent cache data.
115 return @cache_data if @cache_data
116 cache_file # HACK writable check
120 @cache_data = read_cache_data latest_cache_file
126 # The name of the cache file.
129 return @cache_file if @cache_file
130 @cache_file = (try_file(system_cache_file) or
131 try_file(user_cache_file) or
132 raise "unable to locate a writable cache file")
136 # Write the cache to a local file (if it is dirty).
139 write_cache if @dirty
143 def latest_cache_data
144 latest_cache_data = {}
146 cache_data.each do |repo, sice|
147 latest = sice.source_index.latest_specs
149 new_si = Gem::SourceIndex.new
150 new_si.add_specs(*latest)
152 latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size
153 latest_cache_data[repo] = latest_sice
160 # The name of the latest cache file.
162 def latest_cache_file
163 File.join File.dirname(cache_file), "latest_#{File.basename cache_file}"
167 # The name of the latest system cache file.
169 def latest_system_cache_file
170 self.class.latest_system_cache_file
174 # The name of the latest user cache file.
176 def latest_user_cache_file
177 self.class.latest_user_cache_file
181 # Merges the complete cache file into this Gem::SourceInfoCache.
183 def read_all_cache_data
186 all_data = read_cache_data cache_file
188 cache_data.update all_data do |source_uri, latest_sice, all_sice|
189 all_sice.source_index.gems.update latest_sice.source_index.gems
191 Gem::SourceInfoCacheEntry.new all_sice.source_index, latest_sice.size
196 rescue Gem::RemoteFetcher::FetchError
202 # Reads cached data from +file+.
204 def read_cache_data(file)
205 # Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
206 data = open file, 'rb' do |fp| fp.read end
207 cache_data = Marshal.load data
209 cache_data.each do |url, sice|
210 next unless sice.is_a?(Hash)
213 cache = sice['cache']
216 if cache.is_a?(Gem::SourceIndex) and size.is_a?(Numeric) then
217 new_sice = Gem::SourceInfoCacheEntry.new cache, size
218 cache_data[url] = new_sice
219 else # irreperable, force refetch.
220 reset_cache_for url, cache_data
228 if Gem.configuration.really_verbose then
229 say "Exception during cache_data handling: #{e.class} - #{e}"
230 say "Cache file was: #{file}"
231 say "\t#{e.backtrace.join "\n\t"}"
238 # Refreshes each source in the cache from its repository. If +all+ is
239 # false, only latest gems are updated.
242 Gem.sources.each do |source_uri|
243 cache_entry = cache_data[source_uri]
244 if cache_entry.nil? then
245 cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
246 cache_data[source_uri] = cache_entry
249 update if cache_entry.refresh source_uri, all
255 def reset_cache_for(url, cache_data)
256 say "Reseting cache for #{url}" if Gem.configuration.really_verbose
258 sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
259 sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh
261 cache_data[url] = sice
271 # Force cache file to be reset, useful for integration testing of rubygems
278 # Searches all source indexes. See Gem::SourceIndex#search for details on
279 # +pattern+ and +platform_only+. If +all+ is set to true, the full index
280 # will be loaded before searching.
282 def search(pattern, platform_only = false, all = false)
283 read_all_cache_data if all
285 cache_data.map do |source_uri, sic_entry|
286 next unless Gem.sources.include? source_uri
287 sic_entry.source_index.search pattern, platform_only
291 # Searches all source indexes for +pattern+. If +only_platform+ is true,
292 # only gems matching Gem.platforms will be selected. Returns an Array of
293 # pairs containing the Gem::Specification found and the source_uri it was
295 def search_with_source(pattern, only_platform = false, all = false)
296 read_all_cache_data if all
300 cache_data.map do |source_uri, sic_entry|
301 next unless Gem.sources.include? source_uri
303 sic_entry.source_index.search(pattern, only_platform).each do |spec|
304 results << [spec, source_uri]
312 # Set the source info cache data directly. This is mainly used for unit
313 # testing when we don't want to read a file system to grab the cached source
314 # index information. The +hash+ should map a source URL into a
315 # SourceInfoCacheEntry.
317 def set_cache_data(hash)
323 # The name of the system cache file.
325 def system_cache_file
326 self.class.system_cache_file
330 # Determine if +path+ is a candidate for a cache file. Returns +path+ if
334 return path if File.writable? path
335 return nil if File.exist? path
337 dir = File.dirname path
339 unless File.exist? dir then
341 FileUtils.mkdir_p dir
342 rescue RuntimeError, SystemCallError
347 return path if File.writable? dir
353 # Mark the cache as updated (i.e. dirty).
360 # The name of the user cache file.
363 self.class.user_cache_file
367 # Write data to the proper cache files.
370 if not File.exist?(cache_file) or not @only_latest then
371 open cache_file, 'wb' do |io|
372 io.write Marshal.dump(cache_data)
376 open latest_cache_file, 'wb' do |io|
377 io.write Marshal.dump(latest_cache_data)