blobsterix 0.0.9

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.
Files changed (67) hide show
  1. data/.gitignore +27 -0
  2. data/CHANGELOG.txt +13 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +22 -0
  5. data/README.md +122 -0
  6. data/Rakefile +13 -0
  7. data/bin/blobsterix +152 -0
  8. data/bin/test +26 -0
  9. data/blobsterix.gemspec +39 -0
  10. data/config/lighttpd.conf +50 -0
  11. data/lib/blobsterix.rb +213 -0
  12. data/lib/blobsterix/blob/blob_api.rb +55 -0
  13. data/lib/blobsterix/blob/blob_url_helper.rb +55 -0
  14. data/lib/blobsterix/helper/accept_type.rb +62 -0
  15. data/lib/blobsterix/helper/blob_access.rb +73 -0
  16. data/lib/blobsterix/helper/config_loader.rb +33 -0
  17. data/lib/blobsterix/helper/data_response.rb +54 -0
  18. data/lib/blobsterix/helper/http.rb +47 -0
  19. data/lib/blobsterix/helper/logable.rb +11 -0
  20. data/lib/blobsterix/helper/murmur.rb +137 -0
  21. data/lib/blobsterix/helper/status_info.rb +42 -0
  22. data/lib/blobsterix/helper/template_renderer.rb +39 -0
  23. data/lib/blobsterix/mimemagic/magic.rb +138 -0
  24. data/lib/blobsterix/mimemagic/tables.rb +1770 -0
  25. data/lib/blobsterix/mimemagic/version.rb +5 -0
  26. data/lib/blobsterix/router/app_router.rb +134 -0
  27. data/lib/blobsterix/s3/s3_api.rb +92 -0
  28. data/lib/blobsterix/s3/s3_url_helper.rb +93 -0
  29. data/lib/blobsterix/service.rb +34 -0
  30. data/lib/blobsterix/status/status_api.rb +62 -0
  31. data/lib/blobsterix/status/status_url_helper.rb +11 -0
  32. data/lib/blobsterix/storage/blob_meta_data.rb +60 -0
  33. data/lib/blobsterix/storage/bucket.rb +36 -0
  34. data/lib/blobsterix/storage/bucket_entry.rb +29 -0
  35. data/lib/blobsterix/storage/bucket_list.rb +26 -0
  36. data/lib/blobsterix/storage/cache.rb +90 -0
  37. data/lib/blobsterix/storage/file_system.rb +132 -0
  38. data/lib/blobsterix/storage/file_system_meta_data.rb +136 -0
  39. data/lib/blobsterix/storage/storage.rb +30 -0
  40. data/lib/blobsterix/transformation/image_transformation.rb +439 -0
  41. data/lib/blobsterix/transformation/transformation.rb +30 -0
  42. data/lib/blobsterix/transformation/transformation_chain.rb +78 -0
  43. data/lib/blobsterix/transformation/transformation_manager.rb +115 -0
  44. data/lib/blobsterix/version.rb +3 -0
  45. data/scripts/download.rb +30 -0
  46. data/scripts/test +6 -0
  47. data/spec/lib/blob/blob_api_spec.rb +81 -0
  48. data/spec/lib/helper/blob_access_spec.rb +72 -0
  49. data/spec/lib/s3/s3_api_spec.rb +183 -0
  50. data/spec/lib/service_spec.rb +12 -0
  51. data/spec/lib/status/status_api_spec.rb +42 -0
  52. data/spec/lib/storage/cache_spec.rb +135 -0
  53. data/spec/lib/storage/file_system_spec.rb +84 -0
  54. data/spec/spec_helper.rb +139 -0
  55. data/templates/app/Gemfile +12 -0
  56. data/templates/app/Rakefile +7 -0
  57. data/templates/app/config.rb +61 -0
  58. data/templates/app/config/environments/development.rb +40 -0
  59. data/templates/app/config/environments/production.rb +40 -0
  60. data/templates/app/storages/.keep +0 -0
  61. data/templates/app/transformators/.keep +0 -0
  62. data/templates/app/views/.keep +0 -0
  63. data/templates/storage_template.rb +30 -0
  64. data/templates/transformation_template.rb +41 -0
  65. data/templates/views/error_page.erb +18 -0
  66. data/templates/views/status_page.erb +31 -0
  67. metadata +325 -0
