strokedb 0.0.2.1 → 0.0.2.2

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 (192) hide show
  1. data/README +18 -20
  2. data/bench.html +4001 -0
  3. data/bin/strokedb +14 -0
  4. data/examples/movies.rb +105 -0
  5. data/examples/movies2.rb +97 -0
  6. data/examples/strokewiki/README +28 -0
  7. data/examples/strokewiki/view/edit.xhtml +27 -0
  8. data/examples/strokewiki/view/new.xhtml +26 -0
  9. data/examples/strokewiki/view/pages.xhtml +27 -0
  10. data/examples/strokewiki/view/show.xhtml +40 -0
  11. data/examples/strokewiki/view/versions.xhtml +25 -0
  12. data/examples/strokewiki/wiki.rb +106 -0
  13. data/examples/todo.rb +92 -0
  14. data/lib/strokedb.rb +85 -0
  15. data/lib/{config → strokedb}/config.rb +14 -9
  16. data/lib/strokedb/console.rb +87 -0
  17. data/lib/strokedb/core_ext.rb +10 -0
  18. data/lib/{util/ext → strokedb/core_ext}/blank.rb +1 -1
  19. data/lib/{util/ext → strokedb/core_ext}/enumerable.rb +0 -0
  20. data/lib/{util/ext → strokedb/core_ext}/fixnum.rb +0 -0
  21. data/lib/strokedb/core_ext/float.rb +4 -0
  22. data/lib/{util/ext → strokedb/core_ext}/hash.rb +0 -0
  23. data/lib/strokedb/core_ext/infinity.rb +33 -0
  24. data/lib/strokedb/core_ext/kernel.rb +41 -0
  25. data/lib/strokedb/core_ext/object.rb +16 -0
  26. data/lib/{util/ext → strokedb/core_ext}/string.rb +28 -1
  27. data/lib/strokedb/core_ext/symbol.rb +13 -0
  28. data/lib/strokedb/data_structures.rb +5 -0
  29. data/lib/strokedb/data_structures/chunked_skiplist.rb +123 -0
  30. data/lib/{data_structures → strokedb/data_structures}/inverted_list.rb +0 -0
  31. data/lib/{data_structures → strokedb/data_structures}/point_query.rb +0 -0
  32. data/lib/strokedb/data_structures/simple_skiplist.rb +350 -0
  33. data/lib/{data_structures → strokedb/data_structures}/skiplist.rb +1 -1
  34. data/lib/{document → strokedb}/document.rb +180 -71
  35. data/lib/{document → strokedb/document}/callback.rb +0 -0
  36. data/lib/{document → strokedb/document}/delete.rb +2 -2
  37. data/lib/strokedb/document/dsl.rb +4 -0
  38. data/lib/{document → strokedb/document/dsl}/associations.rb +0 -0
  39. data/lib/{document → strokedb/document/dsl}/coercions.rb +0 -0
  40. data/lib/strokedb/document/dsl/meta_dsl.rb +7 -0
  41. data/lib/{document → strokedb/document/dsl}/validations.rb +26 -21
  42. data/lib/{document → strokedb/document/dsl}/virtualize.rb +0 -0
  43. data/lib/{document → strokedb/document}/meta.rb +92 -29
  44. data/lib/{document → strokedb/document}/slot.rb +17 -5
  45. data/lib/{document → strokedb/document}/util.rb +0 -0
  46. data/lib/{document → strokedb/document}/versions.rb +2 -2
  47. data/lib/strokedb/index.rb +2 -0
  48. data/lib/strokedb/nsurl.rb +24 -0
  49. data/lib/strokedb/store.rb +149 -0
  50. data/lib/strokedb/stores.rb +6 -0
  51. data/lib/{stores → strokedb/stores}/chainable_storage.rb +20 -14
  52. data/lib/strokedb/stores/file_storage.rb +118 -0
  53. data/lib/{stores/inverted_list_index → strokedb/stores}/inverted_list_file_storage.rb +50 -0
  54. data/lib/strokedb/stores/memory_storage.rb +80 -0
  55. data/lib/{stores → strokedb/stores}/remote_store.rb +10 -4
  56. data/lib/strokedb/sync.rb +4 -0
  57. data/lib/{sync → strokedb/sync}/chain_sync.rb +0 -0
  58. data/lib/{sync → strokedb/sync}/diff.rb +12 -1
  59. data/lib/{sync/stroke_diff → strokedb/sync/diff}/array.rb +1 -1
  60. data/lib/{sync/stroke_diff → strokedb/sync/diff}/default.rb +0 -0
  61. data/lib/{sync/stroke_diff → strokedb/sync/diff}/hash.rb +1 -1
  62. data/lib/{sync/stroke_diff → strokedb/sync/diff}/string.rb +1 -1
  63. data/lib/{sync → strokedb/sync}/lamport_timestamp.rb +0 -0
  64. data/lib/{sync → strokedb/sync}/store_sync.rb +15 -7
  65. data/lib/strokedb/transaction.rb +78 -0
  66. data/lib/{util → strokedb}/util.rb +14 -7
  67. data/lib/strokedb/util/attach_dsl.rb +29 -0
  68. data/lib/{util → strokedb/util}/blankslate.rb +0 -0
  69. data/lib/strokedb/util/class_optimization.rb +93 -0
  70. data/lib/{util → strokedb/util}/inflect.rb +0 -0
  71. data/lib/strokedb/util/java_util.rb +13 -0
  72. data/lib/{util → strokedb/util}/lazy_array.rb +0 -0
  73. data/lib/{util → strokedb/util}/lazy_mapping_array.rb +4 -0
  74. data/lib/{util → strokedb/util}/lazy_mapping_hash.rb +0 -0
  75. data/lib/{util → strokedb/util}/serialization.rb +21 -0
  76. data/lib/strokedb/util/uuid.rb +159 -0
  77. data/lib/{util → strokedb/util}/xml.rb +0 -0
  78. data/lib/{view → strokedb}/view.rb +2 -2
  79. data/lib/strokedb/volumes.rb +5 -0
  80. data/lib/strokedb/volumes/archive_volume.rb +165 -0
  81. data/lib/strokedb/volumes/block_volume.rb +169 -0
  82. data/lib/strokedb/volumes/distributed_pointer.rb +43 -0
  83. data/lib/strokedb/volumes/fixed_length_skiplist_volume.rb +109 -0
  84. data/lib/strokedb/volumes/map_volume.rb +268 -0
  85. data/meta/MANIFEST +175 -0
  86. data/script/console +2 -70
  87. data/spec/integration/remote_store_spec.rb +70 -0
  88. data/spec/integration/search_spec.rb +76 -0
  89. data/spec/integration/spec_helper.rb +1 -0
  90. data/spec/lib/spec_helper.rb +1 -0
  91. data/spec/lib/strokedb/config_spec.rb +250 -0
  92. data/spec/lib/strokedb/core_ext/blank_spec.rb +20 -0
  93. data/spec/lib/strokedb/core_ext/extract_spec.rb +42 -0
  94. data/spec/lib/strokedb/core_ext/float_spec.rb +62 -0
  95. data/spec/lib/strokedb/core_ext/infinity_spec.rb +40 -0
  96. data/spec/lib/strokedb/core_ext/spec_helper.rb +1 -0
  97. data/spec/lib/strokedb/core_ext/string_spec.rb +25 -0
  98. data/spec/lib/strokedb/core_ext/symbol_spec.rb +8 -0
  99. data/spec/lib/strokedb/data_structures/chunked_skiplist_spec.rb +144 -0
  100. data/spec/lib/strokedb/data_structures/inverted_list_spec.rb +172 -0
  101. data/spec/lib/strokedb/data_structures/simple_skiplist_spec.rb +200 -0
  102. data/spec/lib/strokedb/data_structures/skiplist_spec.rb +253 -0
  103. data/spec/lib/strokedb/data_structures/spec_helper.rb +1 -0
  104. data/spec/lib/strokedb/document/associations_spec.rb +319 -0
  105. data/spec/lib/strokedb/document/callbacks_spec.rb +134 -0
  106. data/spec/lib/strokedb/document/coercions_spec.rb +110 -0
  107. data/spec/lib/strokedb/document/document_spec.rb +1063 -0
  108. data/spec/lib/strokedb/document/meta_meta_spec.rb +30 -0
  109. data/spec/lib/strokedb/document/meta_spec.rb +435 -0
  110. data/spec/lib/strokedb/document/metaslot_spec.rb +43 -0
  111. data/spec/lib/strokedb/document/slot_spec.rb +130 -0
  112. data/spec/lib/strokedb/document/spec_helper.rb +1 -0
  113. data/spec/lib/strokedb/document/validations_spec.rb +1081 -0
  114. data/spec/lib/strokedb/document/virtualize_spec.rb +80 -0
  115. data/spec/lib/strokedb/nsurl_spec.rb +73 -0
  116. data/spec/lib/strokedb/spec_helper.rb +1 -0
  117. data/spec/lib/strokedb/stores/chained_storages_spec.rb +116 -0
  118. data/spec/lib/strokedb/stores/spec_helper.rb +1 -0
  119. data/spec/lib/strokedb/stores/store_spec.rb +201 -0
  120. data/spec/lib/strokedb/stores/transaction_spec.rb +107 -0
  121. data/spec/lib/strokedb/sync/chain_sync_spec.rb +43 -0
  122. data/spec/lib/strokedb/sync/diff_spec.rb +111 -0
  123. data/spec/lib/strokedb/sync/lamport_timestamp_spec.rb +174 -0
  124. data/spec/lib/strokedb/sync/slot_diff_spec.rb +164 -0
  125. data/spec/lib/strokedb/sync/spec_helper.rb +1 -0
  126. data/spec/lib/strokedb/sync/store_sync_spec.rb +181 -0
  127. data/spec/lib/strokedb/sync/stroke_diff/array_spec.rb +97 -0
  128. data/spec/lib/strokedb/sync/stroke_diff/complex_spec.rb +58 -0
  129. data/spec/lib/strokedb/sync/stroke_diff/hash_spec.rb +144 -0
  130. data/spec/lib/strokedb/sync/stroke_diff/scalar_spec.rb +23 -0
  131. data/spec/lib/strokedb/sync/stroke_diff/spec_helper.rb +25 -0
  132. data/spec/lib/strokedb/sync/stroke_diff/string_spec.rb +61 -0
  133. data/spec/lib/strokedb/util/attach_dsl_spec.rb +45 -0
  134. data/spec/lib/strokedb/util/inflect_spec.rb +14 -0
  135. data/spec/lib/strokedb/util/lazy_array_spec.rb +157 -0
  136. data/spec/lib/strokedb/util/lazy_mapping_array_spec.rb +174 -0
  137. data/spec/lib/strokedb/util/lazy_mapping_hash_spec.rb +92 -0
  138. data/spec/lib/strokedb/util/spec_helper.rb +1 -0
  139. data/spec/lib/strokedb/util/uuid_spec.rb +46 -0
  140. data/spec/lib/strokedb/view_spec.rb +228 -0
  141. data/spec/lib/strokedb/volumes/archive_volume_spec.rb +105 -0
  142. data/spec/lib/strokedb/volumes/block_volume_spec.rb +100 -0
  143. data/spec/lib/strokedb/volumes/distributed_pointer_spec.rb +14 -0
  144. data/spec/lib/strokedb/volumes/fixed_length_skiplist_volume_spec.rb +177 -0
  145. data/spec/lib/strokedb/volumes/map_volume_spec.rb +172 -0
  146. data/spec/lib/strokedb/volumes/spec_helper.rb +1 -0
  147. data/spec/regression/docref_spec.rb +94 -0
  148. data/spec/regression/meta_spec.rb +23 -0
  149. data/spec/regression/spec_helper.rb +1 -0
  150. data/spec/regression/sync_spec.rb +36 -0
  151. data/spec/spec.opts +7 -0
  152. data/spec/spec_helper.rb +37 -0
  153. data/spec/temp/storages/TIMESTAMP +1 -0
  154. data/spec/temp/storages/UUID +1 -0
  155. data/spec/temp/storages/database-sync/TIMESTAMP +1 -0
  156. data/spec/temp/storages/database-sync/UUID +1 -0
  157. data/spec/temp/storages/database-sync/config +1 -0
  158. data/spec/temp/storages/database-sync/file/LAST +1 -0
  159. data/spec/temp/storages/database-sync/file/bd/f6/bdf675e5-8a7b-494e-97f2-f74a14ccd95d.av +0 -0
  160. data/spec/temp/storages/database-sync/file/uindex.wal +0 -0
  161. data/spec/temp/storages/database-sync/inverted_list_file/INVERTED_INDEX +1 -0
  162. data/spec/temp/storages/inverted_list_storage/INVERTED_INDEX +0 -0
  163. data/strokedb.gemspec +120 -0
  164. data/task/benchmark.task +9 -0
  165. data/task/ditz.task +30 -0
  166. data/task/echoe.rb +17 -0
  167. data/task/rcov.task +50 -0
  168. data/task/rdoc.task +10 -0
  169. data/task/rspec.task +0 -0
  170. data/vendor/java_inline.rb +106 -0
  171. data/vendor/rbmodexcl/mrimodexcl.rb +82 -0
  172. data/vendor/rbmodexcl/rbmodexcl.rb +5 -0
  173. data/vendor/rbmodexcl/rbxmodexcl.rb +48 -0
  174. data/vendor/rbmodexcl/spec/unextend_spec.rb +50 -0
  175. data/vendor/rbmodexcl/spec/uninclude_spec.rb +26 -0
  176. metadata +271 -79
  177. data/CONTRIBUTORS +0 -7
  178. data/CREDITS +0 -13
  179. data/bin/sdbc +0 -2
  180. data/lib/init.rb +0 -57
  181. data/lib/stores/inverted_list_index/inverted_list_index.rb +0 -49
  182. data/lib/stores/skiplist_store/chunk.rb +0 -119
  183. data/lib/stores/skiplist_store/chunk_storage.rb +0 -21
  184. data/lib/stores/skiplist_store/file_chunk_storage.rb +0 -44
  185. data/lib/stores/skiplist_store/memory_chunk_storage.rb +0 -37
  186. data/lib/stores/skiplist_store/skiplist_store.rb +0 -217
  187. data/lib/stores/store.rb +0 -5
  188. data/lib/sync/stroke_diff/stroke_diff.rb +0 -9
  189. data/lib/util/ext/object.rb +0 -8
  190. data/lib/util/java_util.rb +0 -9
  191. data/lib/util/trigger_partition.rb +0 -136
  192. data/strokedb.rb +0 -75
