geminabox 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of geminabox might be problematic. Click here for more details.
- data/MIT-LICENSE +20 -0
- data/README.markdown +14 -2
- data/lib/geminabox.rb +47 -213
- data/lib/geminabox/disk_cache.rb +69 -68
- data/lib/geminabox/gem_list_merge.rb +51 -0
- data/lib/geminabox/gem_store.rb +77 -75
- data/lib/geminabox/gem_store_error.rb +8 -7
- data/lib/geminabox/gem_version.rb +31 -27
- data/lib/geminabox/gem_version_collection.rb +55 -53
- data/lib/geminabox/hostess.rb +46 -0
- data/lib/geminabox/incoming_gem.rb +52 -47
- data/lib/geminabox/proxy.rb +13 -0
- data/lib/geminabox/proxy/copier.rb +29 -0
- data/lib/geminabox/proxy/file_handler.rb +93 -0
- data/lib/geminabox/proxy/hostess.rb +81 -0
- data/lib/geminabox/proxy/splicer.rb +67 -0
- data/lib/geminabox/rubygems_dependency.rb +27 -0
- data/lib/geminabox/server.rb +279 -0
- data/lib/geminabox/version.rb +3 -1
- data/lib/rubygems/commands/inabox_command.rb +1 -1
- metadata +25 -77
- data/lib/hostess.rb +0 -42
@@ -1,65 +1,70 @@
|
|
1
|
-
|
2
|
-
def initialize(gem_data, root_path = Geminabox.settings.data)
|
3
|
-
unless gem_data.respond_to? :read
|
4
|
-
raise ArgumentError, "Expected an instance of IO"
|
5
|
-
end
|
1
|
+
module Geminabox
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
3
|
+
class IncomingGem
|
4
|
+
def initialize(gem_data, root_path = Geminabox.settings.data)
|
5
|
+
unless gem_data.respond_to? :read
|
6
|
+
raise ArgumentError, "Expected an instance of IO"
|
7
|
+
end
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
digest = Digest::SHA1.new
|
10
|
+
if RbConfig::CONFIG["MAJOR"].to_i <= 1 and RbConfig::CONFIG["MINOR"].to_i <= 8
|
11
|
+
@tempfile = Tempfile.new("gem")
|
12
|
+
else
|
13
|
+
@tempfile = Tempfile.new("gem", :encoding => 'binary')
|
14
|
+
end
|
18
15
|
|
19
|
-
|
20
|
-
|
16
|
+
while data = gem_data.read(1024**2)
|
17
|
+
@tempfile.write data
|
18
|
+
digest << data
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
@tempfile.close
|
22
|
+
@sha1 = digest.hexdigest
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
end
|
24
|
+
@root_path = root_path
|
25
|
+
end
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
false
|
33
|
-
end
|
27
|
+
def gem_data
|
28
|
+
File.open(@tempfile.path, "rb")
|
29
|
+
end
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
def valid?
|
32
|
+
spec && spec.name && spec.version
|
33
|
+
rescue Gem::Package::Error
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def spec
|
38
|
+
@spec ||= extract_spec
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
def extract_spec
|
42
|
+
if Gem::Package.respond_to? :open
|
43
|
+
Gem::Package.open(gem_data, "r", nil) do |pkg|
|
44
|
+
return pkg.metadata
|
45
|
+
end
|
46
|
+
else
|
47
|
+
Gem::Package.new(@tempfile.path).spec
|
43
48
|
end
|
44
|
-
else
|
45
|
-
Gem::Package.new(@tempfile.path).spec
|
46
49
|
end
|
47
|
-
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
+
def name
|
52
|
+
@name ||= get_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_name
|
51
56
|
filename = %W[#{spec.name} #{spec.version}]
|
52
57
|
filename.push(spec.platform) if spec.platform && spec.platform != "ruby"
|
53
|
-
|
58
|
+
filename.join("-") + ".gem"
|
54
59
|
end
|
55
|
-
@name
|
56
|
-
end
|
57
60
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
+
def dest_filename
|
62
|
+
File.join(@root_path, "gems", name)
|
63
|
+
end
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
def hexdigest
|
66
|
+
@sha1
|
67
|
+
end
|
64
68
|
end
|
69
|
+
|
65
70
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Geminabox
|
3
|
+
module Proxy
|
4
|
+
def self.proxy_path(file)
|
5
|
+
File.join File.dirname(__FILE__), 'proxy', file
|
6
|
+
end
|
7
|
+
|
8
|
+
autoload :Hostess, proxy_path('hostess')
|
9
|
+
autoload :FileHandler, proxy_path('file_handler')
|
10
|
+
autoload :Splicer, proxy_path('splicer')
|
11
|
+
autoload :Copier, proxy_path('copier')
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Geminabox
|
4
|
+
module Proxy
|
5
|
+
class Copier < FileHandler
|
6
|
+
|
7
|
+
def self.copy(file_name)
|
8
|
+
copier = new(file_name)
|
9
|
+
copier.get_file
|
10
|
+
copier
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_file
|
14
|
+
return true if proxy_file_exists?
|
15
|
+
return copy_local if local_file_exists?
|
16
|
+
get_remote
|
17
|
+
end
|
18
|
+
|
19
|
+
def copy_local
|
20
|
+
FileUtils.cp local_path, proxy_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_remote
|
24
|
+
File.open(proxy_path, 'w'){|f| f.write(remote_content)}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
module Geminabox
|
3
|
+
module Proxy
|
4
|
+
class FileHandler
|
5
|
+
|
6
|
+
attr_reader :file_name
|
7
|
+
|
8
|
+
def initialize(file_name)
|
9
|
+
@file_name = file_name
|
10
|
+
ensure_destination_exists
|
11
|
+
end
|
12
|
+
|
13
|
+
def local_path
|
14
|
+
File.expand_path(file_name, root_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def root_path
|
18
|
+
Geminabox.data
|
19
|
+
end
|
20
|
+
|
21
|
+
def local_file_exists?
|
22
|
+
file_exists? local_path
|
23
|
+
end
|
24
|
+
|
25
|
+
def proxy_file_exists?
|
26
|
+
file_exists? proxy_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def proxy_path
|
30
|
+
File.expand_path(file_name, proxy_folder_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def file_exists?(path)
|
34
|
+
File.exists? path
|
35
|
+
end
|
36
|
+
|
37
|
+
def proxy_folder_path
|
38
|
+
File.join(root_path, proxy_folder_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def proxy_folder_name
|
42
|
+
'proxy'
|
43
|
+
end
|
44
|
+
|
45
|
+
def remote_content
|
46
|
+
HTTPClient.get_content(remote_url).force_encoding(encoding)
|
47
|
+
end
|
48
|
+
|
49
|
+
def remote_url
|
50
|
+
"http://rubygems.org/#{file_name}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def local_content
|
54
|
+
File.read(local_path).force_encoding(encoding)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def encoding
|
59
|
+
"UTF-8"
|
60
|
+
end
|
61
|
+
|
62
|
+
def ensure_destination_exists
|
63
|
+
create_local_folder unless local_folder_exists?
|
64
|
+
create_proxy_folder unless proxy_folder_exists?
|
65
|
+
end
|
66
|
+
|
67
|
+
def proxy_file_folder
|
68
|
+
File.dirname proxy_path
|
69
|
+
end
|
70
|
+
|
71
|
+
def proxy_folder_exists?
|
72
|
+
Dir.exists?(proxy_file_folder)
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_proxy_folder
|
76
|
+
FileUtils.mkdir_p(proxy_file_folder)
|
77
|
+
end
|
78
|
+
|
79
|
+
def local_file_folder
|
80
|
+
File.dirname local_path
|
81
|
+
end
|
82
|
+
|
83
|
+
def local_folder_exists?
|
84
|
+
Dir.exists?(local_file_folder)
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_local_folder
|
88
|
+
FileUtils.mkdir_p(local_file_folder)
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Geminabox
|
4
|
+
module Proxy
|
5
|
+
class Hostess < Sinatra::Base
|
6
|
+
attr_accessor :file_handler
|
7
|
+
|
8
|
+
def serve
|
9
|
+
if file_handler
|
10
|
+
send_file file_handler.proxy_path
|
11
|
+
else
|
12
|
+
send_file(File.expand_path(File.join(Server.data, *request.path_info)), :type => response['Content-Type'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[specs.4.8.gz
|
17
|
+
latest_specs.4.8.gz
|
18
|
+
prerelease_specs.4.8.gz
|
19
|
+
].each do |index|
|
20
|
+
get "/#{index}" do
|
21
|
+
splice_file index
|
22
|
+
content_type 'application/x-gzip'
|
23
|
+
serve
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
%w[quick/Marshal.4.8/*.gemspec.rz
|
28
|
+
yaml.Z
|
29
|
+
Marshal.4.8.Z
|
30
|
+
].each do |deflated_index|
|
31
|
+
get "/#{deflated_index}" do
|
32
|
+
copy_file request.path_info[1..-1]
|
33
|
+
content_type('application/x-deflate')
|
34
|
+
serve
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
%w[yaml
|
39
|
+
Marshal.4.8
|
40
|
+
specs.4.8
|
41
|
+
latest_specs.4.8
|
42
|
+
prerelease_specs.4.8
|
43
|
+
].each do |old_index|
|
44
|
+
get "/#{old_index}" do
|
45
|
+
splice_file old_index
|
46
|
+
serve
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
get "/gems/*.gem" do
|
51
|
+
get_from_rubygems_if_not_local
|
52
|
+
serve
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def get_from_rubygems_if_not_local
|
57
|
+
|
58
|
+
file = File.expand_path(File.join(Server.data, *request.path_info))
|
59
|
+
|
60
|
+
unless File.exists?(file)
|
61
|
+
Net::HTTP.start("production.cf.rubygems.org") do |http|
|
62
|
+
path = File.join(*request.path_info)
|
63
|
+
response = http.get(path)
|
64
|
+
GemStore.create(IncomingGem.new(StringIO.new(response.body)))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def splice_file(file_name)
|
71
|
+
self.file_handler = Splicer.make(file_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def copy_file(file_name)
|
75
|
+
self.file_handler = Copier.copy(file_name)
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
module Geminabox
|
3
|
+
module Proxy
|
4
|
+
class Splicer < FileHandler
|
5
|
+
|
6
|
+
def self.make(file_name)
|
7
|
+
splicer = new(file_name)
|
8
|
+
splicer.create
|
9
|
+
splicer
|
10
|
+
end
|
11
|
+
|
12
|
+
def create
|
13
|
+
File.open(splice_path, 'w'){|f| f.write(new_content)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_content
|
17
|
+
if local_file_exists?
|
18
|
+
merge_content
|
19
|
+
else
|
20
|
+
remote_content
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def splice_path
|
25
|
+
proxy_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def splice_folder_path
|
29
|
+
proxy_folder_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def splice_file_exists?
|
33
|
+
file_exists? splice_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge_content
|
37
|
+
if gzip?
|
38
|
+
merge_gziped_content
|
39
|
+
else
|
40
|
+
merge_text_content
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def gzip?
|
45
|
+
/\.gz$/ =~ file_name
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def merge_gziped_content
|
50
|
+
package(unpackage(local_content) | unpackage(remote_content))
|
51
|
+
end
|
52
|
+
|
53
|
+
def unpackage(content)
|
54
|
+
Marshal.load(Gem.gunzip(content))
|
55
|
+
end
|
56
|
+
|
57
|
+
def package(content)
|
58
|
+
Gem.gzip(Marshal.dump(content))
|
59
|
+
end
|
60
|
+
|
61
|
+
def merge_text_content
|
62
|
+
local_content.to_s + remote_content.to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Geminabox
|
5
|
+
module RubygemsDependency
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def for(*gems)
|
10
|
+
|
11
|
+
url = [
|
12
|
+
rubygems_uri,
|
13
|
+
'?gems=',
|
14
|
+
gems.map(&:to_s).join(',')
|
15
|
+
].join
|
16
|
+
body = HTTPClient.get_content(url)
|
17
|
+
JSON.parse(body)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rubygems_uri
|
21
|
+
"https://bundler.rubygems.org/api/v1/dependencies.json"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,279 @@
|
|
1
|
+
module Geminabox
|
2
|
+
|
3
|
+
class Server < Sinatra::Base
|
4
|
+
enable :static, :methodoverride
|
5
|
+
|
6
|
+
def self.delegate_to_geminabox(*delegate_methods)
|
7
|
+
delegate_methods.each{|m| set m, Geminabox.send(m)}
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate_to_geminabox(
|
11
|
+
:public_folder,
|
12
|
+
:data,
|
13
|
+
:build_legacy,
|
14
|
+
:incremental_updates,
|
15
|
+
:views,
|
16
|
+
:allow_replace,
|
17
|
+
:gem_permissions,
|
18
|
+
:allow_delete,
|
19
|
+
:rubygems_proxy
|
20
|
+
)
|
21
|
+
|
22
|
+
if Server.rubygems_proxy
|
23
|
+
use Proxy::Hostess
|
24
|
+
else
|
25
|
+
use Hostess
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def disallow_replace?
|
30
|
+
! allow_replace
|
31
|
+
end
|
32
|
+
|
33
|
+
def allow_delete?
|
34
|
+
allow_delete
|
35
|
+
end
|
36
|
+
|
37
|
+
def fixup_bundler_rubygems!
|
38
|
+
return if @post_reset_hook_applied
|
39
|
+
Gem.post_reset{ Gem::Specification.all = nil } if defined? Bundler and Gem.respond_to? :post_reset
|
40
|
+
@post_reset_hook_applied = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def reindex(force_rebuild = false)
|
44
|
+
fixup_bundler_rubygems!
|
45
|
+
force_rebuild = true unless incremental_updates
|
46
|
+
if force_rebuild
|
47
|
+
indexer.generate_index
|
48
|
+
dependency_cache.flush
|
49
|
+
else
|
50
|
+
begin
|
51
|
+
require 'geminabox/indexer'
|
52
|
+
updated_gemspecs = Geminabox::Indexer.updated_gemspecs(indexer)
|
53
|
+
Geminabox::Indexer.patch_rubygems_update_index_pre_1_8_25(indexer)
|
54
|
+
indexer.update_index
|
55
|
+
updated_gemspecs.each { |gem| dependency_cache.flush_key(gem.name) }
|
56
|
+
rescue => e
|
57
|
+
puts "#{e.class}:#{e.message}"
|
58
|
+
puts e.backtrace.join("\n")
|
59
|
+
reindex(:force_rebuild)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def indexer
|
65
|
+
Gem::Indexer.new(data, :build_legacy => build_legacy)
|
66
|
+
end
|
67
|
+
|
68
|
+
def dependency_cache
|
69
|
+
@dependency_cache ||= Geminabox::DiskCache.new(File.join(data, "_cache"))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
before do
|
76
|
+
headers 'X-Powered-By' => "geminabox #{Geminabox::VERSION}"
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/' do
|
80
|
+
@gems = load_gems
|
81
|
+
@index_gems = index_gems(@gems)
|
82
|
+
erb :index
|
83
|
+
end
|
84
|
+
|
85
|
+
get '/atom.xml' do
|
86
|
+
@gems = load_gems
|
87
|
+
erb :atom, :layout => false
|
88
|
+
end
|
89
|
+
|
90
|
+
get '/api/v1/dependencies' do
|
91
|
+
Marshal.dump(gem_list)
|
92
|
+
end
|
93
|
+
|
94
|
+
get '/api/v1/dependencies.json' do
|
95
|
+
gem_list.to_json
|
96
|
+
end
|
97
|
+
|
98
|
+
get '/upload' do
|
99
|
+
erb :upload
|
100
|
+
end
|
101
|
+
|
102
|
+
get '/reindex' do
|
103
|
+
self.class.reindex(:force_rebuild)
|
104
|
+
redirect url("/")
|
105
|
+
end
|
106
|
+
|
107
|
+
get '/gems/:gemname' do
|
108
|
+
gems = Hash[load_gems.by_name]
|
109
|
+
@gem = gems[params[:gemname]]
|
110
|
+
halt 404 unless @gem
|
111
|
+
erb :gem
|
112
|
+
end
|
113
|
+
|
114
|
+
delete '/gems/*.gem' do
|
115
|
+
unless self.class.allow_delete?
|
116
|
+
error_response(403, 'Gem deletion is disabled - see https://github.com/cwninja/geminabox/issues/115')
|
117
|
+
end
|
118
|
+
File.delete file_path if File.exists? file_path
|
119
|
+
self.class.reindex(:force_rebuild)
|
120
|
+
redirect url("/")
|
121
|
+
end
|
122
|
+
|
123
|
+
post '/upload' do
|
124
|
+
unless params[:file] && params[:file][:filename] && (tmpfile = params[:file][:tempfile])
|
125
|
+
@error = "No file selected"
|
126
|
+
halt [400, erb(:upload)]
|
127
|
+
end
|
128
|
+
handle_incoming_gem(Geminabox::IncomingGem.new(tmpfile))
|
129
|
+
end
|
130
|
+
|
131
|
+
post '/api/v1/gems' do
|
132
|
+
begin
|
133
|
+
handle_incoming_gem(Geminabox::IncomingGem.new(request.body))
|
134
|
+
rescue Object => o
|
135
|
+
File.open "/tmp/debug.txt", "a" do |io|
|
136
|
+
io.puts o, o.backtrace
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def handle_incoming_gem(gem)
|
144
|
+
begin
|
145
|
+
GemStore.create(gem, params[:overwrite])
|
146
|
+
rescue GemStoreError => error
|
147
|
+
error_response error.code, error.reason
|
148
|
+
end
|
149
|
+
|
150
|
+
if api_request?
|
151
|
+
"Gem #{gem.name} received and indexed."
|
152
|
+
else
|
153
|
+
redirect url("/")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def api_request?
|
158
|
+
request.accept.first != "text/html"
|
159
|
+
end
|
160
|
+
|
161
|
+
def error_response(code, message)
|
162
|
+
halt [code, message] if api_request?
|
163
|
+
html = <<HTML
|
164
|
+
<html>
|
165
|
+
<head><title>Error - #{code}</title></head>
|
166
|
+
<body>
|
167
|
+
<h1>Error - #{code}</h1>
|
168
|
+
<p>#{message}</p>
|
169
|
+
</body>
|
170
|
+
</html>
|
171
|
+
HTML
|
172
|
+
halt [code, html]
|
173
|
+
end
|
174
|
+
|
175
|
+
def file_path
|
176
|
+
File.expand_path(File.join(settings.data, *request.path_info))
|
177
|
+
end
|
178
|
+
|
179
|
+
def dependency_cache
|
180
|
+
self.class.dependency_cache
|
181
|
+
end
|
182
|
+
|
183
|
+
def all_gems
|
184
|
+
all_gems_with_duplicates.inject(:|)
|
185
|
+
end
|
186
|
+
|
187
|
+
def all_gems_with_duplicates
|
188
|
+
specs_files_paths.map do |specs_file_path|
|
189
|
+
if File.exists?(specs_file_path)
|
190
|
+
Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
|
191
|
+
else
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def specs_file_types
|
198
|
+
[:specs, :prerelease_specs]
|
199
|
+
end
|
200
|
+
|
201
|
+
def specs_files_paths
|
202
|
+
specs_file_types.map do |specs_file_type|
|
203
|
+
File.join(settings.data, spec_file_name(specs_file_type))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def spec_file_name(specs_file_type)
|
208
|
+
[specs_file_type, Gem.marshal_version, 'gz'].join('.')
|
209
|
+
end
|
210
|
+
|
211
|
+
def load_gems
|
212
|
+
@loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
|
213
|
+
end
|
214
|
+
|
215
|
+
def index_gems(gems)
|
216
|
+
Set.new(gems.map{|gem| gem.name[0..0].downcase})
|
217
|
+
end
|
218
|
+
|
219
|
+
def gem_list
|
220
|
+
settings.rubygems_proxy ? combined_gem_list : local_gem_list
|
221
|
+
end
|
222
|
+
|
223
|
+
def query_gems
|
224
|
+
params[:gems].to_s.split(',')
|
225
|
+
end
|
226
|
+
|
227
|
+
def local_gem_list
|
228
|
+
query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
|
229
|
+
end
|
230
|
+
|
231
|
+
def remote_gem_list
|
232
|
+
RubygemsDependency.for(*query_gems)
|
233
|
+
end
|
234
|
+
|
235
|
+
def combined_gem_list
|
236
|
+
GemListMerge.from(local_gem_list, remote_gem_list)
|
237
|
+
end
|
238
|
+
|
239
|
+
helpers do
|
240
|
+
def spec_for(gem_name, version)
|
241
|
+
spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{gem_name}-#{version}.gemspec.rz")
|
242
|
+
Marshal.load(Gem.inflate(File.read(spec_file))) if File.exists? spec_file
|
243
|
+
end
|
244
|
+
|
245
|
+
# Return a list of versions of gem 'gem_name' with the dependencies of each version.
|
246
|
+
def gem_dependencies(gem_name)
|
247
|
+
dependency_cache.marshal_cache(gem_name) do
|
248
|
+
load_gems.
|
249
|
+
select { |gem| gem_name == gem.name }.
|
250
|
+
map { |gem| [gem, spec_for(gem.name, gem.number)] }.
|
251
|
+
reject { |(_, spec)| spec.nil? }.
|
252
|
+
map do |(gem, spec)|
|
253
|
+
{
|
254
|
+
:name => gem.name,
|
255
|
+
:number => gem.number.version,
|
256
|
+
:platform => gem.platform,
|
257
|
+
:dependencies => runtime_dependencies(spec)
|
258
|
+
}
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
|
265
|
+
def runtime_dependencies(spec)
|
266
|
+
spec.
|
267
|
+
dependencies.
|
268
|
+
select { |dep| dep.type == :runtime }.
|
269
|
+
map { |dep| name_and_requirements_for(dep) }
|
270
|
+
end
|
271
|
+
|
272
|
+
def name_and_requirements_for(dep)
|
273
|
+
name = dep.name.kind_of?(Array) ? dep.name.first : dep.name
|
274
|
+
[name, dep.requirement.to_s]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|