Fix up Rubinius specific library specs.
[rbx.git] / lib / rubygems / source_info_cache.rb
blobec6928c00daf962c8c81d564943b9b6ae473defd
1 require 'fileutils'
3 require 'rubygems'
4 require 'rubygems/source_info_cache_entry'
5 require 'rubygems/user_interaction'
7 ##
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.
19 #--
20 # To keep things straight, this is how the cache objects all fit together:
22 #   Gem::SourceInfoCache
23 #     @cache_data = {
24 #       source_uri => Gem::SourceInfoCacheEntry
25 #         @size = source index size
26 #         @source_index = Gem::SourceIndex
27 #       ...
28 #     }
30 class Gem::SourceInfoCache
32   include Gem::UserInteraction
34   ##
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
40     @cache = new
41     @cache.refresh all if Gem.configuration.update_sources
42     @cache
43   end
45   def self.cache_data
46     cache.cache_data
47   end
49   ##
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}"
55   end
57   ##
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}"
63   end
65   ##
66   # Reset all singletons, discarding any changes.
68   def self.reset
69     @cache = nil
70     @system_cache_file = nil
71     @user_cache_file = nil
72   end
74   ##
75   # Search all source indexes.  See Gem::SourceInfoCache#search.
77   def self.search(*args)
78     cache.search(*args)
79   end
81   ##
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)
87   end
89   ##
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
94   end
96   ##
97   # The name of the user cache file.
99   def self.user_cache_file
100     @user_cache_file ||=
101       ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
102   end
104   def initialize # :nodoc:
105     @cache_data = nil
106     @cache_file = nil
107     @dirty = false
108     @only_latest = true
109   end
111   ##
112   # The most recent cache data.
114   def cache_data
115     return @cache_data if @cache_data
116     cache_file # HACK writable check
118     @only_latest = true
120     @cache_data = read_cache_data latest_cache_file
122     @cache_data
123   end
125   ##
126   # The name of the cache file.
128   def 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")
133   end
135   ##
136   # Write the cache to a local file (if it is dirty).
138   def flush
139     write_cache if @dirty
140     @dirty = false
141   end
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
154     end
156     latest_cache_data
157   end
159   ##
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}"
164   end
166   ##
167   # The name of the latest system cache file.
169   def latest_system_cache_file
170     self.class.latest_system_cache_file
171   end
173   ##
174   # The name of the latest user cache file.
176   def latest_user_cache_file
177     self.class.latest_user_cache_file
178   end
180   ##
181   # Merges the complete cache file into this Gem::SourceInfoCache.
183   def read_all_cache_data
184     if @only_latest then
185       @only_latest = false
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
192       end
194       begin
195         refresh true
196       rescue Gem::RemoteFetcher::FetchError
197       end
198     end
199   end
201   ##
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)
211       update
213       cache = sice['cache']
214       size  = sice['size']
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
221       end
222     end
224     cache_data
225   rescue Errno::ENOENT
226     {}
227   rescue => e
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"}"
232     end
234     {}
235   end
237   ##
238   # Refreshes each source in the cache from its repository.  If +all+ is
239   # false, only latest gems are updated.
241   def refresh(all)
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
247       end
249       update if cache_entry.refresh source_uri, all
250     end
252     flush
253   end
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
262     cache_data
263   end
265   def reset_cache_data
266     @cache_data = nil
267     @only_latest = true
268   end
270   ##
271   # Force cache file to be reset, useful for integration testing of rubygems
273   def reset_cache_file
274     @cache_file = nil
275   end
277   ##
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
288     end.flatten.compact
289   end
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
294   # found at.
295   def search_with_source(pattern, only_platform = false, all = false)
296     read_all_cache_data if all
298     results = []
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]
305       end
306     end
308     results
309   end
311   ##
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)
318     @cache_data = hash
319     update
320   end
322   ##
323   # The name of the system cache file.
325   def system_cache_file
326     self.class.system_cache_file
327   end
329   ##
330   # Determine if +path+ is a candidate for a cache file.  Returns +path+ if
331   # it is, nil if not.
333   def try_file(path)
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
340       begin
341         FileUtils.mkdir_p dir
342       rescue RuntimeError, SystemCallError
343         return nil
344       end
345     end
347     return path if File.writable? dir
349     nil
350   end
352   ##
353   # Mark the cache as updated (i.e. dirty).
355   def update
356     @dirty = true
357   end
359   ##
360   # The name of the user cache file.
362   def user_cache_file
363     self.class.user_cache_file
364   end
366   ##
367   # Write data to the proper cache files.
369   def write_cache
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)
373       end
374     end
376     open latest_cache_file, 'wb' do |io|
377       io.write Marshal.dump(latest_cache_data)
378     end
379   end
381   reset