perobs 2.5.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/perobs/Array.rb +1 -1
- data/lib/perobs/BTree.rb +233 -0
- data/lib/perobs/BTreeDB.rb +2 -0
- data/lib/perobs/BTreeNode.rb +706 -0
- data/lib/perobs/BTreeNodeCache.rb +107 -0
- data/lib/perobs/BTreeNodeLink.rb +141 -0
- data/lib/perobs/EquiBlobsFile.rb +570 -0
- data/lib/perobs/FlatFile.rb +179 -78
- data/lib/perobs/FlatFileBlobHeader.rb +92 -17
- data/lib/perobs/FlatFileDB.rb +16 -7
- data/lib/perobs/LockFile.rb +181 -0
- data/lib/perobs/Object.rb +2 -1
- data/lib/perobs/SpaceTree.rb +181 -0
- data/lib/perobs/SpaceTreeNode.rb +672 -0
- data/lib/perobs/SpaceTreeNodeCache.rb +76 -0
- data/lib/perobs/SpaceTreeNodeLink.rb +103 -0
- data/lib/perobs/Store.rb +27 -13
- data/lib/perobs/version.rb +1 -1
- data/test/BTree_spec.rb +128 -0
- data/test/EquiBlobsFile_spec.rb +199 -0
- data/test/FlatFileDB_spec.rb +63 -9
- data/test/LockFile_spec.rb +133 -0
- data/test/SpaceTree_spec.rb +245 -0
- data/test/Store_spec.rb +3 -0
- data/test/spec_helper.rb +13 -0
- metadata +21 -13
- data/lib/perobs/FixedSizeBlobFile.rb +0 -193
- data/lib/perobs/FreeSpaceManager.rb +0 -204
- data/lib/perobs/IndexTree.rb +0 -145
- data/lib/perobs/IndexTreeNode.rb +0 -316
- data/test/FixedSizeBlobFile_spec.rb +0 -91
- data/test/FreeSpaceManager_spec.rb +0 -91
- data/test/IndexTree_spec.rb +0 -118
@@ -0,0 +1,672 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = SpaceTreeNode.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2016, 2017 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/FlatFileBlobHeader'
|
30
|
+
require 'perobs/FlatFile'
|
31
|
+
require 'perobs/SpaceTreeNodeLink'
|
32
|
+
|
33
|
+
module PEROBS
|
34
|
+
|
35
|
+
# The SpaceTree keeps a complete list of all empty spaces in the FlatFile.
|
36
|
+
# Spaces are stored with size and address. The Tree is Tenerary Tree. The
|
37
|
+
# nodes can link to other nodes with smaller spaces, same spaces and bigger
|
38
|
+
# spaces.
|
39
|
+
class SpaceTreeNode
|
40
|
+
|
41
|
+
attr_accessor :size, :blob_address
|
42
|
+
attr_reader :node_address, :parent, :smaller, :equal, :larger
|
43
|
+
|
44
|
+
# Each node can hold a reference to the parent, a lower, equal or larger
|
45
|
+
# size node and the actual value and the address in the FlatFile. Each of
|
46
|
+
# these entries is 8 bytes long.
|
47
|
+
NODE_BYTES = 6 * 8
|
48
|
+
# The pack/unpack format.
|
49
|
+
NODE_BYTES_FORMAT = 'Q6'
|
50
|
+
|
51
|
+
# Create a new SpaceTreeNode object. If node_address is not nil, the data
|
52
|
+
# will be read from the SpaceTree file at the given node_address.
|
53
|
+
# @param tree [SpaceTree] Tree that the object should belong to
|
54
|
+
# @param parent [SpaceTreeNode] Parent node in the tree
|
55
|
+
# @param node_address [Integer] Address of the node in the file
|
56
|
+
# @param blob_address [Integer] Address of the free space blob
|
57
|
+
# @param size [Integer] Size of the free space blob
|
58
|
+
def initialize(tree, parent = nil, node_address = nil, blob_address = 0,
|
59
|
+
size = 0)
|
60
|
+
@tree = tree
|
61
|
+
if blob_address < 0
|
62
|
+
PEROBS.log.fatal "Node address (#{node_address}) must be larger than 0"
|
63
|
+
end
|
64
|
+
@blob_address = blob_address
|
65
|
+
@size = size
|
66
|
+
@smaller = @equal = @larger = nil
|
67
|
+
@node_address = node_address
|
68
|
+
|
69
|
+
unless node_address.nil? || node_address.is_a?(Integer)
|
70
|
+
PEROBS.log.fatal "node_address is not Integer: #{node_address.class}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if node_address
|
74
|
+
# This must be an existing node. Try to read it and fill the instance
|
75
|
+
# variables.
|
76
|
+
if size != 0
|
77
|
+
PEROBS.log.fatal "If node_address is not nil size must be 0"
|
78
|
+
end
|
79
|
+
if blob_address != 0
|
80
|
+
PEROBS.log.fatal "If node_address is not nil blob_address must be 0"
|
81
|
+
end
|
82
|
+
unless read_node
|
83
|
+
PEROBS.log.fatal "SpaceTree node at address #{node_address} " +
|
84
|
+
"does not exist"
|
85
|
+
end
|
86
|
+
else
|
87
|
+
# This is a new node. Make sure the data is written to the file.
|
88
|
+
@node_address = @tree.nodes.free_address
|
89
|
+
self.parent = parent
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add a new node for the given address and size to the tree.
|
94
|
+
# @param address [Integer] address of the free space
|
95
|
+
# @param size [Integer] size of the free space
|
96
|
+
def add_space(address, size)
|
97
|
+
node = self
|
98
|
+
|
99
|
+
loop do
|
100
|
+
if node.size == 0
|
101
|
+
# This happens only for the root node if the tree is empty.
|
102
|
+
node.set_size_and_address(size, address)
|
103
|
+
break
|
104
|
+
elsif size < node.size
|
105
|
+
# The new size is smaller than this node.
|
106
|
+
if node.smaller
|
107
|
+
# There is already a smaller node, so pass it on.
|
108
|
+
node = node.smaller
|
109
|
+
else
|
110
|
+
# There is no smaller node yet, so we create a new one as a
|
111
|
+
# smaller child of the current node.
|
112
|
+
node.set_link('@smaller',
|
113
|
+
@tree.new_node(node, address, size))
|
114
|
+
break
|
115
|
+
end
|
116
|
+
elsif size > node.size
|
117
|
+
# The new size is larger than this node.
|
118
|
+
if node.larger
|
119
|
+
# There is already a larger node, so pass it on.
|
120
|
+
node = node.larger
|
121
|
+
else
|
122
|
+
# There is no larger node yet, so we create a new one as a larger
|
123
|
+
# child of the current node.
|
124
|
+
node.set_link('@larger',
|
125
|
+
@tree.new_node(node, address, size))
|
126
|
+
break
|
127
|
+
end
|
128
|
+
else
|
129
|
+
# Same size as current node. Insert new node as equal child at top of
|
130
|
+
# equal list.
|
131
|
+
new_node = @tree.new_node(node, address, size)
|
132
|
+
new_node.set_link('@equal', node.equal)
|
133
|
+
|
134
|
+
node.set_link('@equal', new_node)
|
135
|
+
|
136
|
+
break
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Check if this node or any sub-node has an entry for the given address
|
142
|
+
# and size.
|
143
|
+
# @param address [Integer] address of the free space
|
144
|
+
# @param size [Integer] size of the free space
|
145
|
+
# @return [Boolean] True if found, otherwise false
|
146
|
+
def has_space?(address, size)
|
147
|
+
node = self
|
148
|
+
loop do
|
149
|
+
if node.blob_address == address
|
150
|
+
return true
|
151
|
+
elsif size < node.size && node.smaller
|
152
|
+
node = node.smaller
|
153
|
+
elsif size > node.size && node.larger
|
154
|
+
node = node.larger
|
155
|
+
elsif size == node.size && node.equal
|
156
|
+
node = node.equal
|
157
|
+
else
|
158
|
+
return false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Return an address/size touple that matches exactly the requested size.
|
164
|
+
# Return nil if nothing was found.
|
165
|
+
# @param size [Integer] size of the free space
|
166
|
+
# @return [Array or nil] address, size touple or nil
|
167
|
+
def find_matching_space(size)
|
168
|
+
node = self
|
169
|
+
|
170
|
+
loop do
|
171
|
+
if node.size < size
|
172
|
+
if node.larger
|
173
|
+
# The current space is not yet large enough. If we have a larger sub
|
174
|
+
# node check that one next.
|
175
|
+
node = node.larger
|
176
|
+
else
|
177
|
+
break
|
178
|
+
end
|
179
|
+
elsif node.size == size
|
180
|
+
# We've found a space that is an exact match. Remove it from the
|
181
|
+
# list and return it.
|
182
|
+
address = node.blob_address
|
183
|
+
node.delete_node
|
184
|
+
return [ address, size ]
|
185
|
+
else
|
186
|
+
break
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
return nil
|
191
|
+
end
|
192
|
+
|
193
|
+
# Return an address/size touple that matches the requested size or is
|
194
|
+
# larger than the requested size plus the overhead for another blob.
|
195
|
+
# Return nil if nothing was found.
|
196
|
+
# @param size [Integer] size of the free space
|
197
|
+
# @return [Array or nil] address, size touple or nil
|
198
|
+
def find_equal_or_larger_space(size)
|
199
|
+
node = self
|
200
|
+
|
201
|
+
loop do
|
202
|
+
if node.size < size
|
203
|
+
if node.larger
|
204
|
+
# The current space is not yet large enough. If we have a larger sub
|
205
|
+
# node check that one next.
|
206
|
+
node = node.larger
|
207
|
+
else
|
208
|
+
break
|
209
|
+
end
|
210
|
+
elsif node.size == size ||
|
211
|
+
node.size >= size * 2 + FlatFileBlobHeader::LENGTH
|
212
|
+
# We've found a space that is either a perfect match or is large
|
213
|
+
# enough to hold at least one more record. Remove it from the list and
|
214
|
+
# return it.
|
215
|
+
actual_size = node.size
|
216
|
+
address = node.blob_address
|
217
|
+
node.delete_node
|
218
|
+
return [ address, actual_size ]
|
219
|
+
elsif node.smaller
|
220
|
+
# The current space is larger than size but not large enough for an
|
221
|
+
# additional record. So check if we have a perfect match in the
|
222
|
+
# smaller brach if available.
|
223
|
+
node = node.smaller
|
224
|
+
else
|
225
|
+
break
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
# Remove a smaller/equal/larger link from the current node.
|
233
|
+
# @param child_node [SpaceTreeNodeLink] node to remove
|
234
|
+
def unlink_node(child_node)
|
235
|
+
if @smaller == child_node
|
236
|
+
@smaller = nil
|
237
|
+
elsif @equal == child_node
|
238
|
+
@equal = nil
|
239
|
+
elsif @larger == child_node
|
240
|
+
@larger = nil
|
241
|
+
else
|
242
|
+
PEROBS.log.fatal "Cannot unlink unknown child node with address " +
|
243
|
+
"#{child_node.node_address} from #{to_s}"
|
244
|
+
end
|
245
|
+
write_node
|
246
|
+
end
|
247
|
+
|
248
|
+
# Depth-first iterator for all nodes. The iterator yields the given block
|
249
|
+
# at 5 points for any found node. The mode variable indicates the point.
|
250
|
+
# :on_enter Coming from the parent we've entered the node for the first
|
251
|
+
# time
|
252
|
+
# :smaller We are about to follow the link to the smaller sub-node
|
253
|
+
# :equal We are about to follow the link to the equal sub-node
|
254
|
+
# :larger We are about to follow the link to the larger sub-node
|
255
|
+
# :on_exit We have completed this node
|
256
|
+
def each
|
257
|
+
# We use a non-recursive implementation to traverse the tree. This stack
|
258
|
+
# keeps track of all the known still to be checked nodes.
|
259
|
+
stack = [ [ self, :on_enter ] ]
|
260
|
+
|
261
|
+
while !stack.empty?
|
262
|
+
node, mode = stack.pop
|
263
|
+
|
264
|
+
# Empty trees only have a dummy node that has no parent, and a size
|
265
|
+
# and address of 0.
|
266
|
+
break if node.size == 0 && node.blob_address == 0 && node.parent.nil?
|
267
|
+
|
268
|
+
case mode
|
269
|
+
when :on_enter
|
270
|
+
yield(node, mode, stack)
|
271
|
+
stack.push([ node, :smaller ])
|
272
|
+
when :smaller
|
273
|
+
yield(node, mode, stack) if node.smaller
|
274
|
+
stack.push([ node, :equal ])
|
275
|
+
stack.push([ node.smaller, :on_enter]) if node.smaller
|
276
|
+
when :equal
|
277
|
+
yield(node, mode, stack) if node.equal
|
278
|
+
stack.push([ node, :larger ])
|
279
|
+
stack.push([ node.equal, :on_enter]) if node.equal
|
280
|
+
when :larger
|
281
|
+
yield(node, mode, stack) if node.larger
|
282
|
+
stack.push([ node, :on_exit])
|
283
|
+
stack.push([ node.larger, :on_enter]) if node.larger
|
284
|
+
when :on_exit
|
285
|
+
yield(node, mode, stack)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def delete_node
|
291
|
+
if @equal
|
292
|
+
# Replace the current node with the next @equal node.
|
293
|
+
@equal.set_link('@smaller', @smaller) if @smaller
|
294
|
+
@equal.set_link('@larger', @larger) if @larger
|
295
|
+
relink_parent(@equal)
|
296
|
+
elsif @smaller && @larger.nil?
|
297
|
+
# We have no @larger node, so we can just replace the current node
|
298
|
+
# with the @smaller node.
|
299
|
+
relink_parent(@smaller)
|
300
|
+
elsif @larger && @smaller.nil?
|
301
|
+
# We have no @smaller node, wo we can just replace the current node
|
302
|
+
# with the @larger node.
|
303
|
+
relink_parent(@larger)
|
304
|
+
elsif @smaller && @larger
|
305
|
+
# Find the largest node in the smaller sub-node. This node will
|
306
|
+
# replace the current node.
|
307
|
+
node = @smaller.find_largest_node
|
308
|
+
if node != @smaller
|
309
|
+
# If the found node is not the direct @smaller node, attach the
|
310
|
+
# smaller sub-node of the found node to the parent of the found
|
311
|
+
# node.
|
312
|
+
node.relink_parent(node.smaller)
|
313
|
+
# The @smaller sub node of the current node is attached to the
|
314
|
+
# @smaller link of the found node.
|
315
|
+
node.set_link('@smaller', @smaller)
|
316
|
+
end
|
317
|
+
# Attach the @larger sub-node of the current node to the @larger link
|
318
|
+
# of the found node.
|
319
|
+
node.set_link('@larger', @larger)
|
320
|
+
# Point the link in the parent of the current node to the found node.
|
321
|
+
relink_parent(node)
|
322
|
+
else
|
323
|
+
# The node is a leaf node.
|
324
|
+
relink_parent(nil)
|
325
|
+
end
|
326
|
+
@tree.delete_node(@node_address) if @parent
|
327
|
+
end
|
328
|
+
|
329
|
+
# Replace the link in the parent node of the current node that points to
|
330
|
+
# the current node with the given node.
|
331
|
+
# @param node [SpaceTreeNode]
|
332
|
+
def relink_parent(node)
|
333
|
+
if @parent
|
334
|
+
if @parent.smaller == self
|
335
|
+
@parent.set_link('@smaller', node)
|
336
|
+
elsif @parent.equal == self
|
337
|
+
@parent.set_link('@equal', node)
|
338
|
+
elsif @parent.larger == self
|
339
|
+
@parent.set_link('@larger', node)
|
340
|
+
else
|
341
|
+
PEROBS.log.fatal "Cannot relink unknown child node with address " +
|
342
|
+
"#{node.node_address} from #{to_s}"
|
343
|
+
end
|
344
|
+
else
|
345
|
+
if node
|
346
|
+
@tree.set_root(node)
|
347
|
+
node.parent = nil
|
348
|
+
else
|
349
|
+
set_size_and_address(0, 0)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Find the node with the smallest size in this sub-tree.
|
355
|
+
# @return [SpaceTreeNode]
|
356
|
+
def find_smallest_node
|
357
|
+
node = self
|
358
|
+
loop do
|
359
|
+
if node.smaller
|
360
|
+
node = node.smaller
|
361
|
+
else
|
362
|
+
# We've found a 'leaf' node.
|
363
|
+
return node
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Find the node with the largest size in this sub-tree.
|
369
|
+
# @return [SpaceTreeNode]
|
370
|
+
def find_largest_node
|
371
|
+
node = self
|
372
|
+
loop do
|
373
|
+
if node.larger
|
374
|
+
node = node.larger
|
375
|
+
else
|
376
|
+
# We've found a 'leaf' node.
|
377
|
+
return node
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def set_size_and_address(size, address)
|
383
|
+
@size = size
|
384
|
+
@blob_address = address
|
385
|
+
write_node
|
386
|
+
end
|
387
|
+
|
388
|
+
def set_link(name, node_or_address)
|
389
|
+
if node_or_address
|
390
|
+
# Set the link to the given SpaceTreeNode or node address.
|
391
|
+
instance_variable_set(name,
|
392
|
+
node = node_or_address.is_a?(SpaceTreeNodeLink) ?
|
393
|
+
node_or_address :
|
394
|
+
SpaceTreeNodeLink.new(@tree, node_or_address))
|
395
|
+
# Link the node back to this node via the parent variable.
|
396
|
+
node.parent = self
|
397
|
+
else
|
398
|
+
# Clear the node link.
|
399
|
+
instance_variable_set(name, nil)
|
400
|
+
end
|
401
|
+
write_node
|
402
|
+
end
|
403
|
+
|
404
|
+
def parent=(p)
|
405
|
+
@parent = p ? SpaceTreeNodeLink.new(@tree, p) : nil
|
406
|
+
write_node
|
407
|
+
end
|
408
|
+
# Compare this node to another node.
|
409
|
+
# @return [Boolean] true if node address is identical, false otherwise
|
410
|
+
def ==(node)
|
411
|
+
node && @node_address == node.node_address
|
412
|
+
end
|
413
|
+
|
414
|
+
# Collects address and size touples of all nodes in the tree with a
|
415
|
+
# depth-first strategy and stores them in an Array.
|
416
|
+
# @return [Array] Array with [ address, size ] touples.
|
417
|
+
def to_a
|
418
|
+
ary = []
|
419
|
+
|
420
|
+
each do |node, mode, stack|
|
421
|
+
if mode == :on_enter
|
422
|
+
ary << [ node.blob_address, node.size ]
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
ary
|
427
|
+
end
|
428
|
+
|
429
|
+
# Textual version of the node data. It has the form
|
430
|
+
# node_address:[blob_address, size] ^parent_node_address
|
431
|
+
# <smaller_node_address >larger_node_address
|
432
|
+
# @return [String]
|
433
|
+
def to_s
|
434
|
+
s = "#{@node_address}:[#{@blob_address}, #{@size}]"
|
435
|
+
if @parent
|
436
|
+
begin
|
437
|
+
s += " ^#{@parent.node_address}"
|
438
|
+
rescue
|
439
|
+
s += ' ^@'
|
440
|
+
end
|
441
|
+
end
|
442
|
+
if @smaller
|
443
|
+
begin
|
444
|
+
s += " <#{@smaller.node_address}"
|
445
|
+
rescue
|
446
|
+
s += ' <@'
|
447
|
+
end
|
448
|
+
end
|
449
|
+
if @equal
|
450
|
+
begin
|
451
|
+
s += " =#{@equal.node_address}"
|
452
|
+
rescue
|
453
|
+
s += ' =@'
|
454
|
+
end
|
455
|
+
end
|
456
|
+
if @larger
|
457
|
+
begin
|
458
|
+
s += " >#{@larger.node_address}"
|
459
|
+
rescue
|
460
|
+
s += ' >@'
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
s
|
465
|
+
end
|
466
|
+
|
467
|
+
# Check this node and all sub nodes for possible structural or logical
|
468
|
+
# errors.
|
469
|
+
# @param flat_file [FlatFile] If given, check that the space is also
|
470
|
+
# present in the given flat file.
|
471
|
+
# @return [false,true] True if OK, false otherwise
|
472
|
+
def check(flat_file)
|
473
|
+
node_counter = 0
|
474
|
+
max_depth = 0
|
475
|
+
|
476
|
+
each do |node, mode, stack|
|
477
|
+
max_depth = stack.size if stack.size > max_depth
|
478
|
+
|
479
|
+
case mode
|
480
|
+
when :smaller
|
481
|
+
if node.smaller
|
482
|
+
return false unless node.check_node_link('smaller', stack)
|
483
|
+
smaller_node = node.smaller
|
484
|
+
if smaller_node.size >= node.size
|
485
|
+
PEROBS.log.error "Smaller SpaceTreeNode size " +
|
486
|
+
"(#{smaller_node}) is not smaller than #{node}"
|
487
|
+
return false
|
488
|
+
end
|
489
|
+
end
|
490
|
+
when :equal
|
491
|
+
if node.equal
|
492
|
+
return false unless node.check_node_link('equal', stack)
|
493
|
+
equal_node = node.equal
|
494
|
+
|
495
|
+
if equal_node.smaller || equal_node.larger
|
496
|
+
PEROBS.log.error "Equal node #{equal_node} must not have " +
|
497
|
+
"smaller/larger childs"
|
498
|
+
return false
|
499
|
+
end
|
500
|
+
|
501
|
+
if node.size != equal_node.size
|
502
|
+
PEROBS.log.error "Equal SpaceTreeNode size (#{equal_node}) is " +
|
503
|
+
"not equal parent node #{node}"
|
504
|
+
return false
|
505
|
+
end
|
506
|
+
end
|
507
|
+
when :larger
|
508
|
+
if node.larger
|
509
|
+
return false unless node.check_node_link('larger', stack)
|
510
|
+
larger_node = node.larger
|
511
|
+
if larger_node.size <= node.size
|
512
|
+
PEROBS.log.error "Larger SpaceTreeNode size " +
|
513
|
+
"(#{larger_node}) is not larger than #{node}"
|
514
|
+
return false
|
515
|
+
end
|
516
|
+
end
|
517
|
+
when :on_exit
|
518
|
+
if flat_file &&
|
519
|
+
!flat_file.has_space?(node.blob_address, node.size)
|
520
|
+
PEROBS.log.error "SpaceTreeNode has space at offset " +
|
521
|
+
"#{node.blob_address} of size #{node.size} that isn't " +
|
522
|
+
"available in the FlatFile."
|
523
|
+
return false
|
524
|
+
end
|
525
|
+
|
526
|
+
node_counter += 1
|
527
|
+
end
|
528
|
+
end
|
529
|
+
PEROBS.log.debug "#{node_counter} SpaceTree nodes checked"
|
530
|
+
PEROBS.log.debug "Maximum tree depth is #{max_depth}"
|
531
|
+
|
532
|
+
return true
|
533
|
+
end
|
534
|
+
|
535
|
+
# Check the integrity of the given sub-node link and the parent link
|
536
|
+
# pointing back to this node.
|
537
|
+
# @param link [String] 'smaller', 'equal' or 'larger'
|
538
|
+
# @param stack [Array] List of parent nodes [ address, mode ] touples
|
539
|
+
# @return [Boolean] true of OK, false otherwise
|
540
|
+
def check_node_link(link, stack)
|
541
|
+
if (node = instance_variable_get('@' + link))
|
542
|
+
# Node links must only be of class SpaceTreeNodeLink
|
543
|
+
unless node.nil? || node.is_a?(SpaceTreeNodeLink)
|
544
|
+
PEROBS.log.error "Node link #{link} of node #{to_s} " +
|
545
|
+
"is of class #{node.class}"
|
546
|
+
return false
|
547
|
+
end
|
548
|
+
|
549
|
+
# Link must not point back to self.
|
550
|
+
if node == self
|
551
|
+
PEROBS.log.error "#{link} address of node " +
|
552
|
+
"#{node.to_s} points to self #{to_s}"
|
553
|
+
return false
|
554
|
+
end
|
555
|
+
|
556
|
+
# Link must not point to any of the parent nodes.
|
557
|
+
if stack.include?(node)
|
558
|
+
PEROBS.log.error "#{link} address of node #{to_s} " +
|
559
|
+
"points to parent node #{node}"
|
560
|
+
|
561
|
+
return false
|
562
|
+
end
|
563
|
+
|
564
|
+
# Parent link of node must point back to self.
|
565
|
+
if node.parent != self
|
566
|
+
PEROBS.log.error "@#{link} node #{node.to_s} does not have parent " +
|
567
|
+
"link pointing " +
|
568
|
+
"to parent node #{to_s}. Pointing at " +
|
569
|
+
"#{node.parent.nil? ? 'nil' : node.parent.to_s} instead."
|
570
|
+
|
571
|
+
return false
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
true
|
576
|
+
end
|
577
|
+
|
578
|
+
# Convert the node and all child nodes into a tree like text form.
|
579
|
+
# @return [String]
|
580
|
+
def to_tree_s
|
581
|
+
str = ''
|
582
|
+
|
583
|
+
each do |node, mode, stack|
|
584
|
+
if mode == :on_enter
|
585
|
+
begin
|
586
|
+
branch_mark = node.parent.nil? ? '' :
|
587
|
+
node.parent.smaller == node ? '<' :
|
588
|
+
node.parent.equal == node ? '=' :
|
589
|
+
node.parent.larger == node ? '>' : '@'
|
590
|
+
|
591
|
+
str += "#{node.text_tree_prefix}#{branch_mark}-" +
|
592
|
+
"#{node.smaller || node.equal || node.larger ? 'v-' : '--'}" +
|
593
|
+
"#{node.to_s}\n"
|
594
|
+
rescue
|
595
|
+
str += "#{node.text_tree_prefix}- @@@@@@@@@@\n"
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
str
|
601
|
+
end
|
602
|
+
|
603
|
+
# The indentation and arch routing for the text tree.
|
604
|
+
# @return [String]
|
605
|
+
def text_tree_prefix
|
606
|
+
if (node = @parent)
|
607
|
+
str = '+'
|
608
|
+
else
|
609
|
+
# Prefix start for root node line
|
610
|
+
str = 'o'
|
611
|
+
end
|
612
|
+
|
613
|
+
while node
|
614
|
+
last_child = false
|
615
|
+
if node.parent
|
616
|
+
if node.parent.smaller == node
|
617
|
+
last_child = node.parent.equal.nil? && node.parent.larger.nil?
|
618
|
+
elsif node.parent.equal == node
|
619
|
+
last_child = node.parent.larger.nil?
|
620
|
+
elsif node.parent.larger == node
|
621
|
+
last_child = true
|
622
|
+
end
|
623
|
+
else
|
624
|
+
# Padding for the root node
|
625
|
+
str = ' ' + str
|
626
|
+
break
|
627
|
+
end
|
628
|
+
|
629
|
+
str = (last_child ? ' ' : '| ') + str
|
630
|
+
node = node.parent
|
631
|
+
end
|
632
|
+
|
633
|
+
str
|
634
|
+
end
|
635
|
+
|
636
|
+
private
|
637
|
+
|
638
|
+
def write_node
|
639
|
+
bytes = [ @blob_address, @size,
|
640
|
+
@parent ? @parent.node_address : 0,
|
641
|
+
@smaller ? @smaller.node_address : 0,
|
642
|
+
@equal ? @equal.node_address : 0,
|
643
|
+
@larger ? @larger.node_address : 0].pack(NODE_BYTES_FORMAT)
|
644
|
+
@tree.nodes.store_blob(@node_address, bytes)
|
645
|
+
end
|
646
|
+
|
647
|
+
def read_node
|
648
|
+
unless @node_address > 0
|
649
|
+
PEROBS.log.fatal "@node_address must be larger than 0"
|
650
|
+
end
|
651
|
+
return false unless (bytes = @tree.nodes.retrieve_blob(@node_address))
|
652
|
+
|
653
|
+
@blob_address, @size, parent_node_address,
|
654
|
+
smaller_node_address, equal_node_address,
|
655
|
+
larger_node_address = bytes.unpack(NODE_BYTES_FORMAT)
|
656
|
+
# The parent address can also be 0 as the parent can rightly point back
|
657
|
+
# to the root node which always has the address 0.
|
658
|
+
@parent = parent_node_address != 0 ?
|
659
|
+
SpaceTreeNodeLink.new(@tree, parent_node_address) : nil
|
660
|
+
@smaller = smaller_node_address != 0 ?
|
661
|
+
SpaceTreeNodeLink.new(@tree, smaller_node_address) : nil
|
662
|
+
@equal = equal_node_address != 0 ?
|
663
|
+
SpaceTreeNodeLink.new(@tree, equal_node_address) : nil
|
664
|
+
@larger = larger_node_address != 0 ?
|
665
|
+
SpaceTreeNodeLink.new(@tree, larger_node_address) : nil
|
666
|
+
|
667
|
+
true
|
668
|
+
end
|
669
|
+
|
670
|
+
end
|
671
|
+
|
672
|
+
end
|