gemirro 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +29 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +6 -3
  5. data/Gemfile.lock +126 -0
  6. data/MANIFEST +3 -10
  7. data/README.md +1 -1
  8. data/bin/gemirro +7 -1
  9. data/gemirro.gemspec +7 -2
  10. data/lib/gemirro/cli/index.rb +14 -4
  11. data/lib/gemirro/cli/init.rb +1 -1
  12. data/lib/gemirro/cli/update.rb +6 -0
  13. data/lib/gemirro/cli.rb +3 -1
  14. data/lib/gemirro/configuration.rb +4 -12
  15. data/lib/gemirro/gems_fetcher.rb +7 -10
  16. data/lib/gemirro/indexer.rb +286 -178
  17. data/lib/gemirro/mirror_file.rb +1 -0
  18. data/lib/gemirro/server.rb +56 -181
  19. data/lib/gemirro/source.rb +2 -15
  20. data/lib/gemirro/utils.rb +130 -65
  21. data/lib/gemirro/version.rb +1 -1
  22. data/lib/gemirro/versions_fetcher.rb +9 -10
  23. data/lib/gemirro/versions_file.rb +26 -24
  24. data/lib/gemirro.rb +0 -1
  25. data/spec/gemirro/configuration_spec.rb +9 -2
  26. data/spec/gemirro/gems_fetcher_spec.rb +1 -1
  27. data/spec/gemirro/indexer_spec.rb +5 -5
  28. data/spec/gemirro/server_spec.rb +79 -73
  29. data/spec/gemirro/source_spec.rb +1 -10
  30. data/spec/gemirro/versions_fetcher_spec.rb +3 -4
  31. data/spec/gemirro/versions_file_spec.rb +8 -32
  32. data/template/config.rb +7 -1
  33. data/template/public/dist/css/gemirro.css +25 -1
  34. data/views/gem.erb +46 -37
  35. data/views/index.erb +41 -33
  36. data/views/layout.erb +4 -17
  37. data/views/not_found.erb +4 -4
  38. metadata +79 -16
  39. data/lib/gemirro/cache.rb +0 -115
  40. data/spec/gemirro/cache_spec.rb +0 -32
  41. data/template/public/dist/css/bootstrap.min.css +0 -7
  42. data/template/public/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  43. data/template/public/dist/fonts/glyphicons-halflings-regular.svg +0 -288
  44. data/template/public/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  45. data/template/public/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  46. data/template/public/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  47. data/template/public/dist/js/bootstrap.min.js +0 -7
@@ -19,12 +19,14 @@ module Gemirro
19
19
  # @return [Array]
20
20
  #
21
21
  class Indexer < ::Gem::Indexer
22
- attr_accessor(:files,
23
- :quick_marshal_dir,
24
- :directory,
25
- :dest_directory,
26
- :only_origin,
27
- :updated_gems)
22
+ attr_accessor(
23
+ :files,
24
+ :quick_marshal_dir,
25
+ :directory,
26
+ :dest_directory,
27
+ :only_origin,
28
+ :updated_gems
29
+ )
28
30
 
29
31
  ##
30
32
  # Create an indexer that will index the gems in +directory+.
@@ -33,48 +35,43 @@ module Gemirro
33
35
  # @param [Hash] options Indexer options
34
36
  # @return [Array]
35
37
  ##
36
- def initialize(directory, options = {})
38
+ def initialize(options = {})
37
39
  require 'fileutils'
38
40
  require 'tmpdir'
39
41
  require 'zlib'
42
+ require 'builder/xchar'
43
+ require 'compact_index'
40
44
 
41
- unless defined?(Builder::XChar)
42
- raise 'Gem::Indexer requires that the XML Builder ' \
43
- 'library be installed:' \
44
- "\n\tgem install builder"
45
- end
46
-
47
- options = { build_modern: true }.merge options
45
+ options.merge!({ build_modern: false })
48
46
 
49
- @build_modern = options[:build_modern]
50
-
51
- @dest_directory = directory
52
- @directory = File.join(Dir.tmpdir,
53
- "gem_generate_index_#{rand(1_000_000_000)}")
47
+ @dest_directory = Gemirro.configuration.destination
48
+ @directory =
49
+ File.join(Dir.tmpdir, "gem_generate_index_#{rand(1_000_000_000)}")
54
50
 
55
51
  marshal_name = "Marshal.#{::Gem.marshal_version}"
56
52
 
