r5_geminabox 0.0.1

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.
@@ -0,0 +1,95 @@
1
+
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
+ Geminabox.http_adapter.get_content(remote_url).force_encoding(encoding)
47
+ rescue
48
+ raise GemStoreError.new(500, "Unable to get content from #{remote_url}")
49
+ end
50
+
51
+ def remote_url
52
+ "http://rubygems.org/#{file_name}"
53
+ end
54
+
55
+ def local_content
56
+ File.read(local_path).force_encoding(encoding)
57
+ end
58
+
59
+ private
60
+ def encoding
61
+ "UTF-8"
62
+ end
63
+
64
+ def ensure_destination_exists
65
+ create_local_folder unless local_folder_exists?
66
+ create_proxy_folder unless proxy_folder_exists?
67
+ end
68
+
69
+ def proxy_file_folder
70
+ File.dirname proxy_path
71
+ end
72
+
73
+ def proxy_folder_exists?
74
+ Dir.exists?(proxy_file_folder)
75
+ end
76
+
77
+ def create_proxy_folder
78
+ FileUtils.mkdir_p(proxy_file_folder)
79
+ end
80
+
81
+ def local_file_folder
82
+ File.dirname local_path
83
+ end
84
+
85
+ def local_folder_exists?
86
+ Dir.exists?(local_file_folder)
87
+ end
88
+
89
+ def create_local_folder
90
+ FileUtils.mkdir_p(local_file_folder)
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,81 @@
1
+ require 'sinatra/base'
2
+ require 'net/http'
3
+
4
+ module Geminabox
5
+ module Proxy
6
+ class Hostess < Sinatra::Base
7
+ attr_accessor :file_handler
8
+
9
+ def serve
10
+ if file_handler
11
+ send_file file_handler.proxy_path
12
+ else
13
+ send_file(File.expand_path(File.join(Server.data, *request.path_info)), :type => response['Content-Type'])
14
+ end
15
+ end
16
+
17
+ %w[specs.4.8.gz
18
+ latest_specs.4.8.gz
19
+ prerelease_specs.4.8.gz
20
+ ].each do |index|
21
+ get "/#{index}" do
22
+ splice_file index
23
+ content_type 'application/x-gzip'
24
+ serve
25
+ end
26
+ end
27
+
28
+ %w[quick/Marshal.4.8/*.gemspec.rz
29
+ yaml.Z
30
+ Marshal.4.8.Z
31
+ ].each do |deflated_index|
32
+ get "/#{deflated_index}" do
33
+ copy_file request.path_info[1..-1]
34
+ content_type('application/x-deflate')
35
+ serve
36
+ end
37
+ end
38
+
39
+ %w[yaml
40
+ Marshal.4.8
41
+ specs.4.8
42
+ latest_specs.4.8
43
+ prerelease_specs.4.8
44
+ ].each do |old_index|
45
+ get "/#{old_index}" do
46
+ splice_file old_index
47
+ serve
48
+ end
49
+ end
50
+
51
+ get "/gems/*.gem" do
52
+ get_from_rubygems_if_not_local
53
+ serve
54
+ end
55
+
56
+ private
57
+ def get_from_rubygems_if_not_local
58
+
59
+ file = File.expand_path(File.join(Server.data, *request.path_info))
60
+
61
+ unless File.exists?(file)
62
+ ruby_gems_url = 'http://production.cf.rubygems.org'
63
+ path = File.join(ruby_gems_url, *request.path_info)
64
+ content = Geminabox.http_adapter.get_content(path)
65
+ GemStore.create(IncomingGem.new(StringIO.new(content)))
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,29 @@
1
+ require 'json'
2
+
3
+ module Geminabox
4
+ module RubygemsDependency
5
+
6
+ class << self
7
+
8
+ def for(*gems)
9
+
10
+ url = [
11
+ rubygems_uri,
12
+ '?gems=',
13
+ gems.map(&:to_s).join(',')
14
+ ].join
15
+ body = Geminabox.http_adapter.get_content(url)
16
+ JSON.parse(body)
17
+ rescue Exception => e
18
+ return [] if Geminabox.allow_remote_failure
19
+ raise e
20
+ end
21
+
22
+ def rubygems_uri
23
+ "https://bundler.rubygems.org/api/v1/dependencies.json"
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,290 @@
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
+ query_gems.any? ? Marshal.dump(gem_list) : 200
92
+ end
93
+
94
+ get '/api/v1/dependencies.json' do
95
+ query_gems.any? ? 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
+ sleep 10
156
+ redirect url("/")
157
+ end
158
+
159
+ def api_request?
160
+ request.accept.first.to_s != "text/html"
161
+ end
162
+
163
+ def error_response(code, message)
164
+ halt [code, message] if api_request?
165
+ html = <<HTML
166
+ <html>
167
+ <head><title>Error - #{code}</title></head>
168
+ <body>
169
+ <h1>Error - #{code}</h1>
170
+ <p>#{message}</p>
171
+ </body>
172
+ </html>
173
+ HTML
174
+ halt [code, html]
175
+ end
176
+
177
+ def file_path
178
+ File.expand_path(File.join(settings.data, *request.path_info))
179
+ end
180
+
181
+ def dependency_cache
182
+ self.class.dependency_cache
183
+ end
184
+
185
+ def all_gems
186
+ all_gems_with_duplicates.inject(:|)
187
+ end
188
+
189
+ def all_gems_with_duplicates
190
+ specs_files_paths.map do |specs_file_path|
191
+ if File.exists?(specs_file_path)
192
+ Marshal.load(Gem.gunzip(Gem.read_binary(specs_file_path)))
193
+ else
194
+ []
195
+ end
196
+ end
197
+ end
198
+
199
+ def specs_file_types
200
+ [:specs, :prerelease_specs]
201
+ end
202
+
203
+ def specs_files_paths
204
+ specs_file_types.map do |specs_file_type|
205
+ File.join(settings.data, spec_file_name(specs_file_type))
206
+ end
207
+ end
208
+
209
+ def spec_file_name(specs_file_type)
210
+ [specs_file_type, Gem.marshal_version, 'gz'].join('.')
211
+ end
212
+
213
+ def load_gems
214
+ @loaded_gems ||= Geminabox::GemVersionCollection.new(all_gems)
215
+ end
216
+
217
+ def index_gems(gems)
218
+ Set.new(gems.map{|gem| gem.name[0..0].downcase})
219
+ end
220
+
221
+ def gem_list
222
+ settings.rubygems_proxy ? combined_gem_list : local_gem_list
223
+ end
224
+
225
+ def query_gems
226
+ params[:gems].to_s.split(',')
227
+ end
228
+
229
+ def local_gem_list
230
+ query_gems.map{|query_gem| gem_dependencies(query_gem) }.flatten(1)
231
+ end
232
+
233
+ def remote_gem_list
234
+ RubygemsDependency.for(*query_gems)
235
+ end
236
+
237
+ def combined_gem_list
238
+ GemListMerge.from(local_gem_list, remote_gem_list)
239
+ end
240
+
241
+ helpers do
242
+ def spec_for(gem_name, version, platform = default_platform)
243
+ filename = [gem_name, version]
244
+ filename.push(platform) if platform != default_platform
245
+ spec_file = File.join(settings.data, "quick", "Marshal.#{Gem.marshal_version}", "#{filename.join("-")}.gemspec.rz")
246
+ File::open(spec_file, 'r') do |unzipped_spec_file|
247
+ unzipped_spec_file.binmode
248
+ Marshal.load(Gem.inflate(unzipped_spec_file.read))
249
+ end if File.exists? spec_file
250
+ end
251
+
252
+ def default_platform
253
+ 'ruby'
254
+ end
255
+
256
+ # Return a list of versions of gem 'gem_name' with the dependencies of each version.
257
+ def gem_dependencies(gem_name)
258
+ dependency_cache.marshal_cache(gem_name) do
259
+ load_gems.
260
+ select { |gem| gem_name == gem.name }.
261
+ map { |gem| [gem, spec_for(gem.name, gem.number, gem.platform)] }.
262
+ reject { |(_, spec)| spec.nil? }.
263
+ map do |(gem, spec)|
264
+ {
265
+ :name => gem.name,
266
+ :number => gem.number.version,
267
+ :platform => gem.platform,
268
+ :dependencies => runtime_dependencies(spec)
269
+ }
270
+ end
271
+ end
272
+ end
273
+
274
+
275
+
276
+ def runtime_dependencies(spec)
277
+ spec.
278
+ dependencies.
279
+ select { |dep| dep.type == :runtime }.
280
+ map { |dep| name_and_requirements_for(dep) }
281
+ end
282
+
283
+ def name_and_requirements_for(dep)
284
+ name = dep.name.kind_of?(Array) ? dep.name.first : dep.name
285
+ [name, dep.requirement.to_s]
286
+ end
287
+ end
288
+ end
289
+
290
+ end