PageRenderTime 103ms CodeModel.GetById 83ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 1ms

/tools/Ruby/lib/ruby/site_ruby/1.8/rubygems/source_info_cache.rb

http://github.com/agross/netopenspace
Ruby | 395 lines | 212 code | 94 blank | 89 comment | 19 complexity | da32563681f17616728d6d1d373bc810 MD5 | raw file
  1require 'fileutils'
  2
  3require 'rubygems'
  4require 'rubygems/source_info_cache_entry'
  5require 'rubygems/user_interaction'
  6
  7##
  8# SourceInfoCache stores a copy of the gem index for each gem source.
  9#
 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
 13#
 14# Once a cache is selected, it will be used for all operations.
 15# SourceInfoCache will not switch between cache files dynamically.
 16#
 17# Cache data is a Hash mapping a source URI to a SourceInfoCacheEntry.
 18#
 19#--
 20# To keep things straight, this is how the cache objects all fit together:
 21#
 22#   Gem::SourceInfoCache
 23#     @cache_data = {
 24#       source_uri => Gem::SourceInfoCacheEntry
 25#         @size = source index size
 26#         @source_index = Gem::SourceIndex
 27#       ...
 28#     }
 29
 30class Gem::SourceInfoCache
 31
 32  include Gem::UserInteraction
 33
 34  ##
 35  # The singleton Gem::SourceInfoCache.  If +all+ is true, a full refresh will
 36  # be performed if the singleton instance is being initialized.
 37
 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
 44
 45  def self.cache_data
 46    cache.cache_data
 47  end
 48
 49  ##
 50  # The name of the system cache file.
 51
 52  def self.latest_system_cache_file
 53    File.join File.dirname(system_cache_file),
 54              "latest_#{File.basename system_cache_file}"
 55  end
 56
 57  ##
 58  # The name of the latest user cache file.
 59
 60  def self.latest_user_cache_file
 61    File.join File.dirname(user_cache_file),
 62              "latest_#{File.basename user_cache_file}"
 63  end
 64
 65  ##
 66  # Reset all singletons, discarding any changes.
 67
 68  def self.reset
 69    @cache = nil
 70    @system_cache_file = nil
 71    @user_cache_file = nil
 72  end
 73
 74  ##
 75  # Search all source indexes.  See Gem::SourceInfoCache#search.
 76
 77  def self.search(*args)
 78    cache.search(*args)
 79  end
 80
 81  ##
 82  # Search all source indexes returning the source_uri.  See
 83  # Gem::SourceInfoCache#search_with_source.
 84
 85  def self.search_with_source(*args)
 86    cache.search_with_source(*args)
 87  end
 88
 89  ##
 90  # The name of the system cache file. (class method)
 91
 92  def self.system_cache_file
 93    @system_cache_file ||= Gem.default_system_source_cache_dir
 94  end
 95
 96  ##
 97  # The name of the user cache file.
 98
 99  def self.user_cache_file
