blobsterix 0.0.14 → 0.0.19
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.
- 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)
|