@@ -1,4 +1,25 @@
1
1
  module StrokeDB
2
+
3
+ class ::Array
4
+ def to_raw
5
+ map do |v|
6
+ v.respond_to?(:to_raw) ? v.to_raw : v
7
+ end
8
+ end
9
+ end
10
+
11
+ class ::Hash
12
+ def to_raw
13
+ raw_hash = {}
14
+ map do |k,v|
15
+ _k = k.respond_to?(:to_raw) ? k.to_raw : k
16
+ _v = v.respond_to?(:to_raw) ? v.to_raw : v
17
+ raw_hash[_k] = _v
18
+ end
19
+ raw_hash
20
+ end
21
+ end
22
+
2
23
  module JsonSerializationMethod
3
24
  def serialize(x)
4
25
  x.to_json
@@ -0,0 +1,159 @@
1
+ begin
2
+ module StrokeDB; end
3
+ class FastUUID
4
+ require 'rubygems'
5
+ require 'inline'
6
+
7
+ C_FLAGS = `uuid-config --cflags`.chomp
8
+ LD_FLAGS = `uuid-config --ldflags --libs`.chomp
9
+
10
+ inline(:C) do |builder|
11
+ builder.add_compile_flags C_FLAGS
12
+ builder.add_link_flags LD_FLAGS
13
+ builder.prefix %{
14
+ #include "dlfcn.h"
15
+ #include "uuid.h"
16
+
17
+ #define PROLOG(size) \\
18
+ uuid_t *uuid; \\
19
+ char str[size], *strptr = str; \\
20
+ size_t len = sizeof(str); \\
21
+ uuid_create(&uuid)
22
+
23
+ #define EPILOG(format, size) \\
24
+ uuid_export(uuid, format, &strptr, &len), \\
25
+ uuid_destroy(uuid), \\
26
+ rb_str_new(str,size)
27
+
28
+ #define CHARS EPILOG(UUID_FMT_STR, 36)
29
+ #define BINARY EPILOG(UUID_FMT_BIN, 16)
30
+ }
31
+ builder.c %{
32
+ VALUE random_uuid()
33
+ {
34
+ PROLOG(40);
35
+ uuid_make(uuid, UUID_MAKE_V4);
36
+ return CHARS;
37
+ }
38
+ }
39
+ builder.c %{
40
+ VALUE random_uuid_raw()
41
+ {
42
+ PROLOG(20);
43
+ uuid_make(uuid, UUID_MAKE_V4);
44
+ return BINARY;
45
+ }
46
+ }
47
+ builder.c %{
48
+ VALUE sha1_uuid(VALUE oid)
49
+ {
50
+ uuid_t *uuid_ns;
51
+ PROLOG(40);
52
+
53
+ uuid_create(&uuid_ns);
54
+ uuid_load(uuid_ns, "ns:OID");
55
+ uuid_make(uuid, UUID_MAKE_V5, uuid_ns, StringValuePtr(oid));
56
+
57
+ uuid_destroy(uuid_ns);
58
+
59
+ return CHARS;
60
+ }
61
+ }
62
+
63
+ builder.c %{
64
+ VALUE uuid_to_raw(VALUE r_uuid)
65
+ {
66
+ PROLOG(20);
67
+ uuid_import(uuid, UUID_FMT_STR, StringValuePtr(r_uuid), 36);
68
+ return BINARY;
69
+ }
70
+ }
71
+
72
+ builder.c %{
73
+ VALUE uuid_to_formatted(VALUE r_uuid)
74
+ {
75
+ PROLOG(40);
76
+ uuid_import(uuid, UUID_FMT_BIN, StringValuePtr(r_uuid), 36);
77
+ return CHARS;
78
+ }
79
+ }
80
+
81
+ end
82
+ end
83
+
84
+ FAST_UUID = FastUUID.new
85
+
86
+ class ::String
87
+ # Convert to raw (16 bytes) string (self can be already raw or formatted).
88
+ def to_raw_uuid
89
+ if size == 16
90
+ self.freeze
91
+ else
92
+ FAST_UUID.uuid_to_raw(self)
93
+ end
94
+ end
95
+ # Convert to formatted string (self can be raw or already formatted).
96
+ def to_formatted_uuid
97
+ if size == 16
98
+ FAST_UUID.uuid_to_formatted(self)
99
+ else
100
+ self.freeze
101
+ end
102
+ end
103
+ end
104
+
105
+ module StrokeDB::Util
106
+
107
+ def self.random_uuid
108
+ ::FAST_UUID.random_uuid
109
+ end
110
+ def self.random_uuid_raw
111
+ ::FAST_UUID.random_uuid_raw
112
+ end
113
+ def self.sha1_uuid(oid)
114
+ ::FAST_UUID.sha1_uuid(oid)
115
+ end
116
+
117
+ end
118
+
119
+
120
+
121
+ rescue NotImplementedError, CompilationError
122
+ if $!.is_a?(CompilationError)
123
+ puts "# Can't compile C code, make sure you have ossp-uuid installed"
124
+ else
125
+ puts "# Error: #{$!.message}"
126
+ end
127
+ puts "# Falling back to uuidtools gem"
128
+ require 'uuidtools'
129
+ module StrokeDB::Util
130
+
131
+ def self.random_uuid
132
+ ::UUID.random_create.to_s
133
+ end
134
+ def self.random_uuid_raw
135
+ ::UUID.random_create.raw
136
+ end
137
+ def self.sha1_uuid(oid)
138
+ ::UUID.sha1_create(UUID_OID_NAMESPACE, oid).to_s
139
+ end
140
+
141
+ class ::String
142
+ # Convert to raw (16 bytes) string (self can be already raw or formatted).
143
+ def to_raw_uuid
144
+ size == 16 ? self.freeze : ::UUID.parse(self).raw
145
+ end
146
+ # Convert to formatted string (self can be raw or already formatted).
147
+ def to_formatted_uuid
148
+ size == 16 ? ::UUID.parse_raw(self).to_s : self.freeze
149
+ end
150
+ end
151
+
152
+ end
153
+ end
154
+
155
+ module StrokeDB::Util
156
+ RAW_UUID_SIZE = random_uuid_raw.size
157
+ FORMATTED_UUID_SIZE = random_uuid.size
158
+ end
159
+
File without changes
@@ -1,5 +1,5 @@
1
1
  module StrokeDB