@@ -0,0 +1,5 @@
1
+ class MimeMagic
2
+ # MimeMagic version string
3
+ # @api public
4
+ VERSION = '0.2.1'
5
+ end
@@ -0,0 +1,134 @@
1
+ module Blobsterix
2
+ module Jsonizer
3
+ def json_var(*var_names)
4
+ @json_vars = (@json_vars||[])+var_names.flatten
5
+ end
6
+
7
+ def json_vars
8
+ @json_vars||= []
9
+ end
10
+ end
11
+
12
+ class AppRouterBase
13
+
14
+ extend Jsonizer
15
+ include Logable
16
+
17
+ #attr_reader :logger
18
+ attr_accessor :env
19
+
20
+ def initialize(env)
21
+ @env = env
22
+ #@logger = env["rack.logger"]
23
+ end
24
+
25
+ def storage
26
+ @storage ||= Blobsterix.storage
27
+ end
28
+
29
+ def cache
30
+ @cache ||= Blobsterix.cache
31
+ end
32
+
33
+ def transformation
34
+ @transformation ||= Blobsterix.transformation
35
+ end
36
+
37
+ def next_api
38
+ Http.NextApi
39
+ end
40
+
41
+ def renderer
42
+ @@renderer||=(Blobsterix.respond_to?(:env) && Blobsterix.env == :production) ? TemplateRenderer.new(binding) : ReloadTemplateRenderer.new(binding)
43
+ end
44
+
45
+ def render(template_name, status_code=200, bind=nil)
46
+ begin
47
+ Http.Response(status_code, renderer.render(template_name, bind||binding), "html")
48
+ rescue Errno::ENOENT => e
49
+ Http.NotFound
50
+ end
51
+ end
52
+
53
+ def render_json(obj=nil)
54
+ Http.OK (obj||self).to_json, "json"
55
+ end
56
+
57
+ def render_xml(obj=nil)
58
+ obj = Nokogiri::XML::Builder.new do |xml|
59
+ yield xml
60
+ end if block_given?
61
+ Http.OK (obj||self).to_xml, "xml"
62
+ end
63
+
64
+ def to_json
65
+ stuff = Hash.new
66
+ self.class.json_vars.each{|var_name|
67
+ stuff[var_name.to_sym]=send(var_name) if respond_to?(var_name)
68
+ }
69
+ stuff.to_json
70
+ end
71
+ def to_xml()
72
+ xml = Nokogiri::XML::Builder.new do |xml|
73
+ xml.BlobsterixStatus() {
74
+ self.class.json_vars.each{|var_name|
75
+ var = send(var_name)
76
+ var = var.to_xml if var.respond_to?(:to_xml)
77
+ xml.send(var_name, var) if respond_to?(var_name)
78
+ }
79
+ }
80
+ end
81
+ xml.to_xml
82
+ end
83
+
84
+ def self.options(opt)
85
+ opt = {:function => opt.to_sym} if opt.class != Hash
86
+ {:controller => self.name, :function => :call}.merge(opt)
87
+ end
88
+
89
+ def self.get(path, opt = {})
90
+ path = Journey::Path::Pattern.new path
91
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "GET"}, {})
92
+ end
93
+
94
+ def self.post(path, opt = {})
95
+ path = Journey::Path::Pattern.new path
96
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "POST"}, {})
97
+ end
98
+
99
+ def self.put(path, opt = {})
100
+ path = Journey::Path::Pattern.new path
101
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "PUT"}, {})
102
+ end
103
+
104
+ def self.delete(path, opt = {})
105
+ path = Journey::Path::Pattern.new path
106
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "DELETE"}, {})
107
+ end
108
+
109
+ def self.head(path, opt = {})
110
+ path = Journey::Path::Pattern.new path
111
+ router.routes.add_route(lambda{|env| call_controller(options(opt), env)}, path, {:request_method => "HEAD"}, {})
112
+ end
113
+
114
+ def self.call(env)
115
+ Blobsterix::StatusInfo.connections+=1
116
+ result=router.call(env)
117
+ Blobsterix::StatusInfo.connections-=1
118
+ result
119
+ end
120
+
121
+ def self.call_controller(options, env)
122
+ options[:controller].respond_to?(options[:function]) ? options[:controller].send(options[:function], env) : Blobsterix.const_get(options[:controller]).new(env).send(options[:function])
123
+ end
124
+
125
+ private
126
+ def self.routes()
127
+ (@@routes ||= {})[self.name] ||= Journey::Routes.new
128
+ end
129
+
130
+ def self.router()
131
+ (@@router ||= {})[self.name] ||= Journey::Router.new routes, {}
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,92 @@
1
+ module Blobsterix
2
+ class S3Api < AppRouterBase
3
+ include S3UrlHelper
4
+
5
+ get "/", :list_buckets
6
+
7
+ get "/*bucket_or_file.:format", :get_file
8
+ get "/*bucket_or_file", :get_file
9
+
10
+ head "/*bucket_or_file.:format", :get_file_head
11
+ head "/*bucket_or_file", :get_file_head
12
+
13
+ put "/", :create_bucket
14
+
15
+ put "/*file.:format", :upload_data
16
+ put "/*file", :upload_data
17
+
18
+ delete "/", :delete_bucket
19
+ delete "/*file.:format", :delete_file
20
+ delete "/*file", :delete_file
21
+
22
+ get "*any", :next_api
23
+ put "*any", :next_api
24
+ delete "*any", :next_api
25
+ head "*any", :next_api
26
+ post "*any", :next_api
27
+
28
+ private
29
+ def list_buckets
30
+ Blobsterix.event("s3_api.list_bucket",:bucket => bucket)
31
+ Http.OK storage.list(bucket).to_xml, "xml"
32
+ end
33
+
34
+ def get_file(send_with_data=true)
35
+ return Http.NotFound if favicon
36
+
37
+ if bucket?
38
+ if meta = storage.get(bucket, file)
39
+ send_with_data ? meta.response(true, env["HTTP_IF_NONE_MATCH"], env, env["HTTP_X_FILE"] === "yes", false) : meta.response(false)
40
+ else
41
+ Http.NotFound
42
+ end
43
+ else
44
+ Http.OK storage.list(bucket).to_xml, "xml"
45
+ end
46
+ end
47
+
48
+ def get_file_head
49
+ #TODO: add event?
50
+ get_file(false)
51
+ end
52
+
53
+ def create_bucket
54
+ Blobsterix.event("s3_api.upload",:bucket => bucket)
55
+ Http.OK storage.create(bucket), "xml"
56
+ end
57
+
58
+ def upload_data
59
+ source = cached_upload
60
+ accept = AcceptType.new("*/*")#source.accept_type()
61
+
62
+ trafo_current = trafo(trafo_string)
63
+ file_current = file
64
+ bucket_current = bucket
65
+ Blobsterix.event("s3_api.upload", :bucket => bucket_current,
66
+ :file => file_current, :accept_type => accept.type, :trafo => trafo_current)
67
+ blob_access=BlobAccess.new(:source => source, :bucket => bucket_current, :id => file_current, :accept_type => accept, :trafo => trafo_current)
68
+ data = transformation.run(blob_access)
69
+ cached_upload_clear
70
+ storage.put(bucket_current, file_current, data).response(false)
71
+ end
72
+
73
+ def delete_bucket
74
+ Blobsterix.event("s3_api.delete_bucket", :bucket => bucket)
75
+
76
+ if bucket?
77
+ Http.OK_no_data storage.delete(bucket), "xml"
78
+ else
79
+ Http.NotFound "no such bucket"
80
+ end
81
+ end
82
+
83
+ def delete_file
84
+ Blobsterix.event("s3_api.delete_file", :bucket => bucket,:file => file)
85
+ if bucket?
86
+ Http.OK_no_data storage.delete_key(bucket, file), "xml"
87
+ else
88
+ Http.NotFound "no such bucket"
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ module Blobsterix
2
+ module S3UrlHelper
3
+ HOST_PATH = /(\w+)(\.s3)?\.\w+\.\w+/
4
+ def bucket_matcher(str)
5
+ if str.include?("s3")
6
+ str.match(/(\w+)\.s3\.\w+\.\w+/)
7
+ else
8
+ str.match(/(\w+)\.\w+\.\w+/)
9
+ end
10
+ end
11
+
12
+ def favicon
13
+ @favicon ||= file.match /favicon/
14
+ end
15
+
16
+ def cache_upload
17
+ cache.put(cache_upload_key, env['rack.input'].read)
18
+ end
19
+
20
+ def cached_upload
21
+ cache_upload if not cache.exists?(cache_upload_key)
22
+ cache.get(cache_upload_key)
23
+ end
24
+
25
+ def cached_upload_clear
26
+ cache.delete(cache_upload_key)
27
+ end
28
+
29
+ def cache_upload_key
30
+ #@cache_upload_key ||= "upload/"+bucket.gsub("/", "_")+"_"+file.gsub("/", "_")
31
+ @cache_upload_key ||= Blobsterix::BlobAccess.new(:bucket => bucket, :id => "upload_#{file.gsub("/", "_")}")
32
+ end
33
+
34
+ def trafo_string
35
+ @trafo ||= env["HTTP_X_AMZ_META_TRAFO"] || ""
36
+ end
37
+
38
+ #TransformationCommand
39
+ def trafo(trafo_s='')
40
+ trafo_a = []
41
+ trafo_s.split(",").each{|command|
42
+ parts = command.split("_")
43
+ key = parts.delete_at(0)
44
+ trafo_a << [key, parts.join("_")]
45
+ }
46
+ trafo_a
47
+ end
48
+
49
+ def bucket
50
+ host = bucket_matcher(env['HTTP_HOST'])
51
+ if host
52
+ host[1]
53
+ elsif (env[nil] && env[nil][:bucket])
54
+ env[nil][:bucket]
55
+ elsif (env[nil] && env[nil][:bucket_or_file])
56
+ if env[nil][:bucket_or_file].include?("/")
57
+ env[nil][:bucket_or_file].split("/")[0]
58
+ else
59
+ env[nil][:bucket_or_file]
60
+ end
61
+ else
62
+ "root"
63
+ end
64
+ end
65
+
66
+ def bucket?
67
+ host = bucket_matcher(env['HTTP_HOST'])
68
+ host || env[nil][:bucket] || included_bucket
69
+ end
70
+
71
+ def format
72
+ @format ||= env[nil][:format]
73
+ end
74
+
75
+ def included_bucket
76
+ if env[nil][:bucket_or_file] && env[nil][:bucket_or_file].include?("/")
77
+ env[nil][:bucket] = env[nil][:bucket_or_file].split("/")[0]
78
+ env[nil][:bucket_or_file] = env[nil][:bucket_or_file].gsub("#{env[nil][:bucket]}/", "")
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ def file
86
+ if format
87
+ [env[nil][:file] || env[nil][:bucket_or_file] || "", format].join(".")
88
+ else
89
+ env[nil][:file] || env[nil][:bucket_or_file] || ""
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,34 @@
1
+ module Blobsterix
2
+ class Service < Goliath::API
3
+ use Goliath::Rack::Params
4
+ =begin
5
+ def on_headers(env, headers)
6
+ env.logger.info 'received headers: ' + headers.inspect
7
+ env['async-headers'] = headers
8
+ end
9
+
10
+ def on_body(env, data)
11
+ env.logger.info 'received data: ' + data
12
+ (env['async-body'] ||= '') << data
13
+ end
14
+
15
+ def on_close(env)
16
+ env.logger.info 'closing connection'
17
+ end
18
+ =end
19
+ def response(env)
20
+ call_stack(env, BlobApi, StatusApi, S3Api)
21
+ end
22
+
23
+ def call_stack(env, *apis)
24
+ last_answer = [404,{}, ""]
25
+ apis.each do |api|
26
+ last_answer = api.call(env)
27
+ if last_answer[0] != 600
28
+ return last_answer
29
+ end
30
+ end
31
+ last_answer[0] != 600 ? last_answer : [404,{}, ""]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ module Blobsterix
2
+ class StatusApi < AppRouterBase
3
+ include StatusUrlHelper
4
+
5
+ get "/status(.:format)", :status
6
+
7
+ get "*any", :next_api
8
+ put "*any", :next_api
9
+ delete "*any", :next_api
10
+ head "*any", :next_api
11
+ post "*any", :next_api
12
+
13
+ json_var :cache_hits, :cache_misses, :cache_errors, :cache_accesses, :connections, :cache_hit_rate, :ram_usage, :uptime
14
+
15
+ def status
16
+ case format
17
+ when :json
18
+ render_json
19
+ when :xml
20
+ render_xml
21
+ else
22
+ render "status_page"
23
+ end
24
+ end
25
+
26
+ def ram_usage
27
+ `pmap #{Process.pid} | tail -1`[10,40].strip
28
+ end
29
+
30
+ def uptime
31
+ @uptime||=StatusInfo.uptime
32
+ end
33
+
34
+ def cache_hits
35
+ @cache_hits||=StatusInfo.cache_hit
36
+ end
37
+
38
+ def cache_hit_rate
39
+ if cache_hits > 0 && cache_accesses > 0
40
+ cache_hits.to_f/cache_accesses.to_f
41
+ else
42
+ 1.to_f
43
+ end
44
+ end
45
+
46
+ def cache_misses
47
+ @cache_misses||=StatusInfo.cache_miss
48
+ end
49
+
50
+ def cache_errors
51
+ @cache_errors||=StatusInfo.cache_error
52
+ end
53
+
54
+ def cache_accesses
55
+ @cache_accesses||=StatusInfo.cache_access
56
+ end
57
+
58
+ def connections
59
+ @connections||=StatusInfo.connections
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ module Blobsterix
2
+ module StatusUrlHelper
3
+ def format
4
+ if env[nil] && env[nil][:format]
5
+ env[nil][:format].to_sym
6
+ else
7
+ :html
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,60 @@
1
+ module Blobsterix
2
+ module Storage
3
+ class BlobMetaData
4
+ def check(key)
5
+ false
6
+ end
7
+ def etag
8
+ ""
9
+ end
10
+ def read
11
+ data()
12
+ end
13
+ def data
14
+ ""
15
+ end
16
+ def path
17
+ ""
18
+ end
19
+ def last_modified
20
+ end
21
+ def last_accessed
22
+ end
23
+ def payload
24
+ {}
25
+ end
26
+ def header
27
+ {}
28
+ end
29
+ def mimetype
30
+ "*/*"
31
+ end
32
+ def mediatype
33
+ "*"
34
+ end
35
+ def size
36
+ 0
37
+ end
38
+ def accept_type
39
+ AcceptType.new
40
+ end
41
+ def valid
42
+ false
43
+ end
44
+ def valid?
45
+ valid
46
+ end
47
+ def write
48
+ if block_given?
49
+ #should yield file
50
+ end
51
+ self
52
+ end
53
+ def delete
54
+ end
55
+ def response(with_data=true, _etag=nil, env = nil, xfile = false, allow_chunks=true)
56
+ Blobsterix::Http::DataResponse.new(self, with_data, _etag, env).call(xfile, allow_chunks)
57
+ end
58
+ end
59
+ end
60
+ end