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
@@ -0,0 +1,51 @@
|
|
1
|
+
module Geminabox
|
2
|
+
class GemListMerge
|
3
|
+
attr_accessor :list
|
4
|
+
|
5
|
+
def self.from(*lists)
|
6
|
+
lists.map{|list| new(list)}.inject(:merge)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(list)
|
10
|
+
@list = list
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge(other)
|
14
|
+
combine_hashes(other).values.flatten.sort do |x, y|
|
15
|
+
x.values[ignore_dependencies] <=> y.values[ignore_dependencies]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
list.each do |item|
|
21
|
+
ensure_symbols_as_keys(item)
|
22
|
+
name = item[:name].to_sym
|
23
|
+
collection[name] ||= []
|
24
|
+
collection[name] << item unless collection[name].include?(item)
|
25
|
+
end
|
26
|
+
collection
|
27
|
+
end
|
28
|
+
|
29
|
+
def collection
|
30
|
+
@collection ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def combine_hashes(other)
|
34
|
+
hash.merge(other.hash) do |key, value, other_value|
|
35
|
+
(value + other_value).uniq{|v| v.values[ignore_dependencies]}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def ignore_dependencies
|
40
|
+
0..-2
|
41
|
+
end
|
42
|
+
|
43
|
+
def ensure_symbols_as_keys(item)
|
44
|
+
item.keys.each do |key|
|
45
|
+
next if key.kind_of? Symbol
|
46
|
+
item[key.to_sym] = item.delete(key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/geminabox/gem_store.rb
CHANGED
@@ -1,97 +1,99 @@
|
|
1
|
+
module Geminabox
|
1
2
|
|
3
|
+
class GemStore
|
4
|
+
attr_accessor :gem, :overwrite
|
2
5
|
|
3
|
-
|
4
|
-
|
6
|
+
def self.create(gem, overwrite = false)
|
7
|
+
gem_store = new(gem, overwrite)
|
8
|
+
gem_store.save
|
9
|
+
end
|
5
10
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(gem, overwrite = false)
|
12
|
-
@gem = gem
|
13
|
-
@overwrite = overwrite && overwrite == 'true'
|
14
|
-
end
|
11
|
+
def initialize(gem, overwrite = false)
|
12
|
+
@gem = gem
|
13
|
+
@overwrite = overwrite && overwrite == 'true'
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
def save
|
17
|
+
ensure_gem_valid
|
18
|
+
prepare_data_folders
|
19
|
+
check_replacement_status
|
20
|
+
write_and_index
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
23
|
+
def prepare_data_folders
|
24
|
+
ensure_existing_data_folder_compatible
|
25
|
+
begin
|
26
|
+
FileUtils.mkdir_p(File.join(Geminabox.data, "gems"))
|
27
|
+
rescue
|
28
|
+
raise GemStoreError.new(
|
29
|
+
500,
|
30
|
+
"Could not create #{File.expand_path(Geminabox.data)}."
|
31
|
+
)
|
32
|
+
end
|
32
33
|
end
|
33
|
-
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def check_replacement_status
|
36
|
+
if !overwrite and Geminabox::Server.disallow_replace? and File.exist?(gem.dest_filename)
|
37
|
+
if existing_file_digest != gem.hexdigest
|
38
|
+
raise GemStoreError.new(409, "Updating an existing gem is not permitted.\nYou should either delete the existing version, or change your version number.")
|
39
|
+
else
|
40
|
+
raise GemStoreError.new(200, "Ignoring upload, you uploaded the same thing previously.\nPlease use -o to ovewrite.")
|
41
|
+
end
|
41
42
|
end
|
42
43
|
end
|
43
|
-
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
def ensure_gem_valid
|
46
|
+
raise GemStoreError.new(400, "Cannot process gem") unless gem.valid?
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
private
|
50
|
+
def ensure_existing_data_folder_compatible
|
51
|
+
if File.exists? Geminabox.data
|
52
|
+
ensure_data_folder_is_directory
|
53
|
+
ensure_data_folder_is_writable
|
54
|
+
end
|
54
55
|
end
|
55
|
-
end
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
def ensure_data_folder_is_directory
|
58
|
+
raise GemStoreError.new(
|
59
|
+
500,
|
60
|
+
"Please ensure #{File.expand_path(Geminabox.data)} is a directory."
|
61
|
+
) unless File.directory? Geminabox.data
|
62
|
+
end
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
def ensure_data_folder_is_writable
|
65
|
+
raise GemStoreError.new(
|
66
|
+
500,
|
67
|
+
"Please ensure #{File.expand_path(Geminabox.data)} is writable by the geminabox web server."
|
68
|
+
) unless File.writable? Geminabox.data
|
69
|
+
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def existing_file_digest
|
72
|
+
Digest::SHA1.file(gem.dest_filename).hexdigest
|
73
|
+
end
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
75
|
+
def write_and_index
|
76
|
+
tmpfile = gem.gem_data
|
77
|
+
atomic_write(gem.dest_filename) do |f|
|
78
|
+
while blk = tmpfile.read(65536)
|
79
|
+
f << blk
|
80
|
+
end
|
80
81
|
end
|
82
|
+
Geminabox::Server.reindex
|
83
|
+
end
|
84
|
+
|
85
|
+
# based on http://as.rubyonrails.org/classes/File.html
|
86
|
+
def atomic_write(file_name)
|
87
|
+
temp_dir = File.join(Geminabox.data, "_temp")
|
88
|
+
FileUtils.mkdir_p(temp_dir)
|
89
|
+
temp_file = Tempfile.new("." + File.basename(file_name), temp_dir)
|
90
|
+
temp_file.binmode
|
91
|
+
yield temp_file
|
92
|
+
temp_file.close
|
93
|
+
File.rename(temp_file.path, file_name)
|
94
|
+
File.chmod(Geminabox::Server.gem_permissions, file_name)
|
81
95
|
end
|
82
|
-
Geminabox.reindex
|
83
|
-
end
|
84
96
|
|
85
|
-
# based on http://as.rubyonrails.org/classes/File.html
|
86
|
-
def atomic_write(file_name)
|
87
|
-
temp_dir = File.join(Geminabox.data, "_temp")
|
88
|
-
FileUtils.mkdir_p(temp_dir)
|
89
|
-
temp_file = Tempfile.new("." + File.basename(file_name), temp_dir)
|
90
|
-
temp_file.binmode
|
91
|
-
yield temp_file
|
92
|
-
temp_file.close
|
93
|
-
File.rename(temp_file.path, file_name)
|
94
|
-
File.chmod(Geminabox.gem_permissions, file_name)
|
95
97
|
end
|
96
98
|
|
97
99
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'nesty'
|
2
|
+
module Geminabox
|
3
|
+
class GemStoreError < StandardError
|
4
|
+
attr_reader :code, :reason
|
2
5
|
|
3
|
-
|
4
|
-
attr_reader :code, :reason
|
6
|
+
include Nesty::NestedError
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@reason = reason
|
8
|
+
def initialize(code, reason)
|
9
|
+
@code = code.to_s
|
10
|
+
@reason = reason
|
11
|
+
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,36 +1,40 @@
|
|
1
|
-
|
2
|
-
attr_accessor :name, :number, :platform
|
1
|
+
module Geminabox
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
@number = number
|
7
|
-
@platform = platform
|
8
|
-
end
|
3
|
+
class GemVersion
|
4
|
+
attr_accessor :name, :number, :platform
|
9
5
|
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
def initialize(name, number, platform)
|
7
|
+
@name = name
|
8
|
+
@number = number
|
9
|
+
@platform = platform
|
10
|
+
end
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
def ruby?
|
13
|
+
!!(platform =~ /ruby/i)
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
sort = (other.ruby? && !ruby?) ? 1 : -1 if sort.zero? && ruby? != other.ruby?
|
22
|
-
sort = other.platform <=> platform if sort.zero?
|
16
|
+
def version
|
17
|
+
Gem::Version.create(number)
|
18
|
+
end
|
23
19
|
|
24
|
-
|
25
|
-
|
20
|
+
def <=>(other)
|
21
|
+
sort = other.name <=> name
|
22
|
+
sort = version <=> other.version if sort.zero?
|
23
|
+
sort = (other.ruby? && !ruby?) ? 1 : -1 if sort.zero? && ruby? != other.ruby?
|
24
|
+
sort = other.platform <=> platform if sort.zero?
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
sort
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
return false unless other.class == self.class
|
31
|
+
[name, number, platform] == [other.name, other.number, other.platform]
|
32
|
+
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def gemfile_name
|
35
|
+
included_platform = ruby? ? nil : platform
|
36
|
+
[name, number, included_platform].compact.join('-')
|
37
|
+
end
|
35
38
|
end
|
39
|
+
|
36
40
|
end
|
@@ -1,63 +1,65 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# This class represents a sorted collection of Geminabox::GemVersion objects.
|
4
|
-
# It it used widely throughout the system for displaying and filtering gems.
|
5
|
-
class Geminabox::GemVersionCollection
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
# Array of Geminabox::GemVersion objects, or an array of [name, version,
|
9
|
-
# platform] triples.
|
10
|
-
def initialize(initial_gems=[])
|
11
|
-
@gems = initial_gems.map{|object|
|
12
|
-
coerce_to_gem_version(object)
|
13
|
-
}.sort
|
14
|
-
end
|
1
|
+
module Geminabox
|
15
2
|
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
end
|
3
|
+
# This class represents a sorted collection of Geminabox::GemVersion objects.
|
4
|
+
# It it used widely throughout the system for displaying and filtering gems.
|
5
|
+
class GemVersionCollection
|
6
|
+
include Enumerable
|
21
7
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
8
|
+
# Array of Geminabox::GemVersion objects, or an array of [name, version,
|
9
|
+
# platform] triples.
|
10
|
+
def initialize(initial_gems=[])
|
11
|
+
@gems = initial_gems.map{|object|
|
12
|
+
coerce_to_gem_version(object)
|
13
|
+
}.sort
|
14
|
+
end
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
|
16
|
+
# FIXME: Terminology makes no sense when the version are not all of the same
|
17
|
+
# name
|
18
|
+
def oldest
|
19
|
+
@gems.first
|
20
|
+
end
|
31
21
|
|
32
|
-
|
33
|
-
|
34
|
-
|
22
|
+
# FIXME: Terminology makes no sense when the version are not all of the same
|
23
|
+
# name
|
24
|
+
def newest
|
25
|
+
@gems.last
|
26
|
+
end
|
35
27
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
28
|
+
def size
|
29
|
+
@gems.size
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
@gems.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
# The collection can contain gems of different names, this method groups them
|
37
|
+
# by name, and then sorts the different version of each name by version and
|
38
|
+
# platform.
|
39
|
+
#
|
40
|
+
# yields 'foo_gem', version_collection
|
41
|
+
def by_name(&block)
|
42
|
+
@grouped ||= @gems.group_by(&:name).map{|name, collection|
|
43
|
+
[name, Geminabox::GemVersionCollection.new(collection)]
|
44
|
+
}.sort_by{|name, collection|
|
45
|
+
name.downcase
|
46
|
+
}
|
47
|
+
|
48
|
+
if block_given?
|
49
|
+
@grouped.each(&block)
|
50
|
+
else
|
51
|
+
@grouped
|
52
|
+
end
|
52
53
|
end
|
53
|
-
end
|
54
54
|
|
55
|
-
private
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
private
|
56
|
+
def coerce_to_gem_version(object)
|
57
|
+
if object.is_a?(Geminabox::GemVersion)
|
58
|
+
object
|
59
|
+
else
|
60
|
+
Geminabox::GemVersion.new(*object)
|
61
|
+
end
|
61
62
|
end
|
62
63
|
end
|
64
|
+
|
63
65
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
|
3
|
+
module Geminabox
|
4
|
+
|
5
|
+
class Hostess < Sinatra::Base
|
6
|
+
def serve
|
7
|
+
send_file(File.expand_path(File.join(Server.data, *request.path_info)), :type => response['Content-Type'])
|
8
|
+
end
|
9
|
+
|
10
|
+
%w[/specs.4.8.gz
|
11
|
+
/latest_specs.4.8.gz
|
12
|
+
/prerelease_specs.4.8.gz
|
13
|
+
].each do |index|
|
14
|
+
get index do
|
15
|
+
content_type 'application/x-gzip'
|
16
|
+
serve
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
%w[/quick/Marshal.4.8/*.gemspec.rz
|
21
|
+
/yaml.Z
|
22
|
+
/Marshal.4.8.Z
|
23
|
+
].each do |deflated_index|
|
24
|
+
get deflated_index do
|
25
|
+
content_type('application/x-deflate')
|
26
|
+
serve
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
%w[/yaml
|
31
|
+
/Marshal.4.8
|
32
|
+
/specs.4.8
|
33
|
+
/latest_specs.4.8
|
34
|
+
/prerelease_specs.4.8
|
35
|
+
].each do |old_index|
|
36
|
+
get old_index do
|
37
|
+
serve
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
get "/gems/*.gem" do
|
42
|
+
serve
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|