2
- View = Meta.new(:uuid => VIEW_UUID) do
2
+ View = Meta.new do
3
3
  attr_accessor :map_with_proc
4
4
  attr_reader :reduce_with_proc
5
5
 
@@ -22,7 +22,7 @@ module StrokeDB
22
22
  end
23
23
 
24
24
  end
25
- ViewCut = Meta.new(:uuid => VIEWCUT_UUID) do
25
+ ViewCut = Meta.new do
26
26
 
27
27
  on_new_document do |cut|
28
28
  cut.instance_eval do
@@ -0,0 +1,5 @@
1
+ require 'volumes/archive_volume'
2
+ require 'volumes/block_volume'
3
+ require 'volumes/distributed_pointer'
4
+ require 'volumes/map_volume'
5
+ require 'volumes/fixed_length_skiplist_volume'
@@ -0,0 +1,165 @@
1
+ require 'readbytes'
2
+ module StrokeDB
3
+ class ArchiveVolume
4
+ attr_reader :file_path, :tail
5
+
6
+ if defined?(::Spec)
7
+ DEFAULT_SIZE = 512*1024
8
+ else
9
+ DEFAULT_SIZE = 64*1024*1024
10
+ end
11
+
12
+ DEFAULT_PATH = "."
13
+
14
+ # Open a volume in a directory +:path+, with UUID +:uuid+
15
+ # and a specified +:size+. If the file does not exist, it is created
16
+ # and filled with zero bytes up to the specified size.
17
+ # Otherwise, it is just opened and ready for reads and writes.
18
+ #
19
+ # Defaults:
20
+ # :path => "."
21
+ # :size => 64 Mb
22
+ #
23
+ # Example:
24
+ # DataVolume.new(:uuid => uuid, :path => "/var/dir", :size => 1024)
25
+ #
26
+ def initialize(options = {})
27
+ @options = options.stringify_keys.reverse_merge('size' => DEFAULT_SIZE, 'path' => DEFAULT_PATH)
28
+ initialize_file
29
+ end
30
+
31
+ # Read a record sitting in a +position+ in the volume file.
32
+ # Record length is stored in a first 4 bytes before the record.
33
+ #
34
+ def read(position)
35
+ @file.seek(position)
36
+ size = @file.readbytes(4).unpack('N').first
37
+ @file.readbytes(size)
38
+ end
39
+
40
+ # Write some data to the end of the file.
41
+ # Returns record position.
42
+ #
43
+ def insert(data)
44
+ raise VolumeCapacityExceeded if (new_tail = @tail + 4 + data.size) > size
45
+ @file.seek(@tail)
46
+ @file.write([data.size].pack('N') + data)
47
+ t = @tail
48
+ @tail = new_tail
49
+ write_tail(@file, @tail)
50
+ t
51
+ end
52
+
53
+ # Close the volume file. You cannot read/insert after that operation.
54
+ # In such case, VolumeClosedException is raised.
55
+ # Call DataVolume.new to open volume again.
56
+ #
57
+ def close!
58
+ safe_close
59
+ end
60
+
61
+ # Close and delete the volume file. You cannot read/insert after that
62
+ # operation. In such case, VolumeClosedException is raised.
63
+ #
64
+ def delete!
65
+ safe_close
66
+ File.delete(@file_path)
67
+ end
68
+
69
+ def path
70
+ @options['path']
71
+ end
72
+
73
+ def size
74
+ @options['size']
75
+ end
76
+
77
+ def uuid
78
+ case @options['uuid']
79
+ when /^#{UUID_RE}$/
80
+ @options['uuid']
81
+ when nil
82
+ @options['uuid'] = Util.random_uuid
83
+ else
84
+ @options['uuid'] = @options['uuid'].to_formatted_uuid
85
+ end
86
+ end
87
+
88
+ def raw_uuid
89
+ @raw_uuid ||= uuid.to_raw_uuid
90
+ end
91
+
92
+ # VolumeClosedException is thrown when you call +read+ or +insert+
93
+ # method on a closed or deleted volume.
94
+ #
95
+ class VolumeClosedException < Exception; end
96
+
97
+ # VolumeCapacityExceeded is thrown when you are trying to +insert+ data
98
+ # that is larger than available capacity.
99
+ class VolumeCapacityExceeded < Exception; end
100
+
101
+ private
102
+
103
+ def initialize_file
104
+ @file_path = File.join(path, hierarchify(uuid) + ".av")
105
+ unless File.exist?(@file_path)
106
+ create_file(@file_path, size)
107
+ else
108
+ @file = File.open(@file_path, File::RDWR)
109
+ end
110
+ @tail = read_tail(@file)
111
+ end
112
+
113
+ # Create file skeleton filled with zeros with a prefix
114
+ # containing current file tail.
115
+ #
116
+ def create_file(path, size)
117
+ FileUtils.mkdir_p(File.dirname(path))
118
+ @file = File.open(path, File::CREAT | File::EXCL | File::RDWR)
119
+ @file.truncate(size)
120
+ write_tail(@file, 4) # 4 is a size of long type.
121
+ end
122
+
123
+ # Close the file if it is opened and remove
124
+ # +read+ and +write+ methods from the instance.
125
+ #
126
+ def safe_close
127
+ @file.close if @file
128
+ @file = nil
129
+ class <<self
130
+ alias :read :raise_volume_closed
131
+ alias :insert :raise_volume_closed
132
+ end
133
+ end
134
+
135
+ # +read+ and +write+ methods are aliased to this
136
+ # when file is closed or deleted.
137
+ #
138
+ def raise_volume_closed(*args)
139
+ raise VolumeClosedException, "Throw this object away and instantiate another one."
140
+ end
141
+ public :raise_volume_closed
142
+
143
+ # Transform filename "aabbccdd" into "aa/bb/aabbccdd"
144
+ # for faster access to a bunch of datavolumes.
145
+ #
146
+ def hierarchify(filename)
147
+ File.join(filename[0,2], filename[2,2], filename)
148
+ end
149
+
150
+ # Read current file end position ("tail") from the file header.
151
+ #
152
+ def read_tail(f)
153
+ f.seek(0)
154
+ f.readbytes(4).unpack('N').first
155
+ end
156
+
157
+ # Update file's end position.
158
+ #
159
+ def write_tail(f, pos)
160
+ f.seek(0)
161
+ f.write([pos].pack('N'))
162
+ pos
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,169 @@
1
+ require 'readbytes'
2
+ module StrokeDB
3
+
4
+ # TODO: inherit from the common AbstractVolume
5
+ class BlockVolume
6
+ attr_reader :file_path, :blocks_count
7
+
8
+ HEADER_LENGTH = 8 # block_size, blocks_count
9
+ DEFAULT_BLOCKS_COUNT = 1024
10
+ DEFAULT_PATH = "."
11
+
12
+ # Open a volume in a directory +:path+, with UUID +:uuid+
13
+ # and a specified +:block_size+. If the file does not exist, it is created
14
+ # and filled with zero bytes up to the specified size.
15
+ # Otherwise, it is just opened and ready for reads and writes.
16
+ # File contains +block_count+ blocks of :block_size: bytes size.
17
+ # When insertion is done to a new position, file is autoextended.
18
+ #
19
+ # Required params: +:block_size+ and +:uuid+
20
+ # Default +:path+ is ".", +:blocks_count+ is 1024
21
+ #
22
+ # Example:
23
+ # DataVolume.new(:uuid => uuid, :path => "/var/dir", :block_size => 1024)
24
+ #
25
+ def initialize(options = {})
26
+ @options = options.stringify_keys.reverse_merge(
27
+ 'path' => DEFAULT_PATH,
28
+ 'blocks_count' => DEFAULT_BLOCKS_COUNT)
29
+ initialize_file
30
+ end
31
+
32
+ # Read a record sitting in a +position+ in the volume file.
33
+ # Record length is stored in a first 4 bytes before the record.
34
+ #
35
+ def read(index)
36
+ csize = @block_size
37
+ @file.seek(HEADER_LENGTH + index * csize)
38
+ @file.readbytes(csize)
39
+ end
40
+
41
+ # Write some data to the end of the file.
42
+ # Returns record position.
43
+ #
44
+ def insert(index, data)
45
+ extend_volume! if index >= @blocks_count
46
+ csize = @block_size
47
+ @file.seek(HEADER_LENGTH + index * csize)
48
+ @file.write(data)
49
+ self
50
+ end
51
+
52
+ # Close the volume file. You cannot read/insert after that operation.
53
+ # In such case, VolumeClosedException is raised.
54
+ # Call DataVolume.new to open volume again.
55
+ #
56
+ def close!
57
+ safe_close
58
+ end
59
+
60
+ # Close and delete the volume file. You cannot read/insert after that
61
+ # operation. In such case, VolumeClosedException is raised.
62
+ #
63
+ def delete!
64
+ safe_close
65
+ File.delete(@file_path)
66
+ end
67
+
68
+ def path
69
+ @options['path']
70
+ end
71
+
72
+ def block_size
73
+ @options['block_size']
74
+ end
75
+
76
+ def blocks_count
77
+ @options['blocks_count']
78
+ end
79
+
80
+ def uuid
81
+ case @options['uuid']
82
+ when /^#{UUID_RE}$/
83
+ @options['uuid']
84
+ when nil
85
+ @options['uuid'] = Util.random_uuid
86
+ else
87
+ @options['uuid'] = @options['uuid'].to_formatted_uuid
88
+ end
89
+ end
90
+
91
+ # VolumeClosedException is thrown when you call +read+ or +insert+
92
+ # method on a closed or deleted volume.
93
+ #
94
+ class VolumeClosedException < Exception; end
95
+
96
+ private
97
+
98
+ def initialize_file
99
+ @file_path = File.join(path, hierarchify(uuid) + ".blocks")
100
+ create_file(@file_path, block_size, blocks_count) unless File.exist?(@file_path)
101
+ @file = File.open(@file_path, File::RDWR)
102
+ @block_size, @blocks_count = read_header(@file)
103
+ if @block_size != block_size && block_size != nil
104
+ raise "Block size collision! Declared #{block_size} bytes, but actually file is formatted by #{@block_size}-byte blocks."
105
+ end
106
+ end
107
+
108
+ # Extends volume to a double size.
109
+ #
110
+ def extend_volume!
111
+ @file.close
112
+ @blocks_count *= 2
113
+ create_file(@file_path, @block_size, @blocks_count)
114
+ @file = File.open(@file_path, File::RDWR)
115
+ end
116
+
117
+ # Create file skeleton filled with zeros with a prefix
118
+ # containing current file tail.
119
+ #
120
+ def create_file(path, block_size, blocks)
121
+ FileUtils.mkdir_p(File.dirname(path))
122
+ File.open(path, File::CREAT | File::WRONLY) do |f|
123
+ f.truncate(HEADER_LENGTH + block_size*blocks)
124
+ write_header(f, block_size, blocks)
125
+ end
126
+ end
127
+
128
+ # Close the file if it is opened and remove
129
+ # +read+ and +write+ methods from the instance.
130
+ #
131
+ def safe_close
132
+ @file.close if @file
133
+ @file = nil
134
+ class <<self
135
+ alias :read :raise_volume_closed
136
+ alias :insert :raise_volume_closed
137
+ end
138
+ end
139
+
140
+ # +read+ and +insert+ methods are aliased to this
141
+ # when file is closed or deleted.
142
+ #
143
+ def raise_volume_closed(*args)
144
+ raise VolumeClosedException, "Throw this object away and instantiate another one."
145
+ end
146
+ public :raise_volume_closed
147
+
148
+ # Transform filename "aabbccdd" into "aa/bb/aabbccdd"
149
+ # for faster access to a bunch of datavolumes.
150
+ #
151
+ def hierarchify(filename)
152
+ File.join(filename[0,2], filename[2,2], filename)
153
+ end
154
+
155
+ # Read current file block size and blocks count.
156
+ #
157
+ def read_header(f)
158
+ f.seek(0)
159
+ f.readbytes(8).unpack('N2')
160
+ end
161
+
162
+ # Update file's end position.
163
+ #
164
+ def write_header(f, block_size, blocks)
165
+ f.seek(0)
166
+ f.write([block_size, blocks].pack('N2'))
167
+ end
168
+ end
169
+ end