57
- @master_index = File.join @directory, 'yaml'
58
- @marshal_index = File.join @directory, marshal_name
53
+ @master_index =
54
+ File.join(@directory, 'yaml')
55
+ @marshal_index =
56
+ File.join(@directory, marshal_name)
59
57
 
60
- @quick_dir = File.join @directory, 'quick'
61
- @quick_marshal_dir = File.join @quick_dir, marshal_name
62
- @quick_marshal_dir_base = File.join 'quick', marshal_name # FIX: UGH
58
+ @quick_dir = File.join(@directory, 'quick')
59
+ @quick_marshal_dir =
60
+ File.join(@quick_dir, marshal_name)
61
+ @quick_marshal_dir_base =
62
+ File.join(@dest_directory, 'quick', marshal_name) # FIX: UGH
63
63
 
64
- @quick_index = File.join @quick_dir, 'index'
65
- @latest_index = File.join @quick_dir, 'latest_index'
64
+ @quick_index =
65
+ File.join(@quick_dir, 'index')
66
+ @latest_index =
67
+ File.join(@quick_dir, 'latest_index')
66
68
 
67
- @specs_index = File.join @directory, "specs.#{::Gem.marshal_version}"
68
69
  @latest_specs_index =
69
70
  File.join(@directory, "latest_specs.#{::Gem.marshal_version}")
70
- @prerelease_specs_index =
71
- File.join(@directory, "prerelease_specs.#{::Gem.marshal_version}")
72
- @dest_specs_index =
73
- File.join(@dest_directory, "specs.#{::Gem.marshal_version}")
74
71
  @dest_latest_specs_index =
75
72
  File.join(@dest_directory, "latest_specs.#{::Gem.marshal_version}")
76
- @dest_prerelease_specs_index =
77
- File.join(@dest_directory, "prerelease_specs.#{::Gem.marshal_version}")
73
+ @infos_dir =
74
+ File.join(@dest_directory, 'info')
78
75
 
79
76
  @files = []
80
77
  end
@@ -82,16 +79,9 @@ module Gemirro
82
79
  ##
83
80
  # Generate indices on the destination directory
84
81
  #
85
- def install_indices
86
- install_indicies
87
- end
88
-
89
- ##
90
- # Generate indicies on the destination directory
91
- #
92
82
  # @return [Array]
93
83
  #
94
- def install_indicies
84
+ def install_indices
95
85
  Utils.logger
96
86
  .debug("Downloading index into production dir #{@dest_directory}")
97
87
 
@@ -100,43 +90,34 @@ module Gemirro
100
90
 
101
91
  if files.include?(@quick_marshal_dir) && !files.include?(@quick_dir)
102
92
  files.delete @quick_marshal_dir
103
- dst_name = File.join(@dest_directory, @quick_marshal_dir_base)
104
- FileUtils.mkdir_p(File.dirname(dst_name), verbose: verbose)
105
- FileUtils.rm_rf(dst_name, verbose: verbose)
106
- FileUtils.mv(@quick_marshal_dir, dst_name,
107
- verbose: verbose, force: true)
93
+ FileUtils.mkdir_p(File.dirname(@quick_marshal_dir_base), verbose: verbose)
94
+ if @quick_marshal_dir_base && File.exist?(@quick_marshal_dir_base)
95
+ FileUtils.rm_rf(@quick_marshal_dir_base, verbose: verbose)
96
+ end
97
+ FileUtils.mv(@quick_marshal_dir, @quick_marshal_dir_base, verbose: verbose, force: true)
108
98
  end
109
99
 
110
100
  files.each do |path|
