perobs 2.3.1 → 2.4.0

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.
@@ -0,0 +1,249 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = FlatFileDB.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'fileutils'
29
+ require 'zlib'
30
+
31
+ require 'perobs/Log'
32
+ require 'perobs/DataBase'
33
+ require 'perobs/FlatFile'
34
+
35
+ module PEROBS
36
+
37
+ # The FlatFileDB is a storage backend that uses a single flat file to store
38
+ # the value blobs.
39
+ class FlatFileDB < DataBase
40
+
41
+ # This version number increases whenever the on-disk format changes in a
42
+ # way that requires conversion actions after an update.
43
+ VERSION = 1
44
+
45
+ attr_reader :max_blob_size
46
+
47
+ # Create a new FlatFileDB object.
48
+ # @param db_name [String] name of the DB directory
49
+ # @param options [Hash] options to customize the behavior. Currently only
50
+ # the following options are supported:
51
+ # :serializer : Can be :marshal, :json, :yaml
52
+ def initialize(db_name, options = {})
53
+ super(options[:serializer] || :json)
54
+
55
+ @db_dir = db_name
56
+ # Create the database directory if it doesn't exist yet.
57
+ ensure_dir_exists(@db_dir)
58
+ PEROBS.log.open(File.join(@db_dir, 'log'))
59
+ check_version
60
+
61
+ # Read the existing DB config.
62
+ @config = get_hash('config')
63
+ check_option('serializer')
64
+
65
+ put_hash('config', @config)
66
+ end
67
+
68
+ # Open the FlatFileDB for transactions.
69
+ def open
70
+ @flat_file = FlatFile.new(@db_dir)
71
+ @flat_file.open
72
+ PEROBS.log.info "FlatFile opened"
73
+ end
74
+
75
+ # Close the FlatFileDB.
76
+ def close
77
+ @flat_file.close
78
+ @flat_file = nil
79
+ PEROBS.log.info "FlatFile closed"
80
+ end
81
+
82
+ # Delete the entire database. The database is no longer usable after this
83
+ # method was called.
84
+ def delete_database
85
+ FileUtils.rm_rf(@db_dir)
86
+ end
87
+
88
+ def FlatFileDB::delete_db(db_name)
89
+ FileUtils.rm_rf(db_name)
90
+ end
91
+
92
+ # Return true if the object with given ID exists
93
+ # @param id [Fixnum or Bignum]
94
+ def include?(id)
95
+ !@flat_file.find_obj_addr_by_id(id).nil?
96
+ end
97
+
98
+ # Store a simple Hash as a JSON encoded file into the DB directory.
99
+ # @param name [String] Name of the hash. Will be used as file name.
100
+ # @param hash [Hash] A Hash that maps String objects to strings or
101
+ # numbers.
102
+ def put_hash(name, hash)
103
+ file_name = File.join(@db_dir, name + '.json')
104
+ begin
105
+ File.write(file_name, hash.to_json)
106
+ rescue => e
107
+ PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
108
+ end
109
+ end
110
+
111
+ # Load the Hash with the given name.
112
+ # @param name [String] Name of the hash.
113
+ # @return [Hash] A Hash that maps String objects to strings or numbers.
114
+ def get_hash(name)
115
+ file_name = File.join(@db_dir, name + '.json')
116
+ return ::Hash.new unless File.exist?(file_name)
117
+
118
+ begin
119
+ json = File.read(file_name)
120
+ rescue => e
121
+ PEROBS.log.fatal "Cannot read hash file '#{file_name}': #{e.message}"
122
+ end
123
+ JSON.parse(json, :create_additions => true)
124
+ end
125
+
126
+ # Store the given object into the cluster files.
127
+ # @param obj [Hash] Object as defined by PEROBS::ObjectBase
128
+ def put_object(obj, id)
129
+ @flat_file.delete_obj_by_id(id)
130
+ @flat_file.write_obj_by_id(id, serialize(obj))
131
+ end
132
+
133
+ # Load the given object from the filesystem.
134
+ # @param id [Fixnum or Bignum] object ID
135
+ # @return [Hash] Object as defined by PEROBS::ObjectBase or nil if ID does
136
+ # not exist
137
+ def get_object(id)
138
+ if (raw_obj = @flat_file.read_obj_by_id(id))
139
+ return deserialize(raw_obj)
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ # This method must be called to initiate the marking process.
146
+ def clear_marks
147
+ t = Time.now
148
+ PEROBS.log.info "Clearing all marks"
149
+ @flat_file.clear_all_marks
150
+ PEROBS.log.info "All marks cleared in #{Time.now - t} seconds"
151
+ end
152
+
153
+ # Permanently delete all objects that have not been marked. Those are
154
+ # orphaned and are no longer referenced by any actively used object.
155
+ # @return [Array] List of IDs that have been removed from the DB.
156
+ def delete_unmarked_objects
157
+ t = Time.now
158
+ PEROBS.log.info "Deleting unmarked objects"
159
+ retval = @flat_file.delete_unmarked_objects
160
+ PEROBS.log.info "Unmarked objects deleted in #{Time.now - t} seconds"
161
+ retval
162
+ end
163
+
164
+ # Mark an object.
165
+ # @param id [Fixnum or Bignum] ID of the object to mark
166
+ def mark(id)
167
+ @flat_file.mark_obj_by_id(id)
168
+ end
169
+
170
+ # Check if the object is marked.
171
+ # @param id [Fixnum or Bignum] ID of the object to check
172
+ # @param ignore_errors [Boolean] If set to true no errors will be raised
173
+ # for non-existing objects.
174
+ def is_marked?(id, ignore_errors = false)
175
+ @flat_file.is_marked_by_id?(id)
176
+ end
177
+
178
+ # Basic consistency check.
179
+ # @param repair [TrueClass/FalseClass] True if found errors should be
180
+ # repaired.
181
+ def check_db(repair = false)
182
+ t = Time.now
183
+ PEROBS.log.info "check_db started"
184
+ @flat_file.check(repair)
185
+ PEROBS.log.info "check_db completed in #{Time.now - t} seconds"
186
+ end
187
+
188
+ # Check if the stored object is syntactically correct.
189
+ # @param id [Fixnum/Bignum] Object ID
190
+ # @param repair [TrueClass/FalseClass] True if an repair attempt should be
191
+ # made.
192
+ # @return [TrueClass/FalseClass] True if the object is OK, otherwise
193
+ # false.
194
+ def check(id, repair)
195
+ begin
196
+ get_object(id)
197
+ rescue => e
198
+ PEROBS.log.warn "Cannot read object with ID #{id}: #{e.message}"
199
+ return false
200
+ end
201
+
202
+ true
203
+ end
204
+
205
+ # Store the given serialized object into the cluster files. This method is
206
+ # for internal use only!
207
+ # @param raw [String] Serialized Object as defined by PEROBS::ObjectBase
208
+ # @param id [Fixnum or Bignum] Object ID
209
+ def put_raw_object(raw, id)
210
+ @flat_file.delete_obj_(id)
211
+ @flat_file.write_obj_by_id(id, raw)
212
+ end
213
+
214
+ private
215
+
216
+ def check_version
217
+ version_file = File.join(@db_dir, 'version')
218
+ version = VERSION
219
+
220
+ if File.exist?(version_file)
221
+ begin
222
+ version = File.read(version_file).to_i
223
+ rescue => e
224
+ PEROBS.log.fatal "Cannot read version number file " +
225
+ "'#{version_file}': " + e.message
226
+ end
227
+ else
228
+ write_version_file(version_file)
229
+ end
230
+
231
+ if version > VERSION
232
+ PEROBS.log.fatal "Cannot downgrade the FlatFile database from " +
233
+ "version #{version} to version #{VERSION}"
234
+ end
235
+ end
236
+
237
+ def write_version_file(version_file)
238
+ begin
239
+ File.write(version_file, "#{VERSION}")
240
+ rescue => e
241
+ PEROBS.log.fatal "Cannot write version number file " +
242
+ "'#{version_file}': " + e.message
243
+ end
244
+ end
245
+
246
+ end
247
+
248
+ end
249
+
@@ -0,0 +1,204 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = FreeSpaceManager.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'perobs/Log'
29
+ require 'perobs/StackFile'
30
+
31
+ module PEROBS
32
+
33
+ # The FreeSpaceManager keeps a list of the free spaces in the FlatFile. Each
34
+ # space is stored with address and size. The data is persisted in the file
35
+ # system. Internally the free spaces are stored in different pools. Each
36
+ # pool holds spaces that are at least of a given size and not as big as the
37
+ # next pool up. Pool entry minimum sizes increase by a factor of 2 from
38
+ # pool to pool.
39
+ class FreeSpaceManager
40
+
41
+ # Create a new FreeSpaceManager object in the specified directory.
42
+ # @param dir [String] directory path
43
+ def initialize(dir)
44
+ @dir = dir
45
+ @pools = []
46
+ end
47
+
48
+ # Open the pool files.
49
+ def open
50
+ Dir.glob(File.join(@dir, 'free_list_*.stack')).each do |file|
51
+ basename = File.basename(file)
52
+ # Cut out the pool index from the file name.
53
+ index = basename[10..-7].to_i
54
+ @pools[index] = StackFile.new(@dir, basename[0..-7], 2 * 8)
55
+ end
56
+ end
57
+
58
+ # Close all pool files.
59
+ def close
60
+ @pools = []
61
+ end
62
+
63
+ # Add a new space with a given address and size.
64
+ # @param address [Integer] Starting address of the space
65
+ # @param size [Integer] size of the space in bytes
66
+ def add_space(address, size)
67
+ if size <= 0
68
+ PEROBS.log.fatal "Size (#{size}) must be larger than 0."
69
+ end
70
+ pool_index = msb(size)
71
+ new_pool(pool_index) unless @pools[pool_index]
72
+ push_pool(pool_index, [ address, size ].pack('QQ'))
73
+ end
74
+
75
+ # Get a space that has at least the requested size.
76
+ # @param size [Integer] Required size in bytes
77
+ # @return [Array] Touple with address and actual size of the space.
78
+ def get_space(size)
79
+ if size <= 0
80
+ PEROBS.log.fatal "Size (#{size}) must be larger than 0."
81
+ end
82
+ # When we search for a free space we need to search the pool that
83
+ # corresponds to (size - 1) * 2. It is the pool that has the spaces that
84
+ # are at least as big as size.
85
+ pool_index = size == 1 ? 0 : msb(size - 1) + 1
86
+ unless @pools[pool_index]
87
+ return nil
88
+ else
89
+ return nil unless (entry = pop_pool(pool_index))
90
+ sp_address, sp_size = entry.unpack('QQ')
91
+ if sp_size < size
92
+ PEROBS.log.fatal "Space at address #{sp_address} is too small. " +
93
+ "Must be at least #{size} bytes but is only #{sp_size} bytes."
94
+ end
95
+ [ sp_address, sp_size ]
96
+ end
97
+ end
98
+
99
+ # Clear all pools and forget any registered spaces.
100
+ def clear
101
+ @pools.each do |pool|
102
+ if pool
103
+ pool.open
104
+ pool.clear
105
+ pool.close
106
+ end
107
+ end
108
+ close
109
+ end
110
+
111
+ # Check if there is a space in the free space lists that matches the
112
+ # address and the size.
113
+ # @param [Integer] address Address of the space
114
+ # @param [Integer] size Length of the space in bytes
115
+ # @return [Boolean] True if space is found, false otherwise
116
+ def has_space?(address, size)
117
+ unless (pool = @pools[msb(size)])
118
+ return false
119
+ end
120
+
121
+ pool.open
122
+ pool.each do |entry|
123
+ sp_address, sp_size = entry.unpack('QQ')
124
+ if address == sp_address
125
+ if size != sp_size
126
+ PEROBS.log.fatal "FreeSpaceManager has space with different " +
127
+ "size"
128
+ end
129
+ pool.close
130
+ return true
131
+ end
132
+ end
133
+
134
+ pool.close
135
+ false
136
+ end
137
+
138
+ def check(flat_file)
139
+ @pools.each do |pool|
140
+ next unless pool
141
+
142
+ pool.open
143
+ pool.each do |entry|
144
+ address, size = entry.unpack('QQ')
145
+ unless flat_file.has_space?(address, size)
146
+ PEROBS.log.error "FreeSpaceManager has space that isn't " +
147
+ "available in the FlatFile."
148
+ return false
149
+ end
150
+ end
151
+ pool.close
152
+ end
153
+
154
+ true
155
+ end
156
+
157
+ def inspect
158
+ '[' + @pools.map do |p|
159
+ if p
160
+ p.open
161
+ r = p.to_ary.map { |bs| bs.unpack('QQ')}.inspect
162
+ p.close
163
+ r
164
+ else
165
+ 'nil'
166
+ end
167
+ end.join(', ') + ']'
168
+ end
169
+
170
+ private
171
+
172
+ def new_pool(index)
173
+ # The file name pattern for the pool files.
174
+ filename = "free_list_#{index}"
175
+ @pools[index] = sf = StackFile.new(@dir, filename, 2 * 8)
176
+ end
177
+
178
+ def push_pool(index, value)
179
+ pool = @pools[index]
180
+ pool.open
181
+ pool.push(value)
182
+ pool.close
183
+ end
184
+
185
+ def pop_pool(index)
186
+ pool = @pools[index]
187
+ pool.open
188
+ value = pool.pop
189
+ pool.close
190
+
191
+ value
192
+ end
193
+
194
+ def msb(i)
195
+ unless i > 0
196
+ PEROBS.log.fatal "i must be larger than 0"
197
+ end
198
+ i.to_s(2).length - 1
199
+ end
200
+
201
+ end
202
+
203
+ end
204
+
data/lib/perobs/Hash.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # = Hash.rb -- Persistent Ruby Object Store
4
4
  #
