blobsterix 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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,33 @@
1
+ module Blobsterix
2
+ module ConfigLoader
3
+ def load_blobsterix_config
4
+ require_storages
5
+ require_transformators
6
+ require_config
7
+ end
8
+
9
+ def require_config()
10
+ require Blobsterix.root.join("config.rb") if (File.exist?(Blobsterix.root.join("config.rb")))
11
+ end
12
+
13
+ def require_transformators()
14
+ trafo_dir = Blobsterix.root.join("transformators")
15
+ return if not File.exist?(trafo_dir)
16
+ Dir.entries(trafo_dir).each{|dir|
17
+ if !File.directory? File.join(trafo_dir,dir) and !(dir =='.' || dir == '..')
18
+ require "#{File.join(trafo_dir,dir)}"
19
+ end
20
+ }
21
+ end
22
+
23
+ def require_storages()
24
+ storages_dir = Blobsterix.root.join("storages")
25
+ return if not File.exist?(storages_dir)
26
+ Dir.entries(storages_dir).each{|dir|
27
+ if !File.directory? File.join(storages_dir,dir) and !(dir =='.' || dir == '..')
28
+ require "#{File.join(storages_dir,dir)}"
29
+ end
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,54 @@
1
+ module Blobsterix
2
+ module Http
3
+ class DataResponse
4
+ attr_reader :meta, :with_data, :etag, :env
5
+
6
+ def initialize(_meta, _with_data=true, _etag=nil, _env = nil)
7
+ @meta = _meta
8
+ @with_data = _with_data
9
+ @etag = _etag
10
+ @env = _env
11
+ end
12
+
13
+ def call(xfile=false, allow_chunks=true)
14
+ if not meta.valid
15
+ Http.NotFound()
16
+ elsif xfile and etag != meta.etag
17
+ [200, meta.header.merge({"X-Sendfile" => meta.path}), ""]
18
+ elsif etag != meta.etag
19
+ if env != nil && meta.size > 30000 && allow_chunks
20
+ chunkresponse
21
+ else
22
+ [200, meta.header, (with_data ? meta.data : "")]
23
+ end
24
+ else
25
+ [304, meta.header, ""]
26
+ end
27
+ end
28
+
29
+ private
30
+ def chunkresponse
31
+ f = File.open(meta.path)
32
+ EM.next_tick do
33
+ send_chunk(f)
34
+ end
35
+ [200, meta.header.merge(Goliath::Response::CHUNKED_STREAM_HEADERS), (with_data ? Goliath::Response::STREAMING : "")]
36
+ end
37
+
38
+ def send_chunk(file)
39
+ dat = file.read(10000)
40
+ again = if dat != nil
41
+ env.chunked_stream_send(dat)
42
+ true
43
+ else
44
+ file.close
45
+ env.chunked_stream_close
46
+ false
47
+ end
48
+ EM.next_tick do
49
+ send_chunk(file)
50
+ end if again
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ module Blobsterix
2
+ module Http
3
+ def self.renderer
4
+ @@renderer||=(Blobsterix.respond_to?(:env) && Blobsterix.env == :production) ? TemplateRenderer.new(binding) : ReloadTemplateRenderer.new(binding)
5
+ end
6
+ def self.error_object_binding(obj)
7
+ obj||={}
8
+ def obj.get_binding
9
+ binding
10
+ end
11
+ def obj.title
12
+ self[:title]
13
+ end
14
+ def obj.content
15
+ self[:content]
16
+ end
17
+ def obj.error_code
18
+ self[:error_code]
19
+ end
20
+ obj.get_binding
21
+ end
22
+ def self.NextApi(data="Not Found", content_type="txt")
23
+ [600, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
24
+ end
25
+ def self.NotFound(data="Not Found", content_type="html")
26
+ [404, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Found", :content=>data, :error_code => 404))]
27
+ end
28
+ def self.ServerError(data="Server Error", content_type="html")
29
+ [500, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Server Error", :content=>data, :error_code => 500))]
30
+ end
31
+ def self.NotAllowed(data="Not Allowed", content_type="html")
32
+ [403, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Allowed", :content=>data, :error_code => 403))]
33
+ end
34
+ def self.NotAuthorized(data="Not Authorized", content_type="html")
35
+ [401, {"Content-Type" => MimeMagic.by_extension(content_type).type}, renderer.render("error_page", error_object_binding(:title=>"Not Authorized", :content=>data, :error_code => 401))]
36
+ end
37
+ def self.OK(data="", content_type="txt")
38
+ [200, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
39
+ end
40
+ def self.Response(status_code=200, data="", content_type="txt")
41
+ [status_code, {"Content-Type" => MimeMagic.by_extension(content_type).type}, data]
42
+ end
43
+ def self.OK_no_data(data="", content_type="txt")
44
+ [204, {}, ""]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ module Blobsterix
2
+ module Logable
3
+ def logger
4
+ @logger ||= Blobsterix.logger
5
+ end
6
+
7
+ def logger=(_logger)
8
+ @logger=_logger
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,137 @@
1
+ #only usefull to hash strings
2
+ class Murmur
3
+ def self.force_overflow_signed(i)
4
+ force_overflow_unsigned(i + 2**31) - 2**31
5
+ end
6
+
7
+ def self.force_overflow_unsigned_16(i)
8
+ i % 2**16 # or equivalently: i & 0xffffffff
9
+ end
10
+
11
+ def self.force_overflow_unsigned(i)
12
+ i % 2**32 # or equivalently: i & 0xffffffff
13
+ end
14
+
15
+ def self.force_overflow_unsigned_64(i)
16
+ i % 2**64 # or equivalently: i & 0xffffffff
17
+ end
18
+
19
+ #64bit processors
20
+ def self.Hash64A (key)
21
+ len = key.size
22
+ seed = 11
23
+
24
+ m = 0xc6a4a7935bd1e995
25
+ r = 47
26
+
27
+ h = seed ^ len;
28
+
29
+ data = String.new(key)
30
+
31
+ while len >= 8
32
+ k = data.slice!(0..7).unpack("Q")[0]
33
+
34
+ k = force_overflow_unsigned_64(k * m)
35
+ k ^= k >> r
36
+ k = force_overflow_unsigned_64(k * m)
37
+
38
+ h ^= k;
39
+ h = force_overflow_unsigned_64(h * m)
40
+
41
+ len-=8
42
+ end
43
+
44
+ h ^= data.slice(6).to_i << 48 if len == 7
45
+ h ^= data.slice(5).to_i << 40 if len >= 6
46
+ h ^= data.slice(4).to_i << 32 if len >= 5
47
+ h ^= data.slice(3).to_i << 24 if len >= 4
48
+ h ^= data.slice(2).to_i << 16 if len >= 3
49
+ h ^= data.slice(1).to_i << 8 if len >= 2
50
+ h ^= data.slice(0).to_i if len >= 1
51
+
52
+ h = force_overflow_unsigned_64(h * m) if len
53
+
54
+ h ^= h >> r
55
+ h = force_overflow_unsigned_64(h * m)
56
+ h ^= h >> r
57
+
58
+ h
59
+ end
60
+
61
+ def self.get_num(num)
62
+ return num if num.class == Fixnum
63
+ num.to_s.unpack("C")[0]
64
+ end
65
+
66
+ #32bit processors
67
+ def self.Hash64B(key)
68
+ len = key.size
69
+ seed = 11
70
+
71
+ m = 0x5bd1e995 #1540483477
72
+ r = 24
73
+
74
+ h1 = seed ^ len
75
+ h2 = 0
76
+
77
+ data = String.new(key)#.force_encoding('ASCII-8BIT')
78
+
79
+ while len >= 8
80
+ k1 = data.slice!(0..3).unpack("I")[0]
81
+
82
+ k1 = force_overflow_unsigned(k1 * m)
83
+ k1 ^= k1 >> r
84
+
85
+ k1 = force_overflow_unsigned(k1 * m)
86
+ h1 = force_overflow_unsigned(h1 * m)
87
+
88
+ h1 ^= k1
89
+ len -= 4
90
+
91
+
92
+ k2 = data.slice!(0..3).unpack("I")[0]
93
+
94
+ k2 = force_overflow_unsigned(k2 * m)
95
+ k2 ^= k2 >> r
96
+
97
+ k2 = force_overflow_unsigned(k2 * m)
98
+ h2 = force_overflow_unsigned(h2 * m)
99
+
100
+ h2 ^= k2
101
+ len -= 4
102
+ end
103
+
104
+ if len >= 4
105
+ k1 = data.slice!(0..3).unpack("I")[0]
106
+
107
+ k1 = force_overflow_unsigned(k1 * m)
108
+ k1 ^= k1 >> r
109
+
110
+ k1 = force_overflow_unsigned(k1 * m)
111
+ h1 = force_overflow_unsigned(h1 * m)
112
+
113
+ h1 ^= k1
114
+ len -= 4
115
+ end
116
+
117
+ h2 ^= (get_num(data[2]) << 16) if len == 3
118
+ h2 ^= (get_num(data[1]) << 8).to_i if len >= 2
119
+ h2 ^= (get_num(data[0])) if len >= 1
120
+
121
+ h2 = force_overflow_unsigned(h2 * m) if len > 0
122
+
123
+ h1 ^= h2 >> 18
124
+ h1 = force_overflow_unsigned(h1 * m)
125
+
126
+ h2 ^= h1 >> 22
127
+ h2 = force_overflow_unsigned(h2 * m)
128
+
129
+ h1 ^= h2 >> 17
130
+ h1 = force_overflow_unsigned(h1 * m)
131
+
132
+ h = h1
133
+ h = (h << 32) | h2
134
+
135
+ h
136
+ end
137
+ end
@@ -0,0 +1,42 @@
1
+ module Blobsterix
2
+ module StatusInfo
3
+ def self.boot_up
4
+ @start_time=Time.now
5
+ end
6
+ boot_up
7
+
8
+ def self.uptime
9
+ Time.now-@start_time
10
+ end
11
+ def self.cache_hit
12
+ @cache_hit||=0
13
+ end
14
+ def self.cache_hit=(obj)
15
+ @cache_hit=obj
16
+ end
17
+ def self.cache_miss
18
+ @cache_miss||=0
19
+ end
20
+ def self.cache_miss=(obj)
21
+ @cache_miss=obj
22
+ end
23
+ def self.cache_error
24
+ @cache_error||=0
25
+ end
26
+ def self.cache_error=(obj)
27
+ @cache_error=obj
28
+ end
29
+ def self.cache_access
30
+ @cache_access||=0
31
+ end
32
+ def self.cache_access=(obj)
33
+ @cache_access=obj
34
+ end
35
+ def self.connections
36
+ @connections||=0
37
+ end
38
+ def self.connections=(obj)
39
+ @connections=obj
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ module Blobsterix
2
+ class ReloadTemplateRenderer
3
+ def initialize(binding_)
4
+ @binding = binding_
5
+ end
6
+
7
+ def render(template_name, bind=nil)
8
+ TemplateRenderer.new(bind||@binding).render(template_name)
9
+ end
10
+ end
11
+
12
+ class TemplateRenderer
13
+ def initialize(controller_binding_)
14
+ @controller_binding=controller_binding_
15
+ end
16
+
17
+ def render(template_name, bind=nil)
18
+ template(template_name).result(bind||controller_binding)
19
+ end
20
+
21
+ private
22
+
23
+ def controller_binding
24
+ @controller_binding
25
+ end
26
+
27
+ def template(template_name)
28
+ begin
29
+ templates[template_name]||=::ERB.new(File.read(Blobsterix.root.join("views", "#{template_name}.erb")))
30
+ rescue Errno::ENOENT => e
31
+ templates[template_name]||=::ERB.new(File.read(Blobsterix.root_gem.join("templates/views", "#{template_name}.erb")))
32
+ end
33
+ end
34
+
35
+ def templates
36
+ @templates||={}
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,138 @@
1
+ # Mime type detection
2
+ class MimeMagic
3
+ attr_reader :type, :mediatype, :subtype
4
+
5
+ # Mime type by type string
6
+ def initialize(type)
7
+ @type = type
8
+ @mediatype, @subtype = type.split('/', 2)
9
+ end
10
+
11
+ # Add custom mime type. Arguments:
12
+ # * <i>type</i>: Mime type
13
+ # * <i>options</i>: Options hash
14
+ #
15
+ # Option keys:
16
+ # * <i>:extensions</i>: String list or single string of file extensions
17
+ # * <i>:parents</i>: String list or single string of parent mime types
18
+ # * <i>:magic</i>: Mime magic specification
19
+ # * <i>:comment</i>: Comment string
20
+ def self.add(type, options)
21
+ extensions = [options[:extensions]].flatten.compact
22
+ TYPES[type] = [extensions,
23
+ [options[:parents]].flatten.compact,
24
+ options[:comment]]
25
+ extensions.each {|ext| EXTENSIONS[ext] = type }
26
+ MAGIC.unshift [type, options[:magic]] if options[:magic]
27
+ end
28
+
29
+ # Removes a mime type from the dictionary. You might want to do this if
30
+ # you're seeing impossible conflicts (for instance, application/x-gmc-link).
31
+ # * <i>type</i>: The mime type to remove. All associated extensions and magic are removed too.
32
+ def self.remove(type)
33
+ EXTENSIONS.delete_if {|ext, t| t == type }
34
+ MAGIC.delete_if {|t, m| t == type }
35
+ TYPES.delete(type)
36
+ end
37
+
38
+ # Returns true if type is a text format
39
+ def text?; mediatype == 'text' || child_of?('text/plain'); end
40
+
41
+ # Mediatype shortcuts
42
+ def image?; mediatype == 'image'; end
43
+ def audio?; mediatype == 'audio'; end
44
+ def video?; mediatype == 'video'; end
45
+
46
+ # Returns true if type is child of parent type
47
+ def child_of?(parent)
48
+ MimeMagic.child?(type, parent)
49
+ end
50
+
51
+ # Get string list of file extensions
52
+ def extensions
53
+ TYPES.key?(type) ? TYPES[type][0] : []
54
+ end
55
+
56
+ # Get mime comment
57
+ def comment
58
+ (TYPES.key?(type) ? TYPES[type][2] : nil).to_s
59
+ end
60
+
61
+ # Lookup mime type by file extension
62
+ def self.by_extension(ext)
63
+ ext = ext.to_s.downcase
64
+ mime = ext[0..0] == '.' ? EXTENSIONS[ext[1..-1]] : EXTENSIONS[ext]
65
+ mime && new(mime)
66
+ end
67
+
68
+ # Lookup mime type by filename
69
+ def self.by_path(path)
70
+ by_extension(File.extname(path))
71
+ end
72
+
73
+ # Lookup mime type by magic content analysis.
74
+ # This is a slow operation.
75
+ def self.by_magic(io)
76
+ mime =
77
+ unless io.respond_to?(:seek) && io.respond_to?(:read)
78
+ str = io.respond_to?(:read) ? io.read : io.to_s
79
+ str = str.force_encoding(Encoding::BINARY) if str.respond_to? :force_encoding
80
+ MAGIC.find {|type, matches| magic_match_str(str, matches) }
81
+ else
82
+ io.binmode
83
+ io.set_encoding(Encoding::BINARY) if io.respond_to?(:set_encoding)
84
+ MAGIC.find {|type, matches| magic_match_io(io, matches) }
85
+ end
86
+ mime && new(mime[0])
87
+ end
88
+
89
+ # Return type as string
90
+ def to_s
91
+ type
92
+ end
93
+
94
+ # Allow comparison with string
95
+ def eql?(other)
96
+ type == other.to_s
97
+ end
98
+
99
+ def hash
100
+ type.hash
101
+ end
102
+
103
+ alias == eql?
104
+
105
+ def self.child?(child, parent)
106
+ child == parent || TYPES.key?(child) && TYPES[child][1].any? {|p| child?(p, parent) }
107
+ end
108
+
109
+ def self.magic_match_io(io, matches)
110
+ matches.any? do |offset, value, children|
111
+ match =
112
+ if Range === offset
113
+ io.seek(offset.begin)
114
+ x = io.read(offset.end - offset.begin + value.bytesize)
115
+ x && x.include?(value)
116
+ else
117
+ io.seek(offset)
118
+ io.read(value.bytesize) == value
119
+ end
120
+ match && (!children || magic_match_io(io, children))
121
+ end
122
+ end
123
+
124
+ def self.magic_match_str(str, matches)
125
+ matches.any? do |offset, value, children|
126
+ match =
127
+ if Range === offset
128
+ x = str[offset.begin, offset.end - offset.begin + value.bytesize]
129
+ x && x.include?(value)
130
+ else
131
+ str[offset, value.bytesize] == value
132
+ end
133
+ match && (!children || magic_match_str(str, children))
134
+ end
135
+ end
136
+
137
+ private_class_method :magic_match_io, :magic_match_str
138
+ end