4 require 'rubygems/remote_fetcher'
5 require 'rubygems/user_interaction'
8 # SpecFetcher handles metadata updates from remote gem repositories.
10 class Gem::SpecFetcher
12 include Gem::UserInteraction
15 # The SpecFetcher cache dir.
17 attr_reader :dir # :nodoc:
20 # Cache of latest specs
22 attr_reader :latest_specs # :nodoc:
27 attr_reader :specs # :nodoc:
35 def self.fetcher=(fetcher) # :nodoc:
40 @dir = File.join Gem.user_home, '.gem', 'specs'
41 @update_cache = File.stat(Gem.user_home).uid == Process.uid
46 @fetcher = Gem::RemoteFetcher.fetcher
50 # Retuns the local directory to write +uri+ to.
53 File.join @dir, "#{uri.host}%#{uri.port}", File.dirname(uri.path)
57 # Fetch specs matching +dependency+. If +all+ is true, all matching
58 # versions are returned. If +matching_platform+ is false, all platforms are
61 def fetch(dependency, all = false, matching_platform = true)
62 specs_and_sources = find_matching dependency, all, matching_platform
64 specs_and_sources.map do |spec_tuple, source_uri|
65 [fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
68 rescue Gem::RemoteFetcher::FetchError => e
69 raise unless warn_legacy e do
70 require 'rubygems/source_info_cache'
72 return Gem::SourceInfoCache.search_with_source(dependency,
73 matching_platform, all)
77 def fetch_spec(spec, source_uri)
78 spec = spec - [nil, 'ruby', '']
79 spec_file_name = "#{spec.join '-'}.gemspec"
81 uri = source_uri + "#{Gem::MARSHAL_SPEC_DIR}#{spec_file_name}"
83 cache_dir = cache_dir uri
85 local_spec = File.join cache_dir, spec_file_name
87 if File.exist? local_spec then
88 spec = Gem.read_binary local_spec
92 spec = @fetcher.fetch_path uri
93 spec = Gem.inflate spec
96 FileUtils.mkdir_p cache_dir
98 open local_spec, 'wb' do |io|
104 # TODO: Investigate setting Gem::Specification#loaded_from to a URI
109 # Find spec names that match +dependency+. If +all+ is true, all matching
110 # versions are returned. If +matching_platform+ is false, gems for all
111 # platforms are returned.
113 def find_matching(dependency, all = false, matching_platform = true)
116 list(all).each do |source_uri, specs|
117 found[source_uri] = specs.select do |spec_name, version, spec_platform|
118 dependency =~ Gem::Dependency.new(spec_name, version) and
119 (not matching_platform or Gem::Platform.match(spec_platform))
123 specs_and_sources = []
125 found.each do |source_uri, specs|
126 uri_str = source_uri.to_s
127 specs_and_sources.push(*specs.map { |spec| [spec, uri_str] })
134 # Returns Array of gem repositories that were generated with RubyGems less
138 Gem.sources.reject do |source_uri|
139 source_uri = URI.parse source_uri
140 spec_path = source_uri + "specs.#{Gem.marshal_version}.gz"
143 @fetcher.fetch_size spec_path
144 rescue Gem::RemoteFetcher::FetchError
146 @fetcher.fetch_size(source_uri + 'yaml') # re-raise if non-repo
147 rescue Gem::RemoteFetcher::FetchError
148 alert_error "#{source_uri} does not appear to be a repository"
157 # Returns a list of gems available for each source in Gem::sources. If
158 # +all+ is true, all versions are returned instead of only latest versions.
160 def list(all = false)
163 file = all ? 'specs' : 'latest_specs'
165 Gem.sources.each do |source_uri|
166 source_uri = URI.parse source_uri
168 if all and @specs.include? source_uri then
169 list[source_uri] = @specs[source_uri]
170 elsif @latest_specs.include? source_uri then
171 list[source_uri] = @latest_specs[source_uri]
173 specs = load_specs source_uri, file
175 cache = all ? @specs : @latest_specs
177 cache[source_uri] = specs
178 list[source_uri] = specs
185 def load_specs(source_uri, file)
186 file_name = "#{file}.#{Gem.marshal_version}.gz"
188 spec_path = source_uri + file_name
190 cache_dir = cache_dir spec_path
192 local_file = File.join(cache_dir, file_name).chomp '.gz'
194 if File.exist? local_file then
195 local_size = File.stat(local_file).size
197 remote_file = spec_path.dup
198 remote_file.path = remote_file.path.chomp '.gz'
199 remote_size = @fetcher.fetch_size remote_file
201 spec_dump = Gem.read_binary local_file if remote_size == local_size
204 unless spec_dump then
207 spec_dump_gz = @fetcher.fetch_path spec_path
208 spec_dump = Gem.gunzip spec_dump_gz
211 specs = Marshal.load spec_dump
213 if loaded and @update_cache then
215 FileUtils.mkdir_p cache_dir
217 open local_file, 'wb' do |io|
218 Marshal.dump specs, io
228 # Warn about legacy repositories if +exception+ indicates only legacy
229 # repositories are available, and yield to the block. Returns false if the
230 # exception indicates some other FetchError.
232 def warn_legacy(exception)
233 uri = exception.uri.to_s
234 if uri =~ /specs\.#{Regexp.escape Gem.marshal_version}\.gz$/ then
236 RubyGems 1.2+ index not found for:
237 \t#{legacy_repos.join "\n\t"}
239 RubyGems will revert to legacy indexes degrading performance.