1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
|
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 prefered 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
@cache = nil
@system_cache_file = nil
@user_cache_file = nil
def self.cache
return @cache if @cache
@cache = new
@cache.refresh if Gem.configuration.update_sources
@cache
end
def self.cache_data
cache.cache_data
end
# Search all source indexes for +pattern+.
def self.search(pattern, platform_only = false)
cache.search pattern, platform_only
end
# Search all source indexes for +pattern+. Only returns gems matching
# Gem.platforms when +only_platform+ is true. See #search_with_source.
def self.search_with_source(pattern, only_platform = false)
cache.search_with_source(pattern, only_platform)
end
def initialize # :nodoc:
@cache_data = nil
@cache_file = nil
@dirty = false
end
# The most recent cache data.
def cache_data
return @cache_data if @cache_data
cache_file # HACK writable check
begin
# Marshal loads 30-40% faster from a String, and 2MB on 20061116 is small
data = File.open cache_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)
end
end
@cache_data
rescue => e
if Gem.configuration.really_verbose then
say "Exception during cache_data handling: #{ex.class} - #{ex}"
say "Cache file was: #{cache_file}"
say "\t#{e.backtrace.join "\n\t"}"
end
reset_cache_data
end
end
def reset_cache_for(url)
say "Reseting cache for #{url}" if Gem.configuration.really_verbose
sice = Gem::SourceInfoCacheEntry.new Gem::SourceIndex.new, 0
sice.refresh url # HACK may be unnecessary, see ::cache and #refresh
@cache_data[url] = sice
@cache_data
end
def reset_cache_data
@cache_data = {}
end
# The name of the cache file to be read
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
# Refreshes each source in the cache from its repository.
def refresh
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
end
flush
end
# Searches all source indexes for +pattern+.
def search(pattern, platform_only = false)
cache_data.map do |source_uri, sic_entry|
next unless Gem.sources.include? source_uri
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)
results = []
cache_data.map do |source_uri, sic_entry|
next unless Gem.sources.include? source_uri
sic_entry.source_index.search(pattern, only_platform).each do |spec|
results << [spec, source_uri]
end
end
results
end
# Mark the cache as updated (i.e. dirty).
def update
@dirty = true
end
# The name of the system cache file.
def system_cache_file
self.class.system_cache_file
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 user_cache_file
self.class.user_cache_file
end
# The name of the user cache file. (class method)
def self.user_cache_file
@user_cache_file ||=
ENV['GEMCACHE'] || Gem.default_user_source_cache_dir
end
# Write data to the proper cache.
def write_cache
open cache_file, "wb" do |f|
f.write Marshal.dump(cache_data)
end
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
private
# Determine if +fn+ is a candidate for a cache file. Return fn if
# it is. Return nil if it is not.
def try_file(fn)
return fn if File.writable?(fn)
return nil if File.exist?(fn)
dir = File.dirname(fn)
unless File.exist? dir then
begin
FileUtils.mkdir_p(dir)
rescue RuntimeError, SystemCallError
return nil
end
end
if File.writable?(dir)
File.open(fn, "wb") { |f| f << Marshal.dump({}) }
return fn
end
nil
end
end
|