shuck 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -9
- data/Rakefile +1 -0
- data/lib/shuck/cli.rb +11 -0
- data/lib/shuck/file_store.rb +59 -3
- data/lib/shuck/rate_limitable_file.rb +21 -0
- data/lib/shuck/server.rb +96 -32
- data/lib/shuck/version.rb +1 -1
- data/lib/shuck/xml_adapter.rb +3 -3
- data/shuck.gemspec +2 -1
- data/test/local_s3_cfg +34 -0
- data/test/right_aws_commands_test.rb +10 -1
- data/test/s3cmd_test.rb +30 -0
- metadata +11 -19
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
shuck (0.0.
|
4
|
+
shuck (0.0.8)
|
5
5
|
builder
|
6
6
|
thor
|
7
7
|
|
@@ -13,17 +13,10 @@ GEM
|
|
13
13
|
mime-types
|
14
14
|
xml-simple
|
15
15
|
builder (2.1.2)
|
16
|
-
columnize (0.3.2)
|
17
|
-
linecache (0.43)
|
18
16
|
mime-types (1.16)
|
19
17
|
right_aws (2.0.0)
|
20
18
|
right_http_connection (>= 1.2.1)
|
21
19
|
right_http_connection (1.2.4)
|
22
|
-
ruby-debug (0.10.4)
|
23
|
-
columnize (>= 0.1)
|
24
|
-
ruby-debug-base (~> 0.10.4.0)
|
25
|
-
ruby-debug-base (0.10.4)
|
26
|
-
linecache (>= 0.3)
|
27
20
|
thor (0.14.4)
|
28
21
|
xml-simple (1.0.12)
|
29
22
|
|
@@ -35,6 +28,5 @@ DEPENDENCIES
|
|
35
28
|
builder
|
36
29
|
bundler (>= 1.0.0)
|
37
30
|
right_aws
|
38
|
-
ruby-debug
|
39
31
|
shuck!
|
40
32
|
thor
|
data/Rakefile
CHANGED
data/lib/shuck/cli.rb
CHANGED
@@ -9,12 +9,14 @@ module Shuck
|
|
9
9
|
method_option :root, :type => :string, :aliases => '-r', :required => true
|
10
10
|
method_option :port, :type => :numeric, :aliases => '-p', :required => true
|
11
11
|
method_option :hostname, :type => :string, :aliases => '-h', :desc => "The root name of the host. Defaults to localhost."
|
12
|
+
method_option :limit, :aliases => '-l', :type => :string, :desc => 'Rate limit for serving (ie. 50K, 1.0M)'
|
12
13
|
def server
|
13
14
|
store = nil
|
14
15
|
if options[:root]
|
15
16
|
root = File.expand_path(options[:root])
|
16
17
|
store = FileStore.new(root)
|
17
18
|
end
|
19
|
+
|
18
20
|
if store.nil?
|
19
21
|
puts "You must specify a root to use a file store (the current default)"
|
20
22
|
exit(-1)
|
@@ -25,6 +27,15 @@ module Shuck
|
|
25
27
|
hostname = options[:hostname]
|
26
28
|
end
|
27
29
|
|
30
|
+
if options[:limit]
|
31
|
+
begin
|
32
|
+
store.rate_limit = options[:limit]
|
33
|
+
rescue
|
34
|
+
puts $!.message
|
35
|
+
exit(-1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
28
39
|
puts "Loading Shuck with #{root} on port #{options[:port]} with hostname #{hostname}"
|
29
40
|
server = Shuck::Server.new(options[:port],store,hostname)
|
30
41
|
server.serve
|
data/lib/shuck/file_store.rb
CHANGED
@@ -2,6 +2,7 @@ require 'fileutils'
|
|
2
2
|
require 'time'
|
3
3
|
require 'shuck/s3_object'
|
4
4
|
require 'shuck/bucket'
|
5
|
+
require 'shuck/rate_limitable_file'
|
5
6
|
require 'digest/md5'
|
6
7
|
require 'yaml'
|
7
8
|
|
@@ -21,6 +22,25 @@ module Shuck
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
# Pass a rate limit in bytes per second
|
26
|
+
def rate_limit=(rate_limit)
|
27
|
+
if rate_limit.is_a?(String)
|
28
|
+
if rate_limit =~ /^(\d+)$/
|
29
|
+
RateLimitableFile.rate_limit = rate_limit.to_i
|
30
|
+
elsif rate_limit =~ /^(.*)K$/
|
31
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000
|
32
|
+
elsif rate_limit =~ /^(.*)M$/
|
33
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000000
|
34
|
+
elsif rate_limit =~ /^(.*)G$/
|
35
|
+
RateLimitableFile.rate_limit = $1.to_f * 1000000000
|
36
|
+
else
|
37
|
+
raise "Invalid Rate Limit Format: Valid values include (1000,10K,1.1M)"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
RateLimitableFile.rate_limit = nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
24
44
|
def buckets
|
25
45
|
@buckets
|
26
46
|
end
|
@@ -37,14 +57,15 @@ module Shuck
|
|
37
57
|
end
|
38
58
|
|
39
59
|
def get_object(bucket,object, request)
|
40
|
-
begin
|
60
|
+
begin
|
41
61
|
real_obj = S3Object.new
|
42
62
|
obj_root = File.join(@root,bucket,object,SHUCK_METADATA_DIR)
|
43
63
|
metadata = YAML.parse(File.open(File.join(obj_root,"metadata"),'rb').read)
|
44
64
|
real_obj.name = object
|
45
65
|
real_obj.md5 = metadata[:md5].value
|
46
66
|
real_obj.content_type = metadata[:content_type] ? metadata[:content_type].value : "application/octet-stream"
|
47
|
-
real_obj.io = File.open(File.join(obj_root,"content"),'rb')
|
67
|
+
#real_obj.io = File.open(File.join(obj_root,"content"),'rb')
|
68
|
+
real_obj.io = RateLimitableFile.open(File.join(obj_root,"content"),'rb')
|
48
69
|
return real_obj
|
49
70
|
rescue
|
50
71
|
puts $!
|
@@ -55,6 +76,40 @@ module Shuck
|
|
55
76
|
def object_metadata(bucket,object)
|
56
77
|
end
|
57
78
|
|
79
|
+
def copy_object(src_bucket,src_object,dst_bucket,dst_object)
|
80
|
+
src_root = File.join(@root,src_bucket,src_object,SHUCK_METADATA_DIR)
|
81
|
+
src_obj = S3Object.new
|
82
|
+
src_metadata_filename = File.join(src_root,"metadata")
|
83
|
+
src_metadata = YAML.parse(File.open(src_metadata_filename,'rb').read)
|
84
|
+
src_content_filename = File.join(src_root,"content")
|
85
|
+
|
86
|
+
dst_filename= File.join(@root,dst_bucket,dst_object)
|
87
|
+
FileUtils.mkdir_p(dst_filename)
|
88
|
+
|
89
|
+
metadata_dir = File.join(dst_filename,SHUCK_METADATA_DIR)
|
90
|
+
FileUtils.mkdir_p(metadata_dir)
|
91
|
+
|
92
|
+
content = File.join(metadata_dir,"content")
|
93
|
+
metadata = File.join(metadata_dir,"metadata")
|
94
|
+
|
95
|
+
File.open(content,'wb') do |f|
|
96
|
+
File.open(src_content_filename,'rb') do |input|
|
97
|
+
f << input.read
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
File.open(metadata,'w') do |f|
|
102
|
+
File.open(src_metadata_filename,'r') do |input|
|
103
|
+
f << input.read
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
obj = S3Object.new
|
108
|
+
obj.md5 = src_metadata[:md5]
|
109
|
+
obj.content_type = src_metadata[:content_type]
|
110
|
+
return obj
|
111
|
+
end
|
112
|
+
|
58
113
|
def store_object(bucket,object,request)
|
59
114
|
begin
|
60
115
|
filename = File.join(@root,bucket,object)
|
@@ -67,7 +122,8 @@ module Shuck
|
|
67
122
|
metadata = File.join(filename,SHUCK_METADATA_DIR,"metadata")
|
68
123
|
|
69
124
|
md5 = Digest::MD5.new
|
70
|
-
|
125
|
+
|
126
|
+
File.open(content,'wb') do |f|
|
71
127
|
request.body do |chunk|
|
72
128
|
f << chunk
|
73
129
|
md5 << chunk
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Shuck
|
2
|
+
class RateLimitableFile < File
|
3
|
+
@@rate_limit = nil
|
4
|
+
# Specify a rate limit in bytes per second
|
5
|
+
def self.rate_limit
|
6
|
+
@@rate_limit
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.rate_limit=(rate_limit)
|
10
|
+
@@rate_limit = rate_limit
|
11
|
+
end
|
12
|
+
|
13
|
+
def read(args)
|
14
|
+
if @@rate_limit
|
15
|
+
time_to_sleep = args / @@rate_limit
|
16
|
+
sleep(time_to_sleep)
|
17
|
+
end
|
18
|
+
return super(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/shuck/server.rb
CHANGED
@@ -3,6 +3,28 @@ require 'shuck/file_store'
|
|
3
3
|
require 'shuck/xml_adapter'
|
4
4
|
|
5
5
|
module Shuck
|
6
|
+
class Request
|
7
|
+
CREATE_BUCKET = "CREATE_BUCKET"
|
8
|
+
STORE = "STORE"
|
9
|
+
COPY = "COPY"
|
10
|
+
GET = "GET"
|
11
|
+
MOVE = "MOVE"
|
12
|
+
DELETE = "DELETE"
|
13
|
+
|
14
|
+
attr_accessor :bucket,:object,:type,:src_bucket,:src_object,:method,:webrick_request,:path
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
puts "-----Inspect Shuck Request"
|
18
|
+
puts "Type: #{@type}"
|
19
|
+
puts "Request Method: #{@method}"
|
20
|
+
puts "Bucket: #{@bucket}"
|
21
|
+
puts "Object: #{@object}"
|
22
|
+
puts "Src Bucket: #{@src_bucket}"
|
23
|
+
puts "Src Object: #{@src_object}"
|
24
|
+
puts "-----Done"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
6
28
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
7
29
|
def initialize(server,store,hostname)
|
8
30
|
super(server)
|
@@ -34,16 +56,22 @@ module Shuck
|
|
34
56
|
elems = path.split("/")
|
35
57
|
end
|
36
58
|
|
37
|
-
if elems.size ==
|
59
|
+
if elems.size == 0
|
60
|
+
# List buckets
|
61
|
+
buckets = @store.buckets
|
62
|
+
response.status = 200
|
63
|
+
response.body = XmlAdapter.buckets(buckets)
|
64
|
+
response['Content-Type'] = "application/xml"
|
65
|
+
elsif elems.size == 1
|
38
66
|
bucket_obj = @store.get_bucket(bucket)
|
39
67
|
if bucket_obj
|
40
68
|
response.status = 200
|
41
69
|
response.body = XmlAdapter.bucket(bucket_obj)
|
42
|
-
response['Content-Type'] = "application/xml"
|
70
|
+
response['Content-Type'] = "application/xml"
|
43
71
|
else
|
44
72
|
response.status = 404
|
45
73
|
response.body = XmlAdapter.error_no_such_bucket(bucket)
|
46
|
-
response['Content-Type'] = "application/xml"
|
74
|
+
response['Content-Type'] = "application/xml"
|
47
75
|
end
|
48
76
|
else
|
49
77
|
object = elems[1,elems.size].join('/')
|
@@ -88,61 +116,97 @@ module Shuck
|
|
88
116
|
end
|
89
117
|
|
90
118
|
def do_PUT(request,response)
|
91
|
-
|
119
|
+
s_req = normalize_request(request)
|
120
|
+
|
121
|
+
case s_req.type
|
122
|
+
when Request::COPY
|
123
|
+
@store.copy_object(s_req.src_bucket,s_req.src_object,s_req.bucket,s_req.object)
|
124
|
+
when Request::STORE
|
125
|
+
real_obj = @store.store_object(s_req.bucket,s_req.object,s_req.webrick_request)
|
126
|
+
response['Etag'] = real_obj.md5
|
127
|
+
when Request::CREATE_BUCKET
|
128
|
+
@store.create_bucket(s_req.bucket)
|
129
|
+
end
|
130
|
+
|
131
|
+
response.status = 200
|
132
|
+
response.body = ""
|
133
|
+
response['Content-Type'] = "text/xml"
|
134
|
+
end
|
135
|
+
|
136
|
+
def do_POST(request,response)
|
137
|
+
p request
|
138
|
+
end
|
139
|
+
|
140
|
+
def do_DELETE(request,response)
|
141
|
+
p request
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
# This method takes a webrick request and generates a normalized Shuck request
|
146
|
+
def normalize_request(webrick_r)
|
147
|
+
path = webrick_r.path
|
92
148
|
path_len = path.size
|
93
149
|
path_style_request = true
|
94
|
-
host =
|
95
|
-
puts host
|
150
|
+
host = webrick_r["Host"]
|
96
151
|
bucket = nil
|
152
|
+
object = nil
|
97
153
|
path_style_request = true
|
154
|
+
|
98
155
|
if host != @hostname
|
99
156
|
bucket = host.split(".")[0]
|
100
157
|
path_style_request = false
|
101
158
|
end
|
102
159
|
|
160
|
+
req = Request.new
|
161
|
+
req.path = webrick_r.path
|
162
|
+
|
103
163
|
if path == "/"
|
104
164
|
if bucket
|
105
|
-
|
106
|
-
else
|
107
|
-
puts "Unsure what to do"
|
165
|
+
req.type = Request::CREATE_BUCKET
|
108
166
|
end
|
109
|
-
else
|
167
|
+
else
|
110
168
|
if path_style_request
|
111
169
|
elems = path[1,path_len].split("/")
|
112
170
|
bucket = elems[0]
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
if path_style_request
|
118
|
-
if elems.size == 1
|
119
|
-
@store.create_bucket(bucket)
|
120
|
-
else
|
171
|
+
if elems.size == 1
|
172
|
+
req.type = Request::CREATE_BUCKET
|
173
|
+
else
|
174
|
+
req.type = Request::STORE
|
121
175
|
object = elems[1,elems.size].join('/')
|
122
|
-
real_obj = @store.store_object(bucket,object,request)
|
123
|
-
response['Etag'] = real_obj.md5
|
124
176
|
end
|
125
177
|
else
|
126
|
-
|
127
|
-
|
178
|
+
req.type = Request::STORE
|
179
|
+
object = webrick_r.path
|
128
180
|
end
|
129
181
|
end
|
130
|
-
response.status = 200
|
131
|
-
response.body = ""
|
132
|
-
response['Content-Type'] = "text/xml"
|
133
|
-
end
|
134
182
|
|
135
|
-
|
136
|
-
|
137
|
-
|
183
|
+
copy_source = webrick_r.header["x-amz-copy-source"]
|
184
|
+
if copy_source and copy_source.size == 1
|
185
|
+
src_elems = copy_source.first.split("/")
|
186
|
+
root_offset = src_elems[0] == "" ? 1 : 0
|
187
|
+
req.src_bucket = src_elems[root_offset]
|
188
|
+
req.src_object = src_elems[1 + root_offset,src_elems.size].join("/")
|
189
|
+
req.type = Request::COPY
|
190
|
+
end
|
191
|
+
|
192
|
+
req.bucket = bucket
|
193
|
+
req.object = object
|
194
|
+
req.webrick_request = webrick_r
|
195
|
+
return req
|
138
196
|
end
|
139
197
|
|
140
|
-
def
|
141
|
-
puts "
|
142
|
-
|
198
|
+
def dump_request(request)
|
199
|
+
puts "----------Dump Request-------------"
|
200
|
+
puts request.request_method
|
201
|
+
puts request.path
|
202
|
+
request.each do |k,v|
|
203
|
+
puts "#{k}:#{v}"
|
204
|
+
end
|
205
|
+
puts "----------End Dump -------------"
|
143
206
|
end
|
144
207
|
end
|
145
208
|
|
209
|
+
|
146
210
|
class Server
|
147
211
|
def initialize(port,store,hostname = "localhost")
|
148
212
|
@port = port
|
data/lib/shuck/version.rb
CHANGED
data/lib/shuck/xml_adapter.rb
CHANGED
@@ -3,7 +3,7 @@ require 'time'
|
|
3
3
|
|
4
4
|
module Shuck
|
5
5
|
class XmlAdapter
|
6
|
-
def self.buckets(
|
6
|
+
def self.buckets(bucket_objects)
|
7
7
|
output = ""
|
8
8
|
xml = Builder::XmlMarkup.new(:target => output)
|
9
9
|
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
|
@@ -13,10 +13,10 @@ module Shuck
|
|
13
13
|
owner.DisplayName("Shuck")
|
14
14
|
}
|
15
15
|
lam.Buckets { |buckets|
|
16
|
-
|
16
|
+
bucket_objects.each do |bucket|
|
17
17
|
buckets.Bucket do |b|
|
18
18
|
b.Name(bucket.name)
|
19
|
-
b.CreationDate(bucket.creation_date.
|
19
|
+
b.CreationDate(bucket.creation_date.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
}
|
data/shuck.gemspec
CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
18
|
s.add_development_dependency "aws-s3"
|
19
19
|
s.add_development_dependency "right_aws"
|
20
|
-
s.add_development_dependency "
|
20
|
+
#s.add_development_dependency "aws-sdk"
|
21
|
+
#s.add_development_dependency "ruby-debug19"
|
21
22
|
s.add_dependency "thor"
|
22
23
|
s.add_dependency "builder"
|
23
24
|
|
data/test/local_s3_cfg
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
[default]
|
2
|
+
access_key = abc
|
3
|
+
acl_public = False
|
4
|
+
bucket_location = US
|
5
|
+
cloudfront_host = cloudfront.amazonaws.com
|
6
|
+
cloudfront_resource = /2008-06-30/distribution
|
7
|
+
default_mime_type = binary/octet-stream
|
8
|
+
delete_removed = False
|
9
|
+
dry_run = False
|
10
|
+
encoding = UTF-8
|
11
|
+
encrypt = False
|
12
|
+
force = False
|
13
|
+
get_continue = False
|
14
|
+
gpg_command = None
|
15
|
+
gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
16
|
+
gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s
|
17
|
+
gpg_passphrase =
|
18
|
+
guess_mime_type = True
|
19
|
+
host_base = s3.localhost:10453
|
20
|
+
host_bucket = %(bucket)s.s3.localhost:10453
|
21
|
+
human_readable_sizes = False
|
22
|
+
list_md5 = False
|
23
|
+
preserve_attrs = True
|
24
|
+
progress_meter = True
|
25
|
+
proxy_host =
|
26
|
+
proxy_port = 0
|
27
|
+
recursive = False
|
28
|
+
recv_chunk = 4096
|
29
|
+
secret_key = def
|
30
|
+
send_chunk = 4096
|
31
|
+
simpledb_host = sdb.amazonaws.com
|
32
|
+
skip_existing = False
|
33
|
+
use_https = False
|
34
|
+
verbosity = WARNING
|
@@ -6,7 +6,9 @@ require 'right_aws'
|
|
6
6
|
class RightAWSCommandsTest < Test::Unit::TestCase
|
7
7
|
|
8
8
|
def setup
|
9
|
-
@s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
|
9
|
+
@s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX',
|
10
|
+
{:multi_thread => false, :server => 'localhost',
|
11
|
+
:port => 10453, :protocol => 'http',:logger => Logger.new("/dev/null") })
|
10
12
|
end
|
11
13
|
|
12
14
|
def teardown
|
@@ -51,4 +53,11 @@ class RightAWSCommandsTest < Test::Unit::TestCase
|
|
51
53
|
assert_equal "recursive", output
|
52
54
|
end
|
53
55
|
|
56
|
+
def test_intra_bucket_copy
|
57
|
+
@s3.put("s3media","original.txt","Hello World")
|
58
|
+
@s3.copy("s3media","original.txt","s3media","copy.txt")
|
59
|
+
obj = @s3.get("s3media","copy.txt")
|
60
|
+
assert_equal "Hello World",obj[:object]
|
61
|
+
end
|
62
|
+
|
54
63
|
end
|
data/test/s3cmd_test.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'shuck/server'
|
4
|
+
|
5
|
+
# You need to have s3cmd installed to use this
|
6
|
+
class S3CmdTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@s3cmd = "s3cmd --config"
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_bucket
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_store
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_large_store
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_multi_directory
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_intra_bucket_copy
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 8
|
9
|
+
version: 0.0.8
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Curtis Spencer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2012-02-22 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -59,7 +59,7 @@ dependencies:
|
|
59
59
|
type: :development
|
60
60
|
version_requirements: *id003
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
62
|
+
name: thor
|
63
63
|
prerelease: false
|
64
64
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
@@ -69,10 +69,10 @@ dependencies:
|
|
69
69
|
segments:
|
70
70
|
- 0
|
71
71
|
version: "0"
|
72
|
-
type: :
|
72
|
+
type: :runtime
|
73
73
|
version_requirements: *id004
|
74
74
|
- !ruby/object:Gem::Dependency
|
75
|
-
name:
|
75
|
+
name: builder
|
76
76
|
prerelease: false
|
77
77
|
requirement: &id005 !ruby/object:Gem::Requirement
|
78
78
|
none: false
|
@@ -84,19 +84,6 @@ dependencies:
|
|
84
84
|
version: "0"
|
85
85
|
type: :runtime
|
86
86
|
version_requirements: *id005
|
87
|
-
- !ruby/object:Gem::Dependency
|
88
|
-
name: builder
|
89
|
-
prerelease: false
|
90
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
91
|
-
none: false
|
92
|
-
requirements:
|
93
|
-
- - ">="
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
segments:
|
96
|
-
- 0
|
97
|
-
version: "0"
|
98
|
-
type: :runtime
|
99
|
-
version_requirements: *id006
|
100
87
|
description: Use Shuck to test basic S3 functionality without actually connecting to S3
|
101
88
|
email:
|
102
89
|
- curtis@sevenforge.com
|
@@ -117,13 +104,16 @@ files:
|
|
117
104
|
- lib/shuck/bucket.rb
|
118
105
|
- lib/shuck/cli.rb
|
119
106
|
- lib/shuck/file_store.rb
|
107
|
+
- lib/shuck/rate_limitable_file.rb
|
120
108
|
- lib/shuck/s3_object.rb
|
121
109
|
- lib/shuck/server.rb
|
122
110
|
- lib/shuck/version.rb
|
123
111
|
- lib/shuck/xml_adapter.rb
|
124
112
|
- shuck.gemspec
|
113
|
+
- test/local_s3_cfg
|
125
114
|
- test/right_aws_commands_test.rb
|
126
115
|
- test/s3_commands_test.rb
|
116
|
+
- test/s3cmd_test.rb
|
127
117
|
- test/test_helper.rb
|
128
118
|
has_rdoc: true
|
129
119
|
homepage: ""
|
@@ -158,6 +148,8 @@ signing_key:
|
|
158
148
|
specification_version: 3
|
159
149
|
summary: Shuck is a phony S3 engine with minimal dependencies
|
160
150
|
test_files:
|
151
|
+
- test/local_s3_cfg
|
161
152
|
- test/right_aws_commands_test.rb
|
162
153
|
- test/s3_commands_test.rb
|
154
|
+
- test/s3cmd_test.rb
|
163
155
|
- test/test_helper.rb
|