5
- # Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
5
+ # Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
6
  #
7
7
  # MIT License
8
8
  #
@@ -25,6 +25,7 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
+ require 'perobs/Log'
28
29
  require 'perobs/ObjectBase'
29
30
 
30
31
  module PEROBS
@@ -48,7 +49,7 @@ module PEROBS
48
49
  :==, :[], :assoc, :compare_by_identity, :compare_by_identity?, :default,
49
50
  :default_proc, :each, :each_key, :each_pair, :each_value, :empty?,
50
51
  :eql?, :fetch, :flatten, :has_key?, :has_value?, :hash, :include?,
51
- :inspect, :invert, :key, :key?, :keys, :length, :member?, :merge,
52
+ :invert, :key, :key?, :keys, :length, :member?, :merge,
52
53
  :pretty_print, :pretty_print_cycle, :rassoc, :reject, :select, :size,
53
54
  :to_a, :to_h, :to_hash, :to_s, :value?, :values, :values_at
54
55
  ] + Enumerable.instance_methods).uniq.each do |method_sym|
@@ -104,6 +105,7 @@ module PEROBS
104
105
  @data.delete_if do |k, v|
105
106
  v && v.respond_to?(:is_poxreference?) && v.id == id
106
107
  end
108
+ @store.cache.cache_write(self)
107
109
  end
