blobsterix 0.0.14 → 0.0.19
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +9 -0
- data/README.md +5 -1
- data/blobsterix.gemspec +1 -0
- data/lib/blobsterix/blob/blob_api.rb +1 -0
- data/lib/blobsterix/blob/blob_url_helper.rb +2 -39
- data/lib/blobsterix/helper/blob_access.rb +9 -10
- data/lib/blobsterix/helper/config_loader.rb +10 -18
- data/lib/blobsterix/helper/directory_list.rb +131 -0
- data/lib/blobsterix/helper/http.rb +13 -14
- data/lib/blobsterix/helper/jsonizer.rb +45 -0
- data/lib/blobsterix/helper/logable.rb +25 -0
- data/lib/blobsterix/helper/murmur.rb +14 -0
- data/lib/blobsterix/helper/simple_proxy.rb +11 -0
- data/lib/blobsterix/helper/template_renderer.rb +4 -0
- data/lib/blobsterix/helper/url_helper.rb +41 -0
- data/lib/blobsterix/router/app_router.rb +15 -65
- data/lib/blobsterix/s3/s3_api.rb +20 -6
- data/lib/blobsterix/s3/s3_auth.rb +32 -0
- data/lib/blobsterix/s3/s3_auth_key_store.rb +14 -0
- data/lib/blobsterix/s3/s3_auth_v2.rb +62 -0
- data/lib/blobsterix/s3/s3_auth_v2_helper.rb +42 -0
- data/lib/blobsterix/s3/s3_auth_v2_query.rb +37 -0
- data/lib/blobsterix/s3/s3_auth_v4.rb +33 -0
- data/lib/blobsterix/s3/s3_url_helper.rb +1 -38
- data/lib/blobsterix/service.rb +3 -9
- data/lib/blobsterix/storage/bucket.rb +6 -3
- data/lib/blobsterix/storage/bucket_entry.rb +11 -1
- data/lib/blobsterix/storage/cache.rb +13 -21
- data/lib/blobsterix/storage/file_system.rb +37 -40
- data/lib/blobsterix/storage/storage.rb +2 -2
- data/lib/blobsterix/transformation/image_transformation.rb +134 -386
- data/lib/blobsterix/version.rb +1 -1
- data/lib/blobsterix.rb +22 -1
- data/spec/lib/helper/directory_list_spec.rb +51 -0
- data/spec/lib/s3/s3_api_spec.rb +27 -0
- data/spec/lib/s3/s3_auth_spec.rb +181 -0
- data/spec/lib/storage/file_system_spec.rb +14 -0
- data/spec/spec_helper.rb +1 -0
- data/templates/storage_template.rb +2 -2
- metadata +30 -2
data/lib/blobsterix/s3/s3_api.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Blobsterix
|
2
2
|
class S3Api < AppRouterBase
|
3
3
|
include S3UrlHelper
|
4
|
+
include UrlHelper
|
4
5
|
|
5
6
|
get "/", :list_buckets
|
6
7
|
|
@@ -26,12 +27,20 @@ module Blobsterix
|
|
26
27
|
post "*any", :next_api
|
27
28
|
|
28
29
|
private
|
30
|
+
def check_auth
|
31
|
+
return true unless Blobsterix.secret_key_store
|
32
|
+
Blobsterix::S3Auth.authenticate(env).check(Blobsterix.secret_key_store)
|
33
|
+
end
|
34
|
+
|
29
35
|
def list_buckets
|
30
|
-
|
31
|
-
|
36
|
+
return Http.NotAuthorized unless check_auth
|
37
|
+
Blobsterix.event("s3_api.list_bucket",:bucket => bucket)
|
38
|
+
start_path = env["params"]["marker"] if env["params"]
|
39
|
+
Http.OK storage.list(bucket, :start_path => start_path).to_xml, "xml"
|
32
40
|
end
|
33
41
|
|
34
42
|
def get_file(send_with_data=true)
|
43
|
+
return Http.NotAuthorized unless check_auth
|
35
44
|
return Http.NotFound if favicon
|
36
45
|
|
37
46
|
if bucket?
|
@@ -41,25 +50,28 @@ module Blobsterix
|
|
41
50
|
Http.NotFound
|
42
51
|
end
|
43
52
|
else
|
44
|
-
|
53
|
+
list_buckets
|
45
54
|
end
|
46
55
|
end
|
47
56
|
|
48
57
|
def get_file_head
|
49
|
-
|
58
|
+
return Http.NotAuthorized unless check_auth
|
59
|
+
#TODO: add event?
|
50
60
|
get_file(false)
|
51
61
|
end
|
52
62
|
|
53
63
|
def create_bucket
|
54
|
-
|
64
|
+
return Http.NotAuthorized unless check_auth
|
65
|
+
Blobsterix.event("s3_api.upload",:bucket => bucket)
|
55
66
|
Http.OK storage.create(bucket), "xml"
|
56
67
|
end
|
57
68
|
|
58
69
|
def upload_data
|
70
|
+
return Http.NotAuthorized unless check_auth
|
59
71
|
source = cached_upload
|
60
72
|
accept = AcceptType.new("*/*")#source.accept_type()
|
61
73
|
|
62
|
-
trafo_current = trafo(
|
74
|
+
trafo_current = trafo(transformation_string)
|
63
75
|
file_current = file
|
64
76
|
bucket_current = bucket
|
65
77
|
Blobsterix.event("s3_api.upload", :bucket => bucket_current,
|
@@ -71,6 +83,7 @@ module Blobsterix
|
|
71
83
|
end
|
72
84
|
|
73
85
|
def delete_bucket
|
86
|
+
return Http.NotAuthorized unless check_auth
|
74
87
|
Blobsterix.event("s3_api.delete_bucket", :bucket => bucket)
|
75
88
|
|
76
89
|
if bucket?
|
@@ -81,6 +94,7 @@ module Blobsterix
|
|
81
94
|
end
|
82
95
|
|
83
96
|
def delete_file
|
97
|
+
return Http.NotAuthorized unless check_auth
|
84
98
|
Blobsterix.event("s3_api.delete_file", :bucket => bucket,:file => file)
|
85
99
|
if bucket?
|
86
100
|
Http.OK_no_data storage.delete_key(bucket, file), "xml"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3Auth
|
3
|
+
|
4
|
+
VERSIONS = [
|
5
|
+
V2,
|
6
|
+
V2Query,
|
7
|
+
V4
|
8
|
+
]
|
9
|
+
|
10
|
+
def self.authenticate(env)
|
11
|
+
VERSIONS.each do |version|
|
12
|
+
v = version.create(env)
|
13
|
+
return v if v
|
14
|
+
end
|
15
|
+
NoAuth.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.current_time
|
19
|
+
(@current_time||=lambda{Time.now}).call
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.current_time=(obj)
|
23
|
+
@current_time=obj
|
24
|
+
end
|
25
|
+
|
26
|
+
class NoAuth
|
27
|
+
def check(secret)
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3Auth
|
3
|
+
class V2
|
4
|
+
include ::Blobsterix::S3UrlHelper
|
5
|
+
include ::Blobsterix::UrlHelper
|
6
|
+
include ::Blobsterix::Logable
|
7
|
+
include V2Helper
|
8
|
+
|
9
|
+
# these should actually be used when calculating
|
10
|
+
# the signature when suplied as query parameters,
|
11
|
+
# but they are not needed in this usecase
|
12
|
+
SUBRESOURCES = [
|
13
|
+
"acl",
|
14
|
+
"lifecycle",
|
15
|
+
"location",
|
16
|
+
"logging",
|
17
|
+
"notification",
|
18
|
+
"partNumber",
|
19
|
+
"policy",
|
20
|
+
"requestPayment",
|
21
|
+
"torrent",
|
22
|
+
"uploadId",
|
23
|
+
"uploads",
|
24
|
+
"versionId",
|
25
|
+
"versioning",
|
26
|
+
"versions",
|
27
|
+
"website"
|
28
|
+
]
|
29
|
+
|
30
|
+
V2_REGEX = /AWS (\w+):(.+)/
|
31
|
+
|
32
|
+
def self.create(env)
|
33
|
+
auth_string = env["HTTP_AUTHORIZATION"]
|
34
|
+
matcher = V2_REGEX.match(auth_string)
|
35
|
+
matcher ? V2.new(env, matcher[1], matcher[2]) : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :env, :access_key, :signature
|
39
|
+
def initialize(env, access_key, signature)
|
40
|
+
@env = env
|
41
|
+
@access_key = access_key
|
42
|
+
@signature = signature
|
43
|
+
end
|
44
|
+
|
45
|
+
def time_to_check
|
46
|
+
env["HTTP_X_AMZ_DATE"]||env["HTTP_DATE"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_expired?
|
50
|
+
::Blobsterix::S3Auth.current_time-Time.parse(time_to_check) > 15*60
|
51
|
+
end
|
52
|
+
|
53
|
+
def time_of_request
|
54
|
+
env["HTTP_DATE"] unless env["HTTP_X_AMZ_DATE"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def server_signature(secret_key, str)
|
58
|
+
gen_signature(secret_key, str)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3Auth
|
3
|
+
module V2Helper
|
4
|
+
|
5
|
+
def canonicalized_amz_headers
|
6
|
+
amz_headers = env.select{|key,value|
|
7
|
+
/HTTP_X_AMZ/i.match(key) if key.is_a?(String)
|
8
|
+
}.sort.map{|key,value|
|
9
|
+
"#{key.gsub("HTTP_","").downcase.gsub("_","-")}:#{value}"
|
10
|
+
}
|
11
|
+
return amz_headers.join("\n")+"\n" if amz_headers.length > 0
|
12
|
+
""
|
13
|
+
end
|
14
|
+
|
15
|
+
def canonicalized_resource(escape=false)
|
16
|
+
return "/" if bucket == "root"
|
17
|
+
escape ? "/#{bucket}/#{CGI.escape(file)}" : "/#{bucket}/#{file}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def string_to_sign(escape=false)
|
21
|
+
"#{env["REQUEST_METHOD"]}\n"+
|
22
|
+
"#{env["HTTP_CONTENT_MD5"]||""}\n"+
|
23
|
+
"#{env["CONTENT_TYPE"]||""}\n"+
|
24
|
+
"#{time_of_request}\n"+
|
25
|
+
"#{canonicalized_amz_headers}#{canonicalized_resource(escape)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def check(secret_key_store)
|
29
|
+
return false if is_expired?
|
30
|
+
# logger.info string_to_sign
|
31
|
+
own_key_0 = server_signature(secret_key_store.get_key(access_key), string_to_sign)
|
32
|
+
own_key_1 = server_signature(secret_key_store.get_key(access_key), string_to_sign(true))
|
33
|
+
# logger.info "[#{@signature}] == [#{own_key_0}] OR [#{@signature}] == [#{own_key_1}]"
|
34
|
+
@signature == own_key_0 || @signature == own_key_1
|
35
|
+
end
|
36
|
+
|
37
|
+
def gen_signature(secret_key, str)
|
38
|
+
Base64.encode64( OpenSSL::HMAC.digest("sha1", secret_key, str)).chop
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3Auth
|
3
|
+
class V2Query
|
4
|
+
include ::Blobsterix::S3UrlHelper
|
5
|
+
include ::Blobsterix::UrlHelper
|
6
|
+
include ::Blobsterix::Logable
|
7
|
+
include V2Helper
|
8
|
+
|
9
|
+
def self.create(env)
|
10
|
+
return nil unless env["params"] && env["params"]["AWSAccessKeyId"] && env["params"]["Signature"]
|
11
|
+
V2Query.new(env, env["params"]["AWSAccessKeyId"], env["params"]["Signature"], env["params"]["Expires"])
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :env, :access_key, :signature, :expires
|
15
|
+
def initialize(env, access_key, signature, expires)
|
16
|
+
@env = env
|
17
|
+
@access_key = access_key
|
18
|
+
@signature = signature
|
19
|
+
@expires = expires
|
20
|
+
end
|
21
|
+
|
22
|
+
def time_of_request
|
23
|
+
expires
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_expired?
|
27
|
+
return false unless expires
|
28
|
+
::Blobsterix::S3Auth.current_time>Time.at(expires.to_i)
|
29
|
+
end
|
30
|
+
|
31
|
+
def server_signature(secret_key, str)
|
32
|
+
# URI::encode(gen_signature(secret_key, str))
|
33
|
+
gen_signature(secret_key, str)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Blobsterix
|
2
|
+
module S3Auth
|
3
|
+
class V4
|
4
|
+
include ::Blobsterix::S3UrlHelper
|
5
|
+
include ::Blobsterix::UrlHelper
|
6
|
+
|
7
|
+
|
8
|
+
V4_REGEX = /AWS4-HMAC-SHA256 Credential=(\w+\/\d+\/.+\/\w+\/aws4_request),.*SignedHeaders=(.+),.*Signature=(\w+)/
|
9
|
+
|
10
|
+
def self.create(env)
|
11
|
+
auth_string = env["HTTP_AUTHORIZATION"]
|
12
|
+
matcher = V4_REGEX.match(auth_string)
|
13
|
+
matcher ? V4.new(env, matcher[1], matcher[2], matcher[3]) : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :env, :credential, :signed_headers, :signature
|
17
|
+
def initialize(env, credential, signed_headers, signature)
|
18
|
+
@env = env
|
19
|
+
@credential = credential
|
20
|
+
@signed_headers = signed_headers
|
21
|
+
@signature = signature
|
22
|
+
end
|
23
|
+
def getSignatureKey(key, dateStamp, regionName, serviceName)
|
24
|
+
kDate = OpenSSL::HMAC.digest('sha256', "AWS4" + key, dateStamp)
|
25
|
+
kRegion = OpenSSL::HMAC.digest('sha256', kDate, regionName)
|
26
|
+
kService = OpenSSL::HMAC.digest('sha256', kRegion, serviceName)
|
27
|
+
kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")
|
28
|
+
|
29
|
+
kSigning
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -9,10 +9,6 @@ module Blobsterix
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def favicon
|
13
|
-
@favicon ||= file.match /favicon/
|
14
|
-
end
|
15
|
-
|
16
12
|
def cache_upload
|
17
13
|
cache.put_stream(cache_upload_key, env['rack.input'])
|
18
14
|
end
|
@@ -30,20 +26,9 @@ module Blobsterix
|
|
30
26
|
@cache_upload_key ||= Blobsterix::BlobAccess.new(:bucket => bucket, :id => "upload_#{file.gsub("/", "_")}")
|
31
27
|
end
|
32
28
|
|
33
|
-
def
|
29
|
+
def transformation_string
|
34
30
|
@trafo ||= env["HTTP_X_AMZ_META_TRAFO"] || ""
|
35
31
|
end
|
36
|
-
|
37
|
-
#TransformationCommand
|
38
|
-
def trafo(trafo_s='')
|
39
|
-
trafo_a = []
|
40
|
-
trafo_s.split(",").each{|command|
|
41
|
-
parts = command.split("_")
|
42
|
-
key = parts.delete_at(0)
|
43
|
-
trafo_a << [key, parts.join("_")]
|
44
|
-
}
|
45
|
-
trafo_a
|
46
|
-
end
|
47
32
|
|
48
33
|
def bucket
|
49
34
|
host = bucket_matcher(env['HTTP_HOST'])
|
@@ -66,27 +51,5 @@ module Blobsterix
|
|
66
51
|
host = bucket_matcher(env['HTTP_HOST'])
|
67
52
|
host || env[nil][:bucket] || included_bucket
|
68
53
|
end
|
69
|
-
|
70
|
-
def format
|
71
|
-
@format ||= env[nil][:format]
|
72
|
-
end
|
73
|
-
|
74
|
-
def included_bucket
|
75
|
-
if env[nil][:bucket_or_file] && env[nil][:bucket_or_file].include?("/")
|
76
|
-
env[nil][:bucket] = env[nil][:bucket_or_file].split("/")[0]
|
77
|
-
env[nil][:bucket_or_file] = env[nil][:bucket_or_file].gsub("#{env[nil][:bucket]}/", "")
|
78
|
-
true
|
79
|
-
else
|
80
|
-
false
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def file
|
85
|
-
if format
|
86
|
-
[env[nil][:file] || env[nil][:bucket_or_file] || "", format].join(".")
|
87
|
-
else
|
88
|
-
env[nil][:file] || env[nil][:bucket_or_file] || ""
|
89
|
-
end
|
90
|
-
end
|
91
54
|
end
|
92
55
|
end
|
data/lib/blobsterix/service.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Blobsterix
|
2
2
|
class Service < Goliath::API
|
3
3
|
use Goliath::Rack::Params
|
4
|
+
# use SudiMiddleWare
|
4
5
|
include Logable
|
5
6
|
=begin
|
6
7
|
def on_headers(env, headers)
|
@@ -17,16 +18,9 @@ module Blobsterix
|
|
17
18
|
env.logger.info 'closing connection'
|
18
19
|
end
|
19
20
|
=end
|
20
|
-
def get_request_id
|
21
|
-
@request_id||=0
|
22
|
-
@request_id+=1
|
23
|
-
end
|
24
21
|
def response(env)
|
25
|
-
env["
|
26
|
-
|
27
|
-
a = call_stack(env, BlobApi, StatusApi, S3Api)
|
28
|
-
logger.info "RAM USAGE After[#{Process.pid}]: " + `pmap #{Process.pid} | tail -1`[10,40].strip
|
29
|
-
a
|
22
|
+
# env["params"] = params
|
23
|
+
call_stack(env, BlobApi, StatusApi, S3Api)
|
30
24
|
end
|
31
25
|
|
32
26
|
def call_stack(env, *apis)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Blobsterix
|
2
2
|
module Storage
|
3
3
|
class Bucket
|
4
|
-
attr_accessor :name, :creation_date, :contents
|
4
|
+
attr_accessor :name, :creation_date, :contents, :truncated, :next_marker, :marker
|
5
5
|
def initialize(name, date)
|
6
6
|
@name = name
|
7
7
|
@creation_date = date
|
8
8
|
@contents = []
|
9
|
+
@truncated = false
|
9
10
|
yield self if block_given?
|
10
11
|
end
|
11
12
|
def to_xml()
|
@@ -14,9 +15,11 @@ module Blobsterix
|
|
14
15
|
xml.ListBucketResult(:xmlns => "http://doc.s3.amazonaws.com/#{date.year}-#{date.month}-#{date.day}") {
|
15
16
|
xml.Name name
|
16
17
|
xml.Prefix
|
17
|
-
xml.Marker
|
18
|
+
xml.Marker marker
|
19
|
+
xml.NextMarker next_marker
|
18
20
|
xml.MaxKeys 1000
|
19
|
-
xml.
|
21
|
+
xml.KeyCount contents.length
|
22
|
+
xml.IsTruncated truncated
|
20
23
|
contents.each{|entry|
|
21
24
|
entry.insert_xml(xml)
|
22
25
|
}
|
@@ -2,6 +2,16 @@ module Blobsterix
|
|
2
2
|
module Storage
|
3
3
|
class BucketEntry
|
4
4
|
attr_accessor :key, :last_modified, :etag, :size, :storage_class, :mimetype, :fullpath
|
5
|
+
|
6
|
+
def self.create(file, meta)
|
7
|
+
BucketEntry.new(file.to_s.gsub("\\", "/")) do |entry|
|
8
|
+
entry.last_modified = meta.last_modified.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
9
|
+
entry.etag = meta.etag
|
10
|
+
entry.size = meta.size
|
11
|
+
entry.mimetype = meta.mimetype
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
def initialize(key)
|
6
16
|
@key = key
|
7
17
|
@last_modified = "2009-10-12T17:50:30.000Z"
|
@@ -21,7 +31,7 @@ module Blobsterix
|
|
21
31
|
xml.Size size
|
22
32
|
xml.StorageClass storage_class
|
23
33
|
xml.MimeType mimetype
|
24
|
-
xml.FullPath fullpath
|
34
|
+
# xml.FullPath fullpath
|
25
35
|
}
|
26
36
|
end
|
27
37
|
end
|
@@ -4,15 +4,16 @@ module Blobsterix
|
|
4
4
|
include Blobsterix::Logable
|
5
5
|
|
6
6
|
def invalidation
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
each_meta_file do |meta_file|
|
8
|
+
blob_access = Blobsterix::SimpleProxy.new(Proc.new {
|
9
|
+
meta_to_blob_access(FileSystemMetaData.new(meta_file))
|
10
|
+
})
|
11
|
+
if Blobsterix.cache_checker.call(blob_access,File.atime(meta_file),File.ctime(meta_file))
|
12
|
+
invalidate(blob_access, true)
|
13
13
|
end
|
14
|
-
|
14
|
+
end
|
15
15
|
end
|
16
|
+
|
16
17
|
def initialize(path)
|
17
18
|
@path = Pathname.new(path)
|
18
19
|
FileUtils.mkdir_p(@path) if !Dir.exist?(@path)
|
@@ -68,10 +69,10 @@ module Blobsterix
|
|
68
69
|
private
|
69
70
|
|
70
71
|
def each_meta_file
|
71
|
-
|
72
|
-
cache_file =
|
73
|
-
if block_given? && !cache_file.to_s.match(/\.meta$/)
|
74
|
-
yield
|
72
|
+
Blobsterix::DirectoryList.each(@path) {|file_path, file|
|
73
|
+
cache_file = file_path.join file
|
74
|
+
if block_given? && !cache_file.to_s.match(/\.meta$/)
|
75
|
+
yield cache_file
|
75
76
|
cache_file
|
76
77
|
end
|
77
78
|
}
|
@@ -98,16 +99,7 @@ module Blobsterix
|
|
98
99
|
end
|
99
100
|
|
100
101
|
def hash_filename(filename)
|
101
|
-
|
102
|
-
bits = hash.to_s(2)
|
103
|
-
parts = []
|
104
|
-
6.times { |index|
|
105
|
-
len = 11
|
106
|
-
len = bits.length if len >= bits.length
|
107
|
-
value = bits.slice!(0, len).to_i(2).to_s(16).rjust(3,"0")
|
108
|
-
parts.push(value)
|
109
|
-
}
|
110
|
-
parts.join("/")
|
102
|
+
Murmur.map_filename(filename)
|
111
103
|
end
|
112
104
|
end
|
113
105
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'benchmark'
|
2
3
|
|
3
4
|
module Blobsterix
|
4
5
|
module Storage
|
@@ -15,29 +16,15 @@ module Blobsterix
|
|
15
16
|
Dir.entries(contents).include?(bucket) and File.directory?(File.join(contents,bucket))
|
16
17
|
end
|
17
18
|
|
18
|
-
def list(bucket="root")
|
19
|
+
def list(bucket="root", opts={})
|
19
20
|
if bucket =~ /root/
|
20
|
-
|
21
|
-
|
22
|
-
Dir.entries("#{contents}").each{|dir|
|
23
|
-
l.buckets << Bucket.new(dir, time_string_of(dir)) if File.directory? File.join("#{contents}",dir) and !(dir =='.' || dir == '..')
|
24
|
-
}
|
25
|
-
end
|
21
|
+
list_buckets
|
26
22
|
else
|
27
23
|
if bucket_exist(bucket)
|
28
24
|
b = Bucket.new(bucket, time_string_of(bucket))
|
29
|
-
|
25
|
+
b.marker = opts[:start_path] if opts[:start_path]
|
30
26
|
Blobsterix.wait_for(Proc.new {
|
31
|
-
|
32
|
-
b.contents << BucketEntry.new(file) do |entry|
|
33
|
-
meta = metaData(bucket, file)
|
34
|
-
entry.last_modified = meta.last_modified.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
35
|
-
entry.etag = meta.etag
|
36
|
-
entry.size = meta.size
|
37
|
-
entry.mimetype = meta.mimetype
|
38
|
-
entry.fullpath = contents(bucket, file).gsub("#{contents}/", "")
|
39
|
-
end
|
40
|
-
end
|
27
|
+
collect_bucket_entries(bucket, b, opts)
|
41
28
|
})
|
42
29
|
b
|
43
30
|
else
|
@@ -80,7 +67,7 @@ module Blobsterix
|
|
80
67
|
|
81
68
|
def delete(bucket)
|
82
69
|
logger.info "Storage: delete bucket #{contents(bucket)}"
|
83
|
-
FileUtils.rm_rf(contents(bucket)) if bucket_exist(bucket) &&
|
70
|
+
FileUtils.rm_rf(contents(bucket)) if bucket_exist(bucket) && bucket_empty?(bucket)
|
84
71
|
#Dir.rmdir(contents(bucket)) if bucket_exist(bucket) && bucket_files(bucket).empty?
|
85
72
|
end
|
86
73
|
|
@@ -92,6 +79,29 @@ module Blobsterix
|
|
92
79
|
end
|
93
80
|
|
94
81
|
private
|
82
|
+
def list_buckets
|
83
|
+
BucketList.new do |l|
|
84
|
+
Dir.entries("#{contents}").each{|dir|
|
85
|
+
l.buckets << Bucket.new(dir, time_string_of(dir)) if File.directory? File.join("#{contents}",dir) and !(dir =='.' || dir == '..')
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
def collect_bucket_entries(bucket, b, opts)
|
90
|
+
start_path = map_filename(opts[:start_path].to_s.gsub("/", "\\")) if opts[:start_path]
|
91
|
+
current_obj = Blobsterix::DirectoryList.each_limit(contents(bucket), :limit => 20, :start_path => start_path) do |path, file|
|
92
|
+
if file.to_s.end_with?(".meta")
|
93
|
+
false
|
94
|
+
else
|
95
|
+
b.contents << BucketEntry.create(file, metaData(bucket, file.to_s))
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
next_marker = current_obj.current_file.to_s.gsub("\\", "/")
|
100
|
+
if current_obj.next
|
101
|
+
b.next_marker=next_marker
|
102
|
+
b.truncated=true
|
103
|
+
end
|
104
|
+
end
|
95
105
|
def contents(bucket=nil, key=nil)
|
96
106
|
if bucket
|
97
107
|
key ? File.join(@contents, bucket, map_filename(key.gsub("/", "\\"))) : File.join(@contents, bucket)
|
@@ -101,29 +111,16 @@ module Blobsterix
|
|
101
111
|
end
|
102
112
|
|
103
113
|
def map_filename(filename)
|
104
|
-
|
105
|
-
bits = hash.to_s(2)
|
106
|
-
parts = []
|
107
|
-
6.times { |index|
|
108
|
-
len = 11
|
109
|
-
len = bits.length if len >= bits.length
|
110
|
-
value = bits.slice!(0, len).to_i(2).to_s(16).rjust(3,"0")
|
111
|
-
parts.push(value)
|
112
|
-
}
|
113
|
-
parts.push(filename)
|
114
|
-
parts.join("/")
|
114
|
+
Murmur.map_filename(filename, filename)
|
115
115
|
end
|
116
116
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
[]
|
125
|
-
end
|
126
|
-
})
|
117
|
+
def bucket_empty?(bucket)
|
118
|
+
empty = true
|
119
|
+
Blobsterix::DirectoryList.each(contents(bucket)) do
|
120
|
+
empty = false
|
121
|
+
break
|
122
|
+
end
|
123
|
+
empty
|
127
124
|
end
|
128
125
|
|
129
126
|
def metaData(bucket, key)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Blobsterix
|
2
2
|
module Storage
|
3
3
|
class Storage
|
4
|
-
def list(bucket="root")
|
4
|
+
def list(bucket="root", opts={})
|
5
5
|
Nokogiri::XML::Builder.new do |xml|
|
6
6
|
xml.Error "no such bucket"
|
7
7
|
end
|
@@ -12,7 +12,7 @@ module Blobsterix
|
|
12
12
|
def get(bucket, key)
|
13
13
|
Blobsterix::Storage::BlobMetaData.new
|
14
14
|
end
|
15
|
-
def put(bucket, key, value)
|
15
|
+
def put(bucket, key, value, opts={})
|
16
16
|
Blobsterix::Storage::BlobMetaData.new
|
17
17
|
end
|
18
18
|
def create(bucket)
|