111
101
  file = path.sub(%r{^#{Regexp.escape @directory}/?}, '')
112
- src_name = File.join(@directory, file)
113
- dst_name = File.join(@dest_directory, file)
114
-
115
- if ["#{@specs_index}.gz",
116
- "#{@latest_specs_index}.gz",
117
- "#{@prerelease_specs_index}.gz"].include?(path)
118
- res = build_zlib_file(file, src_name, dst_name, true)
119
- next unless res
120
- else
121
- source_content = download_from_source(file)
122
- next if source_content.nil?
123
102
 
124
- MirrorFile.new(dst_name).write(source_content)
125
- end
103
+ source_content = download_from_source(file)
104
+ next if source_content.nil?
105
+
106
+ MirrorFile.new(File.join(@dest_directory, file)).write(source_content)
126
107
 
127
108
  FileUtils.rm_rf(path)
128
109
  end
129
110
  end
130
111
 
131
112
  ##
132
- # Download file from source
113
+ # Download file from source (example: rubygems.org)
133
114
  #
134
115
  # @param [String] file File path
135
116
  # @return [String]
136
117
  #
137
118
  def download_from_source(file)
138
119
  source_host = Gemirro.configuration.source.host
139
- Utils.logger.info("Download from source: #{file}")
120
+ Utils.logger.info("Download from source #{source_host}/#{file}")
140
121
  resp = Http.get("#{source_host}/#{File.basename(file)}")
141
122
  return unless resp.code == 200
142
123
 
@@ -149,29 +130,184 @@ module Gemirro
149
130
  # @return [Array]
150
131
  #
151
132
  def build_indices
152
- build_indicies
133
+ specs = *map_gems_to_specs(gem_file_list)
134
+ specs.select! { |s| s.instance_of?(::Gem::Specification) }
135
+ ::Gem::Specification.dirs = []
136
+ ::Gem::Specification.all = specs
137
+
138
+ build_marshal_gemspecs(specs)
139
+
140
+ build_compact_index_names
141
+ build_compact_index_infos(specs)
142
+ build_compact_index_versions(specs)
153
143
  end
154
144
 
155
145
  ##
156
- # Build indicies
146
+ # Cache compact_index endpoint /names
147
+ # Report all gems with versions available. Does not require opening spec files.
157
148
  #
158
- # @return [Array]
149
+ # @return nil
159
150
  #
160
- def build_indicies
161
- specs = *map_gems_to_specs(gem_file_list)
162
- specs.select! { |s| s.instance_of?(::Gem::Specification) }
163
- ::Gem::Specification.dirs = []
164
- ::Gem::Specification.all = specs
151
+ def build_compact_index_names
152
+ Utils.logger.info('[1/1]: Caching /names')
153
+ FileUtils.rm_rf(Dir.glob(File.join(@dest_directory, 'names*.list')))
154
+
155
+ gem_name_list = Dir.glob('*.gem', base: File.join(@dest_directory, 'gems')).collect do |x|
156
+ x.sub(/-\d+(\.\d+)*(\.[a-zA-Z\d]+)*([-_a-zA-Z\d]+)?\.gem/, '')
157
+ end.uniq.sort!
158
+
159
+ Tempfile.create('names.list') do |f|
160
+ f.write CompactIndex.names(gem_name_list).to_s
161
+ f.rewind
162
+ FileUtils.cp(
163
+ f.path,
164
+ File.join(
165
+ @dest_directory,
166
+ "names.#{Digest::MD5.file(f.path).hexdigest}.#{Digest::SHA256.file(f.path).hexdigest}.list"
167
+ ),
168
+ verbose: verbose
169
+ )
170
+ end
165
171
 
166
- if ::Gem::VERSION >= '2.5.0'
167
- build_marshal_gemspecs specs
168
- build_modern_indices specs if @build_modern
169
- compress_indices
172
+ nil
173
+ end
174
+
175
+ ##
176
+ # Cache compact_index endpoint /versions
177
+ #
178
+ # @param [Array] specs Gems list
179
+ # @param [Boolean] partial Is gem list an update or a full index
180
+ # @return nil
181
+ #
182
+ def build_compact_index_versions(specs, partial = false)
183
+ Utils.logger.info('[1/1]: Caching /versions')
184
+
185
+ cg =
186
+ specs
187
+ .sort_by(&:name)
188
+ .group_by(&:name)
189
+ .collect do |name, gem_versions|
190
+ gem_versions =
191
+ gem_versions.sort do |a, b|
192
+ a.version <=> b.version
193
+ end
194
+
195
+ info_file = Dir.glob(File.join(@infos_dir, "#{name}.*.*.list")).last
196
+
197
+ throw "Info file for #{name} not found" unless info_file
198
+
199
+ info_file_checksum = info_file.split('.', -4)[-3]
200
+
201
+ CompactIndex::Gem.new(
202
+ name,
203
+ gem_versions.collect do |y|
204
+ CompactIndex::GemVersion.new(
205
+ y.version.to_s,
206
+ y.platform,
207
+ nil,
208
+ info_file_checksum
209
+ )
210
+ end
211
+ )
212
+ end
213
+
214
+ Tempfile.create('versions.list') do |f|
215
+ previous_versions_file = Dir.glob(File.join(@dest_directory, 'versions.*.*.list')).last
216
+
217
+ if partial && previous_versions_file
218
+ versions_file = CompactIndex::VersionsFile.new(previous_versions_file)
219
+ else
220
+ versions_file = CompactIndex::VersionsFile.new(f.path)
221
+ f.write format('created_at: %s', Time.now.utc.iso8601)
222
+ f.write "\n---\n"
223
+ end
224
+
225
+ f.write CompactIndex.versions(versions_file, cg)
226
+ f.rewind
227
+
228
+ FileUtils.rm_rf(Dir.glob(File.join(@dest_directory, 'versions.*.*.list')))
229
+
230
+ FileUtils.cp(
231
+ f.path,
232
+ File.join(
233
+ @dest_directory,
234
+ "versions.#{Digest::MD5.file(f.path).hexdigest}.#{Digest::SHA256.file(f.path).hexdigest}.list"
235
+ ),
236
+ verbose: verbose
237
+ )
238
+ end
239
+
240
+ nil
241
+ end
242
+
243
+ ##
244
+ # Cache compact_index endpoint /info/[gemname]
245
+ #
246
+ # @param [Array] specs Gems list
247
+ # @param [Boolean] partial Is gem list an update or a full index
248
+ # @return nil
249
+ #
250
+ def build_compact_index_infos(specs, partial = false)
251
+ FileUtils.mkdir_p(@infos_dir, verbose: verbose)
252
+
253
+ if partial
254
+ specs.collect(&:name).uniq do |name|
255
+ FileUtils.rm_rf(Dir.glob(File.join(@infos_dir, "#{name}.*.*.list")))
256
+ end
170
257
  else
171
- build_marshal_gemspecs
172
- build_modern_indicies if @build_modern
173
- compress_indicies
258
+ FileUtils.rm_rf(Dir.glob(File.join(@infos_dir, '*.list')))
174
259
  end
260
+
261
+ grouped_specs = specs.sort_by(&:name).group_by(&:name)
262
+ grouped_specs.each_with_index do |(name, gem_versions), index|
263
+ Utils.logger.info("[#{index + 1}/#{grouped_specs.size}]: Caching /info/#{name}")
264
+
265
+ gem_versions =
266
+ gem_versions.sort do |a, b|
267
+ a.version <=> b.version
268
+ end
269
+
270
+ versions =
271
+ Parallel.map(gem_versions, in_threads: Utils.configuration.update_thread_count) do |spec|
272
+ deps =
273
+ spec
274
+ .dependencies
275
+ .select { |d| d.type == :runtime }
276
+ .sort_by(&:name)
277
+ .collect do |dependency|
278
+ CompactIndex::Dependency.new(
279
+ dependency.name,
280
+ dependency.requirement.to_s
281
+ )
282
+ end
283
+
284
+ CompactIndex::GemVersion.new(
285
+ spec.version,
286
+ spec.platform,
287
+ Digest::SHA256.file(spec.loaded_from).hexdigest,
288
+ nil,
289
+ deps,
290
+ spec.required_ruby_version.to_s,
291
+ spec.required_rubygems_version.to_s
292
+ )
293
+ end
294
+
295
+ Tempfile.create("info_#{name}.list") do |f|
296
+ f.write CompactIndex.info(versions).to_s
297
+ f.rewind
298
+
299
+ FileUtils.cp(
300
+ f.path,
301
+ File.join(
302
+ @infos_dir,
303
+ "#{name}.#{Digest::MD5.file(f.path).hexdigest}.#{Digest::SHA256.file(f.path).hexdigest}.list"
304
+ ),
305
+ verbose: verbose
306
+ )
307
+ end
308
+ end
309
+
310
+ nil
175
311
  end
176
312
 
177
313
  ##
@@ -181,7 +317,9 @@ module Gemirro
181
317
  # @return [Array]
182
318
  #
183
319
  def map_gems_to_specs(gems)
184
- gems.map.with_index do |gemfile, index|
320
+ results = []
321
+
322
+ Parallel.each_with_index(gems, in_threads: Utils.configuration.update_thread_count) do |gemfile, index|
185
323
  Utils.logger.info("[#{index + 1}/#{gems.size}]: Processing #{gemfile.split('/')[-1]}")
186
324
  if File.empty?(gemfile)
187
325
  Utils.logger.warn("Skipping zero-length gem: #{gemfile}")
@@ -190,12 +328,12 @@ module Gemirro
190
328
 
191
329
  begin
192
330
  begin
193
- spec = if ::Gem::Package.respond_to? :open
194
- ::Gem::Package
195
- .open(File.open(gemfile, 'rb'), 'r', &:metadata)
196
- else
197
- ::Gem::Package.new(gemfile).spec
198
- end
331
+ spec =
332
+ if ::Gem::Package.respond_to? :open
333
+ ::Gem::Package.open(File.open(gemfile, 'rb'), 'r', &:metadata)
334
+ else
335
+ ::Gem::Package.new(gemfile).spec
336
+ end
199
337
  rescue NotImplementedError
200
338
  next
201
339
  end
@@ -213,19 +351,14 @@ module Gemirro
213
351
  end
214
352
 
215
353
  version = spec.version.version
216
- unless version =~ /^\d+\.\d+\.\d+.*/
354
+ unless version =~ /^\d+(\.\d+)?(\.\d+)?.*/
217
355
  msg = "Skipping gem #{spec.full_name} - invalid version #{version}"
218
356
  Utils.logger.warn(msg)
219
357
  next
220
358
  end
221
359
 
222
- if ::Gem::VERSION >= '2.5.0'
223
- spec.abbreviate
224
- spec.sanitize
225
- else
226
- abbreviate spec
227
- sanitize spec
228
- end
360
+ spec.abbreviate
361
+ spec.sanitize
229
362
 
230
363
  spec
231
364
  rescue SignalException
@@ -238,20 +371,49 @@ module Gemirro
238
371
  "\t#{e.backtrace.join "\n\t"}"].join("\n")
239
372
  Utils.logger.debug(msg)
240
373
  end
241
- end.compact
374
+
375
+ results[index] = spec
376
+ end
377
+
378
+ # nils can result from insert by index
379
+ results.compact
242
380
  end
243
381
 
382
+ ##
383
+ # Handle `index --update`, detecting changed files and file lists.
384
+ #
385
+ # @return nil
386
+ #
244
387
  def update_index
245
388
  make_temp_directories
246
389
 
247
- specs_mtime = File.stat(@dest_specs_index).mtime
390
+ present_gemfiles = Dir.glob('*.gem', base: File.join(@dest_directory, 'gems'))
391
+ indexed_gemfiles = Dir.glob('*.gemspec.rz', base: @quick_marshal_dir_base).collect { |x| x.gsub(/spec.rz$/, '') }
392
+
393
+ @updated_gems = []
394
+
395
+ # detect files manually added to public/gems
396
+ @updated_gems += (present_gemfiles - indexed_gemfiles).collect { |x| File.join(@dest_directory, 'gems', x) }
397
+ # detect files manually deleted from public/gems
398
+ @updated_gems += (indexed_gemfiles - present_gemfiles).collect { |x| File.join(@dest_directory, 'gems', x) }
399
+
400
+ versions_mtime =
401
+ begin
402
+ File.stat(Dir.glob(File.join(@dest_directory, 'versions*.list')).last).mtime
403
+ rescue StandardError
404
+ Time.at(0)
405
+ end
248
406
  newest_mtime = Time.at(0)
249
407
 
250
- @updated_gems = gem_file_list.select do |gem|
251
- gem_mtime = File.stat(gem).mtime
252
- newest_mtime = gem_mtime if gem_mtime > newest_mtime
253
- gem_mtime > specs_mtime
254
- end
408
+ # files that have been replaced
409
+ @updated_gems +=
410
+ gem_file_list.select do |gem|
411
+ gem_mtime = File.stat(gem).mtime
412
+ newest_mtime = gem_mtime if gem_mtime > newest_mtime
413
+ gem_mtime > versions_mtime
414
+ end
415
+
416
+ @updated_gems.uniq!
255
417
 
256
418
  if @updated_gems.empty?
257
419
  Utils.logger.info('No new gems')
@@ -259,94 +421,40 @@ module Gemirro
259
421
  end
260
422
 
261
423
  specs = map_gems_to_specs(@updated_gems)
262
- prerelease, released = specs.partition { |s| s.version.prerelease? }
263
424
 
264
- ::Gem::Specification.dirs = []
265
- ::Gem::Specification.all = *specs
266
- files = if ::Gem::VERSION >= '2.5.0'
267
- build_marshal_gemspecs specs
268
- else
269
- build_marshal_gemspecs
270
- end
271
-
272
- ::Gem.time('Updated indexes') do
273
- update_specs_index(released, @dest_specs_index, @specs_index)
274
- update_specs_index(released,
275
- @dest_latest_specs_index,
276
- @latest_specs_index)
277
- update_specs_index(prerelease,
278
- @dest_prerelease_specs_index,
279
- @prerelease_specs_index)
280
- end
281
-
282
- if ::Gem::VERSION >= '2.5.0'
283
- compress_indices
284
- else
285
- compress_indicies
286
- end
287
-
288
- Utils.logger.info("Updating production dir #{@dest_directory}") if verbose
289
- files << @specs_index
290
- files << "#{@specs_index}.gz"
291
- files << @latest_specs_index
292
- files << "#{@latest_specs_index}.gz"
293
- files << @prerelease_specs_index
294
- files << "#{@prerelease_specs_index}.gz"
295
-
296
- files.each do |path|
297
- file = path.sub(%r{^#{Regexp.escape @directory}/?}, '')
298
- src_name = File.join(@directory, file)
299
- dst_name = File.join(@dest_directory, file)
300
-
301
- if ["#{@specs_index}.gz",
302
- "#{@latest_specs_index}.gz",
303
- "#{@prerelease_specs_index}.gz"].include?(path)
304
- res = build_zlib_file(file, src_name, dst_name)
305
- next unless res
306
- else
307
- FileUtils.mv(src_name,
308
- dst_name,
309
- verbose: verbose,
310
- force: true)
425
+ # specs only includes latest discovered files.
426
+ # /info/[gemname] can not be rebuilt
427
+ # incrementally, so retrive specs for all versions of these gems.
428
+ gem_name_updates = specs.collect(&:name).uniq
429
+ u2 =
430
+ Dir.glob(File.join(File.join(@dest_directory, 'gems'), '*.gem')).select do |possibility|
431
+ gem_name_updates.any? { |updated| File.basename(possibility) =~ /^#{updated}-\d/ }
311
432
  end
312
433
 
313
- File.utime(newest_mtime, newest_mtime, dst_name)
314
- end
315
- end
316
-
317
- def build_zlib_file(file, src_name, dst_name, from_source = false)
318
- content = Marshal.load(Zlib::GzipReader.open(src_name).read)
319
- create_zlib_file("#{dst_name}.orig", content)
320
-
321
- return false if @only_origin
322
-
323
- if from_source
324
- source_content = download_from_source(file)
325
- source_content = Marshal.load(Zlib::GzipReader
326
- .new(StringIO
327
- .new(source_content)).read)
328
- else
329
- source_content = Marshal.load(Zlib::GzipReader.open(dst_name).read)
330
- end
434
+ Utils.logger.info('Reloading for /info/[gemname]')
435
+ version_specs = map_gems_to_specs(u2)
331
436
 
332
- return false if source_content.nil?
437
+ ::Gem::Specification.dirs = []
438
+ ::Gem::Specification.all = *specs
439
+ build_marshal_gemspecs specs
333
440
 
334
- new_content = source_content.concat(content).uniq
335
- create_zlib_file(dst_name, new_content)
441
+ build_compact_index_infos(version_specs, true)
442
+ build_compact_index_versions(specs, true)
443
+ build_compact_index_names
336
444
  end
337
445
 
338
- def create_zlib_file(dst_name, content)
339
- temp_file = Tempfile.new('gemirro')
340
-
341
- Zlib::GzipWriter.open(temp_file.path) do |io|
342
- io.write(Marshal.dump(content))
446
+ def download_source_versions
447
+ Tempfile.create(File.basename(Gemirro.configuration.versions_file)) do |f|
448
+ f.write(download_from_source('versions'))
449
+ f.close
450
+
451
+ FileUtils.rm(Gemirro.configuration.versions_file, verbose: verbose)
452
+ FileUtils.cp(
453
+ f.path,
454
+ Gemirro.configuration.versions_file,
455
+ verbose: verbose
456
+ )
343
457
  end
344
-
345
- FileUtils.mv(temp_file.path,
346
- dst_name,
347
- verbose: verbose,
348
- force: true)
349
- Utils.cache.flush_key(File.basename(dst_name))
350
458
  end
351
459
 
352
460
  def verbose
@@ -26,6 +26,7 @@ module Gemirro
26
26
  # @param [String] content
27
27
  #
28
28
  def write(content)
29
+ FileUtils.mkdir_p(File.dirname(@path))
29
30
  handle = File.open(@path, 'w')
30
31
 
31
32
  handle.write(content)