108
110
 
109
111
  # Restore the persistent data from a single data structure.
@@ -117,6 +119,17 @@ module PEROBS
117
119
  @data
118
120
  end
119
121
 
122
+ # Textual dump for debugging purposes
123
+ # @return [String]
124
+ def inspect
125
+ "<#{self.class}:#{@_id}>\n{\n" +
126
+ @data.map do |k, v|
127
+ " #{k.inspect} => " + (v.respond_to?(:is_poxreference?) ?
128
+ "<PEROBS::ObjectBase:#{v._id}>" : v.inspect)
129
+ end.join(",\n") +
130
+ "\n}\n"
131
+ end
132
+
120
133
  private
121
134
 
122
135
  def _serialize
@@ -130,9 +143,9 @@ module PEROBS
130
143
  # objects should not be used directly. The library only exposes them
131
144
  # via POXReference proxy objects.
132
145
  if v.is_a?(ObjectBase)
133
- raise RuntimeError, 'A PEROBS::ObjectBase object escaped! ' +
146
+ PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
134
147
  "It is stored in a PEROBS::Hash with key #{k.inspect}. " +
135
- 'Have you used self() instead of myself() to' +
148
+ 'Have you used self() instead of myself() to ' +
136
149
  "get the reference of this PEROBS object?\n" +
