strokedb 0.0.2.1 → 0.0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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