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,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