100    @user_cache_file ||=
101      ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
102  end
103
104  def initialize # :nodoc:
105    @cache_data = nil
106    @cache_file = nil
107    @dirty = false
108    @only_latest = true
109  end
110
111  ##
112  # The most recent cache data.
113
114  def cache_data
115    return @cache_data if @cache_data
116    cache_file # HACK writable check
117
118    @only_latest = true
119
120    @cache_data = read_cache_data latest_cache_file
121
122    @cache_data
123  end
124
125  ##
126  # The name of the cache file.
127
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
134
135  ##
136  # Write the cache to a local file (if it is dirty).
137
138  def flush
139    write_cache if @dirty
140    @dirty = false
141  end
142
143  def latest_cache_data
144    latest_cache_data = {}
145
146    cache_data.each do |repo, sice|
147      latest = sice.source_index.latest_specs
148
149      new_si = Gem::SourceIndex.new
150      new_si.add_specs(*latest)
151
152      latest_sice = Gem::SourceInfoCacheEntry.new new_si, sice.size
153      latest_cache_data[repo] = latest_sice
154    end
155
156    latest_cache_data
157  end
158
159  ##
160  # The name of the latest cache file.
161
162  def latest_cache_file
163    File.join File.dirname(cache_file), "latest_#{File.basename cache_file}"
164  end
165
166  ##
167  # The name of the latest system cache file.
168
169  def latest_system_cache_file
170    self.class.latest_system_cache_file
171  end
172
173  ##
174  # The name of the latest user cache file.
175
176  def latest_user_cache_file
177    self.class.latest_user_cache_file
178  end
179
180  ##
181  # Merges the complete cache file into this Gem::SourceInfoCache.
182
183  def read_all_cache_data
184    if @only_latest then
185      @only_latest = false
186      all_data = read_cache_data cache_file
187
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
190
191        Gem::SourceInfoCacheEntry.new all_sice.source_index, latest_sice.size
192      end
193
194      begin
195        refresh true
196      rescue Gem::RemoteFetcher::FetchError
197      end
198    end
199  end
200
201  ##
202  # Reads cached data from +file+.
203
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
208
209    cache_data.each do |url, sice|
210      next unless sice.is_a?(Hash)
211      update
212
213      cache = sice['cache']
214      size  = sice['size']
215
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
223
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
233
234    {}
235  end
236
237  ##
238  # Refreshes each source in the cache from its repository.  If +all+ is
239  # false, only latest gems are updated.
240
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
248
249      update if cache_entry.refresh source_uri, all
250    end
251
252    flush
253  end
254
255  def reset_cache_for(url, cache_data)
256    say "Reseting cache for #{url}" if Gem.configuration.really_verbose
257
258    sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
259    sice.refresh url, false # HACK may be unnecessary, see ::cache and #refresh
260
261    cache_data[url] = sice
262    cache_data
263  end
264
265  def reset_cache_data
266    @cache_data = nil
267    @only_latest = true
268  end
269
270  ##
271  # Force cache file to be reset, useful for integration testing of rubygems
272
273  def reset_cache_file
274    @cache_file = nil
275  end
276
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.
281
282  def search(pattern, platform_only = false, all = false)
283    read_all_cache_data if all
284
285    cache_data.map do |source_uri, sic_entry|
286      next unless Gem.sources.include? source_uri
287      # TODO - Remove this gunk after 2008/11
288      unless pattern.kind_of? Gem::Dependency then
289        pattern = Gem::Dependency.new pattern, Gem::Requirement.default
290      end
291      sic_entry.source_index.search pattern, platform_only
292    end.flatten.compact
293  end
294
295  ##
296  # Searches all source indexes for +pattern+.  If +only_platform+ is true,
297  # only gems matching Gem.platforms will be selected.  Returns an Array of
298  # pairs containing the Gem::Specification found and the source_uri it was
299  # found at.
300
301  def search_with_source(pattern, only_platform = false, all = false)
302    read_all_cache_data if all
303
304    results = []
305
306    cache_data.map do |source_uri, sic_entry|
307      next unless Gem.sources.include? source_uri
308
309      # TODO - Remove this gunk after 2008/11
310      unless pattern.kind_of?(Gem::Dependency)
311        pattern = Gem::Dependency.new(pattern, Gem::Requirement.default) 
312      end
313
314      sic_entry.source_index.search(pattern, only_platform).each do |spec|
315        results << [spec, source_uri]
316      end
317    end
318
319    results
320  end
321
322  ##
323  # Set the source info cache data directly.  This is mainly used for unit
324  # testing when we don't want to read a file system to grab the cached source
325  # index information.  The +hash+ should map a source URL into a
326  # SourceInfoCacheEntry.
327
328  def set_cache_data(hash)
329    @cache_data = hash
330    update
331  end
332
333  ##
334  # The name of the system cache file.
335
336  def system_cache_file
337    self.class.system_cache_file
338  end
339
340  ##
341  # Determine if +path+ is a candidate for a cache file.  Returns +path+ if
342  # it is, nil if not.
343
344  def try_file(path)
345    return path if File.writable? path
346    return nil if File.exist? path
347
348    dir = File.dirname path
349
350    unless File.exist? dir then
351      begin
352        FileUtils.mkdir_p dir
353      rescue RuntimeError, SystemCallError
354        return nil
355      end
356    end
357
358    return path if File.writable? dir
359
360    nil
361  end
362
363  ##
364  # Mark the cache as updated (i.e. dirty).
365
366  def update
367    @dirty = true
368  end
369
370  ##
371  # The name of the user cache file.
372
373  def user_cache_file
374    self.class.user_cache_file
375  end
376
377  ##
378  # Write data to the proper cache files.
379
380  def write_cache
381    if not File.exist?(cache_file) or not @only_latest then
382      open cache_file, 'wb' do |io|
383        io.write Marshal.dump(cache_data)
384      end
385    end
386
387    open latest_cache_file, 'wb' do |io|
388      io.write Marshal.dump(latest_cache_data)
389    end
390  end
391
392  reset
393
394end
395