137
150
  v.inspect
138
151
  end
@@ -0,0 +1,164 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = IndexTree.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'perobs/FixedSizeBlobFile'
29
+ require 'perobs/IndexTreeNode'
30
+
31
+ module PEROBS
32
+
33
+ # The IndexTree maps the object ID to the address in the FlatFile. The
34
+ # search in the tree is much faster than the linear search in the FlatFile.
35
+ class IndexTree
36
+
37
+ # Determines how many levels of the IndexTree will be kept in memory to
38
+ # accerlerate the access. A number of 7 will keep up to 21845 entries in
39
+ # the cache but will accelerate the access to the FlatFile address.
40
+ MAX_CACHED_LEVEL = 7
41
+
42
+ attr_reader :nodes, :ids
43
+
44
+ def initialize(db_dir)
45
+ # Directory path used to store the files.
46
+ @db_dir = db_dir
47
+
48
+ # This FixedSizeBlobFile contains the nodes of the IndexTree.
49
+ @nodes = FixedSizeBlobFile.new(db_dir, 'database_index',
50
+ IndexTreeNode::NODE_BYTES)
51
+
52
+ # The node sequence usually only reveals a partial match with the
53
+ # requested ID. So, the leaves of the tree point to the object_id_index
54
+ # file which contains the full object ID and the address of the
55
+ # corresponding object in the FlatFile.
56
+ @ids = FixedSizeBlobFile.new(db_dir, 'object_id_index', 2 * 8)
57
+
58
+ # The first MAX_CACHED_LEVEL levels of nodes will be cached in memory to
59
+ # improve access times.
60
+ @node_cache = {}
61
+ end
62
+
63
+ # Open the tree files.
64
+ def open
65
+ @nodes.open
66
+ @ids.open
67
+ @root = IndexTreeNode.new(self, 0, 0)
68
+ end
69
+
70
+ # Close the tree files.
71
+ def close
72
+ @ids.close
73
+ @nodes.close
74
+ end
75
+
76
+ # Flush out all unwritten data
77
+ def sync
78
+ @ids.sync
79
+ @nodes.sync
80
+ end
81
+
82
+ # Delete all data from the tree.
83
+ def clear
84
+ @nodes.clear
85
+ @ids.clear
86
+ @node_cache = {}
87
+ @root = IndexTreeNode.new(self, 0, 0)
88
+ end
89
+
90
+ # Return an IndexTreeNode object that corresponds to the given address.
91
+ # @param nibble [Fixnum] Index of the nibble the node should correspond to
92
+ # @param address [Integer] Address of the node in @nodes or nil
93
+ def get_node(nibble, address = nil)
94
+ # Generate a mask for the least significant bits up to and including the
95
+ # nibble.
96
+ mask = (2 ** ((1 + nibble) * 4)) - 1
97
+ if address && (node = @node_cache[address & mask])
98
+ # We have an address and have found the node in the node cache.
99
+ return node
100
+ else
101
+ # We don't have a IndexTreeNode object yet for this node. Create it
102
+ # with the data from the 'database_index' file.
103
+ node = IndexTreeNode.new(self, nibble, address)
104
+ # Add the node to the node cache if it's up to MAX_CACHED_LEVEL levels
105
+ # down from the root.
106
+ @node_cache[address & mask] = node if nibble <= MAX_CACHED_LEVEL
107
+ return node
108
+ end
109
+ end
110
+
111
+ # Delete a node from the tree that corresponds to the address.
112
+ # @param nibble [Fixnum] The corresponding nibble for the node
113
+ # @param address [Integer] The address of the node in @nodes
114
+ def delete_node(nibble, address)
115
+ # First delete the node from the node cache.
116
+ mask = (2 ** ((1 + nibble) * 4)) - 1
117
+ @node_cache.delete(address & mask)
118
+ # Then delete it from the 'database_index' file.
119
+ @nodes.delete_blob(address)
120
+ end
121
+
122
+ # Store a ID/value touple into the tree. The value can later be retrieved
123
+ # by the ID again. IDs are always unique in the tree. If the ID already
124
+ # exists in the tree, the value will be overwritten.
125
+ # @param id [Integer] ID or key
126
+ # @param value [Integer] value to store
127
+ def put_value(id, value)
128
+ #MAX_CACHED_LEVEL.downto(0) do |i|
129
+ # mask = (2 ** ((1 + i) * 4)) - 1
130
+ # if (node = @node_cache[value & mask])
131
+ # return node.put_value(id, value)
132
+ # end
133
+ #end
134
+ @root.put_value(id, value)
135
+ end
136
+
137
+ # Retrieve the value that was stored with the given ID.
138
+ # @param id [Integer] ID of the value to retrieve
139
+ # @return [Fixnum] value
140
+ def get_value(id)
141
+ @root.get_value(id)
142
+ end
143
+
144
+ # Delete the value with the given ID.
145
+ # @param [Integer] id
146
+ def delete_value(id)
147
+ @root.delete_value(id)
148
+ end
149
+
150
+ # Check if the index is OK and matches the flat_file data.
151
+ def check(flat_file)
152
+ @root.check(flat_file)
153
+ end
154
+
155
+ # Convert the tree into a human readable form.
156
+ # @return [String]
157
+ def inspect
158
+ @root.inspect
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+