perobs 3.0.1 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +19 -18
- data/lib/perobs.rb +2 -0
- data/lib/perobs/Array.rb +68 -21
- data/lib/perobs/BTree.rb +110 -54
- data/lib/perobs/BTreeBlob.rb +14 -13
- data/lib/perobs/BTreeDB.rb +11 -10
- data/lib/perobs/BTreeNode.rb +551 -197
- data/lib/perobs/BTreeNodeCache.rb +10 -8
- data/lib/perobs/BTreeNodeLink.rb +11 -1
- data/lib/perobs/BigArray.rb +285 -0
- data/lib/perobs/BigArrayNode.rb +1002 -0
- data/lib/perobs/BigHash.rb +246 -0
- data/lib/perobs/BigTree.rb +197 -0
- data/lib/perobs/BigTreeNode.rb +873 -0
- data/lib/perobs/Cache.rb +47 -22
- data/lib/perobs/ClassMap.rb +2 -2
- data/lib/perobs/ConsoleProgressMeter.rb +61 -0
- data/lib/perobs/DataBase.rb +4 -3
- data/lib/perobs/DynamoDB.rb +62 -20
- data/lib/perobs/EquiBlobsFile.rb +174 -59
- data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
- data/lib/perobs/FlatFile.rb +536 -242
- data/lib/perobs/FlatFileBlobHeader.rb +120 -84
- data/lib/perobs/FlatFileDB.rb +58 -27
- data/lib/perobs/FuzzyStringMatcher.rb +175 -0
- data/lib/perobs/Hash.rb +129 -35
- data/lib/perobs/IDList.rb +144 -0
- data/lib/perobs/IDListPage.rb +107 -0
- data/lib/perobs/IDListPageFile.rb +180 -0
- data/lib/perobs/IDListPageRecord.rb +142 -0
- data/lib/perobs/LockFile.rb +3 -0
- data/lib/perobs/Object.rb +28 -20
- data/lib/perobs/ObjectBase.rb +53 -10
- data/lib/perobs/PersistentObjectCache.rb +142 -0
- data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
- data/lib/perobs/ProgressMeter.rb +97 -0
- data/lib/perobs/SpaceManager.rb +273 -0
- data/lib/perobs/SpaceTree.rb +63 -47
- data/lib/perobs/SpaceTreeNode.rb +134 -115
- data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
- data/lib/perobs/StackFile.rb +1 -1
- data/lib/perobs/Store.rb +180 -70
- data/lib/perobs/version.rb +1 -1
- data/perobs.gemspec +4 -4
- data/test/Array_spec.rb +48 -39
- data/test/BTreeDB_spec.rb +2 -2
- data/test/BTree_spec.rb +50 -1
- data/test/BigArray_spec.rb +261 -0
- data/test/BigHash_spec.rb +152 -0
- data/test/BigTreeNode_spec.rb +153 -0
- data/test/BigTree_spec.rb +259 -0
- data/test/EquiBlobsFile_spec.rb +105 -5
- data/test/FNV_Hash_1a_64_spec.rb +59 -0
- data/test/FlatFileDB_spec.rb +199 -15
- data/test/FuzzyStringMatcher_spec.rb +261 -0
- data/test/Hash_spec.rb +27 -16
- data/test/IDList_spec.rb +77 -0
- data/test/LegacyDBs/LegacyDB.rb +155 -0
- data/test/LegacyDBs/version_3/class_map.json +1 -0
- data/test/LegacyDBs/version_3/config.json +1 -0
- data/test/LegacyDBs/version_3/database.blobs +0 -0
- data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
- data/test/LegacyDBs/version_3/index.blobs +0 -0
- data/test/LegacyDBs/version_3/version +1 -0
- data/test/LockFile_spec.rb +9 -6
- data/test/Object_spec.rb +5 -5
- data/test/SpaceManager_spec.rb +176 -0
- data/test/SpaceTree_spec.rb +27 -9
- data/test/Store_spec.rb +353 -206
- data/test/perobs_spec.rb +7 -3
- data/test/spec_helper.rb +9 -4
- metadata +59 -16
- data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
- data/lib/perobs/TreeDB.rb +0 -277
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = ProgressMeter.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2018 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 'time'
|
29
|
+
|
30
|
+
module PEROBS
|
31
|
+
|
32
|
+
# This is the base class for all ProgressMeter classes. It only logs into
|
33
|
+
# the PEROBS log. You need to create a derived class that overloads
|
34
|
+
# print_bar() and print_time() to provide more fancy outputs.
|
35
|
+
class ProgressMeter
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@name = nil
|
39
|
+
@max_value = nil
|
40
|
+
@current_value = nil
|
41
|
+
@start_time = nil
|
42
|
+
@end_time = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def start(name, max_value)
|
46
|
+
@name = name
|
47
|
+
unless max_value >= 0
|
48
|
+
raise ArgumentError, "Maximum value (#{max_value}) must be larger " +
|
49
|
+
"or equal to 0"
|
50
|
+
end
|
51
|
+
@max_value = max_value
|
52
|
+
@current_value = 0
|
53
|
+
@start_time = Time.now
|
54
|
+
@end_time = nil
|
55
|
+
print_bar
|
56
|
+
|
57
|
+
if block_given?
|
58
|
+
yield(self)
|
59
|
+
done
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def update(value)
|
64
|
+
return unless (value_i = value.to_i) > @current_value
|
65
|
+
|
66
|
+
@current_value = value_i
|
67
|
+
print_bar
|
68
|
+
end
|
69
|
+
|
70
|
+
def done
|
71
|
+
@end_time = Time.now
|
72
|
+
print_time
|
73
|
+
PEROBS.log.info "#{@name} completed in " +
|
74
|
+
secsToHMS(@end_time - @start_time)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def print_bar
|
80
|
+
end
|
81
|
+
|
82
|
+
def print_time
|
83
|
+
end
|
84
|
+
|
85
|
+
def secsToHMS(secs)
|
86
|
+
secs = secs.to_i
|
87
|
+
s = secs % 60
|
88
|
+
mins = secs / 60
|
89
|
+
m = mins % 60
|
90
|
+
h = mins / 60
|
91
|
+
"#{h}:#{'%02d' % m}:#{'%02d' % s}"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,273 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = SpaceManager.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2020 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/BTree'
|
29
|
+
require 'perobs/EquiBlobsFile'
|
30
|
+
require 'perobs/FlatFile'
|
31
|
+
require 'perobs/FlatFileBlobHeader'
|
32
|
+
|
33
|
+
module PEROBS
|
34
|
+
|
35
|
+
# The SpaceManager is used to keep a list of all the empty spaces in a
|
36
|
+
# FlatFileDB file. An empty space is described by its starting address and
|
37
|
+
# its length in bytes. The SpaceManager keeps a list of all the spaces and
|
38
|
+
# can find the best fit space when a new blob needs to be added to the
|
39
|
+
# FlatFileDB.
|
40
|
+
#
|
41
|
+
# The SpaceManager uses two files to store the list. The first is a file
|
42
|
+
# with the actual addresses. This is a set of linked address lists. Each
|
43
|
+
# list holds the addresses for spaces that have exactly the same size. The
|
44
|
+
# second file is a BTree file that serves as the index. It is used to map
|
45
|
+
# the length of a space to the address of the linked list for that
|
46
|
+
# particular length. The linked list consists of elements that only hold 2
|
47
|
+
# items. The actual address in the FlatFileDB and the address of the next
|
48
|
+
# entry in the linked list in the list file.
|
49
|
+
class SpaceManager
|
50
|
+
|
51
|
+
attr_reader :added_spaces, :recycled_spaces, :failed_requests
|
52
|
+
|
53
|
+
def initialize(db_dir, progressmeter, btree_order = 65)
|
54
|
+
@db_dir = db_dir
|
55
|
+
@progressmeter = progressmeter
|
56
|
+
|
57
|
+
@index = BTree.new(@db_dir, 'space_index', btree_order, @progressmeter)
|
58
|
+
# The space list contains blobs that have each 2 entries. The address of
|
59
|
+
# the space in the FlatFile and the address of the next blob in the
|
60
|
+
# space list file that is an entry for the same space size. An address
|
61
|
+
# of 0 marks the end of the list.
|
62
|
+
@list = EquiBlobsFile.new(@db_dir, 'space_list', @progressmeter, 2 * 8, 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def open
|
66
|
+
@index.open
|
67
|
+
@list.open
|
68
|
+
reset_stats
|
69
|
+
end
|
70
|
+
|
71
|
+
def close
|
72
|
+
if @index.is_open?
|
73
|
+
PEROBS.log.info "SpaceManager has currently #{@list.total_entries} " +
|
74
|
+
"used blobs and #{@list.total_spaces} unused blobs in list " +
|
75
|
+
"EquiBlobsFile"
|
76
|
+
PEROBS.log.info "#{@added_spaces} were added, #{@recycled_spaces} " +
|
77
|
+
"spaces were recycled and #{@failed_requests} requests failed"
|
78
|
+
|
79
|
+
@list.close
|
80
|
+
@index.close
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_open?
|
85
|
+
@index.is_open?
|
86
|
+
end
|
87
|
+
|
88
|
+
def sync
|
89
|
+
@list.sync
|
90
|
+
@index.sync
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_space(address, length)
|
94
|
+
if (list_entry_addr = @index.get(length))
|
95
|
+
# There is already at least one move entry for this length.
|
96
|
+
new_list_entry_addr = insert_space_in_list(address, list_entry_addr)
|
97
|
+
else
|
98
|
+
new_list_entry_addr = insert_space_in_list(address, 0)
|
99
|
+
end
|
100
|
+
@index.insert(length, new_list_entry_addr)
|
101
|
+
@added_spaces += 1
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_space?(address, length)
|
105
|
+
if (list_entry_addr = @index.get(length))
|
106
|
+
while list_entry_addr > 0
|
107
|
+
blob = @list.retrieve_blob(list_entry_addr)
|
108
|
+
space_address, next_entry_addr = blob.unpack('QQ')
|
109
|
+
return true if space_address == address
|
110
|
+
list_entry_addr = next_entry_addr
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
false
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_space(length)
|
118
|
+
# We use a simple exact fit strategy. All attempts to use a more
|
119
|
+
# elaborate scheme were actually less efficient. Non-exact matches
|
120
|
+
# generate new spaces for the remainder and fragment the blob file with
|
121
|
+
# lots of unusable small spaces. Most applications seem to have
|
122
|
+
# clustered their blob sizes around a number of popular sizes. So exact
|
123
|
+
# match is very efficient to implement and results in the highest
|
124
|
+
# probability that a space will be reused soon.
|
125
|
+
list_entry_addr = @index.get(length)
|
126
|
+
|
127
|
+
if list_entry_addr
|
128
|
+
blob = @list.retrieve_blob(list_entry_addr)
|
129
|
+
space_address, next_entry_addr = blob.unpack('QQ')
|
130
|
+
@list.delete_blob(list_entry_addr)
|
131
|
+
|
132
|
+
if next_entry_addr > 0
|
133
|
+
# Update the index entry for the length to point to the
|
134
|
+
# following space list entry.
|
135
|
+
@index.insert(length, next_entry_addr)
|
136
|
+
else
|
137
|
+
# The space list for this length is empty. Remove the entry
|
138
|
+
# from the index.
|
139
|
+
@index.remove(length)
|
140
|
+
end
|
141
|
+
@recycled_spaces += 1
|
142
|
+
|
143
|
+
# We return the length to remain compatible with the old SpaceTree
|
144
|
+
# API.
|
145
|
+
return [ space_address, length ]
|
146
|
+
end
|
147
|
+
|
148
|
+
@failed_requests += 1
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
def clear
|
153
|
+
@list.clear
|
154
|
+
@index.clear
|
155
|
+
reset_stats
|
156
|
+
end
|
157
|
+
|
158
|
+
def erase
|
159
|
+
@list.erase
|
160
|
+
@index.erase
|
161
|
+
end
|
162
|
+
|
163
|
+
def check(flat_file = nil)
|
164
|
+
sync
|
165
|
+
return false unless @index.check
|
166
|
+
return false unless @list.check
|
167
|
+
|
168
|
+
smallest_space = nil
|
169
|
+
largest_space = nil
|
170
|
+
total_space_bytes = 0
|
171
|
+
space_distribution = ::Hash.new(0)
|
172
|
+
|
173
|
+
@index.each do |length, list_entry_addr|
|
174
|
+
if list_entry_addr <= 0
|
175
|
+
PEROBS.log.error "list_entry_addr (#{list_entry_addr}) " +
|
176
|
+
"must be positive"
|
177
|
+
return false
|
178
|
+
end
|
179
|
+
|
180
|
+
# Detect smallest and largest space
|
181
|
+
if smallest_space.nil? || length < smallest_space
|
182
|
+
smallest_space = length
|
183
|
+
end
|
184
|
+
if largest_space.nil? || length > largest_space
|
185
|
+
largest_space = length
|
186
|
+
end
|
187
|
+
|
188
|
+
known_addresses = [ list_entry_addr ]
|
189
|
+
entries = 0
|
190
|
+
while list_entry_addr > 0
|
191
|
+
entries += 1
|
192
|
+
unless (blob = @list.retrieve_blob(list_entry_addr))
|
193
|
+
PEROBS.log.error "SpaceManager points to non-existing " +
|
194
|
+
"space list entry at address #{list_entry_addr}"
|
195
|
+
return false
|
196
|
+
end
|
197
|
+
space_address, next_entry_addr = blob.unpack('QQ')
|
198
|
+
|
199
|
+
if known_addresses.include?(next_entry_addr)
|
200
|
+
PEROBS.log.error "Space list is cyclic: "
|
201
|
+
"#{known_addresses + next_entry_addr}"
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
if flat_file &&
|
205
|
+
!flat_file.has_space?(space_address, length)
|
206
|
+
PEROBS.log.error "SpaceManager has space at offset " +
|
207
|
+
"#{space_address} of size #{length} that isn't " +
|
208
|
+
"available in the FlatFile."
|
209
|
+
return false
|
210
|
+
end
|
211
|
+
list_entry_addr = next_entry_addr
|
212
|
+
end
|
213
|
+
|
214
|
+
total_space_bytes += length * entries
|
215
|
+
space_distribution[msb(length)] += entries
|
216
|
+
end
|
217
|
+
|
218
|
+
PEROBS.log.info "SpaceManager stats: smallest: #{smallest_space}; " +
|
219
|
+
"largest: #{largest_space}; total bytes: #{total_space_bytes}; " +
|
220
|
+
"distribution: " +
|
221
|
+
"#{space_distribution.map { |l, c| "#{2 ** (l - 1)}-#{2 ** l - 1}:#{c}; " }}"
|
222
|
+
|
223
|
+
true
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_a
|
227
|
+
a = []
|
228
|
+
|
229
|
+
@index.each do |length, list_entry_addr|
|
230
|
+
while list_entry_addr > 0
|
231
|
+
blob = @list.retrieve_blob(list_entry_addr)
|
232
|
+
space_address, next_entry_addr = blob.unpack('QQ')
|
233
|
+
|
234
|
+
a << [ space_address, length ]
|
235
|
+
|
236
|
+
list_entry_addr = next_entry_addr
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
a.sort { |a, b| a[0] <=> b[0] }
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def insert_space_in_list(next_element_addr, space_address)
|
246
|
+
blob = [ next_element_addr, space_address ].pack('QQ')
|
247
|
+
@list.store_blob(blob_addr = @list.free_address, blob)
|
248
|
+
|
249
|
+
blob_addr
|
250
|
+
end
|
251
|
+
|
252
|
+
def msb(i)
|
253
|
+
return 63 if i < 0
|
254
|
+
|
255
|
+
bit = 0
|
256
|
+
while (i > 0)
|
257
|
+
bit += 1
|
258
|
+
i = i >> 1
|
259
|
+
end
|
260
|
+
|
261
|
+
bit
|
262
|
+
end
|
263
|
+
|
264
|
+
def reset_stats
|
265
|
+
@added_spaces = 0
|
266
|
+
@recycled_spaces = 0
|
267
|
+
@failed_requests = 0
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
data/lib/perobs/SpaceTree.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = SpaceTree.rb -- Persistent Ruby Object Store
|
4
4
|
#
|
5
|
-
# Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
|
5
|
+
# Copyright (c) 2016, 2017, 2018 by Chris Schlaeger <chris@taskjuggler.org>
|
6
6
|
#
|
7
7
|
# MIT License
|
8
8
|
#
|
@@ -27,7 +27,7 @@
|
|
27
27
|
|
28
28
|
require 'perobs/Log'
|
29
29
|
require 'perobs/EquiBlobsFile'
|
30
|
-
require 'perobs/
|
30
|
+
require 'perobs/PersistentObjectCache'
|
31
31
|
require 'perobs/SpaceTreeNode'
|
32
32
|
require 'perobs/FlatFile'
|
33
33
|
|
@@ -40,40 +40,63 @@ module PEROBS
|
|
40
40
|
# size which drastically simplifies the backing store operation.
|
41
41
|
class SpaceTree
|
42
42
|
|
43
|
-
attr_reader :nodes
|
43
|
+
attr_reader :nodes, :cache, :progressmeter
|
44
44
|
|
45
45
|
# Manage the free spaces tree in the specified directory
|
46
46
|
# @param dir [String] directory path of an existing directory
|
47
|
-
def initialize(dir)
|
47
|
+
def initialize(dir, progressmeter)
|
48
48
|
@dir = dir
|
49
|
+
@progressmeter = progressmeter
|
49
50
|
|
50
51
|
# This EquiBlobsFile contains the nodes of the SpaceTree.
|
51
|
-
@nodes = EquiBlobsFile.new(@dir, 'database_spaces',
|
52
|
+
@nodes = EquiBlobsFile.new(@dir, 'database_spaces', progressmeter,
|
52
53
|
SpaceTreeNode::NODE_BYTES, 1)
|
53
54
|
|
54
|
-
|
55
|
+
# Benchmark runs showed a cache size of 128 to be a good compromise
|
56
|
+
# between read and write performance trade-offs and memory consumption.
|
57
|
+
@cache = PersistentObjectCache.new(256, 256, SpaceTreeNode, self)
|
55
58
|
end
|
56
59
|
|
57
60
|
# Open the SpaceTree file.
|
58
61
|
def open
|
59
62
|
@nodes.open
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
|
63
|
+
@cache.clear
|
64
|
+
node = @nodes.total_entries == 0 ?
|
65
|
+
SpaceTreeNode::create(self) :
|
66
|
+
SpaceTreeNode::load(self, @nodes.first_entry)
|
67
|
+
@root_address = node.node_address
|
64
68
|
end
|
65
69
|
|
66
70
|
# Close the SpaceTree file.
|
67
71
|
def close
|
72
|
+
@cache.flush(true)
|
68
73
|
@nodes.close
|
69
|
-
@
|
70
|
-
@
|
74
|
+
@root_address = nil
|
75
|
+
@cache.clear
|
71
76
|
end
|
72
77
|
|
78
|
+
# @return true if file is currently open.
|
79
|
+
def is_open?
|
80
|
+
!@root_address.nil?
|
81
|
+
end
|
82
|
+
|
83
|
+
# Flush all pending writes to the file system.
|
84
|
+
def sync
|
85
|
+
@cache.flush(true)
|
86
|
+
@nodes.sync
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set a new root node for the SpaceTree
|
90
|
+
# @param node [SpaceTreeNode]
|
73
91
|
def set_root(node)
|
74
|
-
@
|
92
|
+
@root_address = node.node_address
|
93
|
+
@nodes.first_entry = node.node_address
|
75
94
|
end
|
76
95
|
|
96
|
+
# Return the SpaceTreeNode that is the root of the SpaceTree.
|
97
|
+
def root
|
98
|
+
@root_address ? @cache.get(@root_address) : nil
|
99
|
+
end
|
77
100
|
|
78
101
|
# Erase the SpaceTree file. This method cannot be called while the file is
|
79
102
|
# open.
|
@@ -88,7 +111,13 @@ module PEROBS
|
|
88
111
|
if size <= 0
|
89
112
|
PEROBS.log.fatal "Size (#{size}) must be larger than 0."
|
90
113
|
end
|
91
|
-
|
114
|
+
# The following check is fairly costly and should never trigger unless
|
115
|
+
# there is a bug in the PEROBS code. Only use this for debugging.
|
116
|
+
#if has_space?(address, size)
|
117
|
+
# PEROBS.log.fatal "The space with address #{address} and size " +
|
118
|
+
# "#{size} can't be added twice."
|
119
|
+
#end
|
120
|
+
root.add_space(address, size)
|
92
121
|
end
|
93
122
|
|
94
123
|
# Get a space that has at least the requested size.
|
@@ -99,10 +128,10 @@ module PEROBS
|
|
99
128
|
PEROBS.log.fatal "Size (#{size}) must be larger than 0."
|
100
129
|
end
|
101
130
|
|
102
|
-
if (address_size =
|
131
|
+
if (address_size = root.find_matching_space(size))
|
103
132
|
# First we try to find an exact match.
|
104
133
|
return address_size
|
105
|
-
elsif (address_size =
|
134
|
+
elsif (address_size = root.find_equal_or_larger_space(size))
|
106
135
|
return address_size
|
107
136
|
else
|
108
137
|
return nil
|
@@ -112,38 +141,15 @@ module PEROBS
|
|
112
141
|
# Delete the node at the given address in the SpaceTree file.
|
113
142
|
# @param address [Integer] address in file
|
114
143
|
def delete_node(address)
|
115
|
-
@
|
144
|
+
@cache.delete(address)
|
116
145
|
@nodes.delete_blob(address)
|
117
146
|
end
|
118
147
|
|
119
148
|
# Clear all pools and forget any registered spaces.
|
120
149
|
def clear
|
121
150
|
@nodes.clear
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@node_cache.insert(@root)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Create a new SpaceTreeNode.
|
128
|
-
# @param parent [SpaceTreeNode] parent node
|
129
|
-
# @param blob_address [Integer] address of the free space
|
130
|
-
# @param size [Integer] size of the free space
|
131
|
-
def new_node(parent, blob_address, size)
|
132
|
-
node = SpaceTreeNode.new(self, parent, nil, blob_address, size)
|
133
|
-
@node_cache.insert(node)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Return the SpaceTreeNode that matches the given node address. If a blob
|
137
|
-
# address and size are given, a new node is created instead of being read
|
138
|
-
# from the file.
|
139
|
-
# @param node_address [Integer] Address of the node in the SpaceTree file
|
140
|
-
# @return [SpaceTreeNode]
|
141
|
-
def get_node(node_address)
|
142
|
-
if (node = @node_cache.get(node_address))
|
143
|
-
return node
|
144
|
-
end
|
145
|
-
|
146
|
-
@node_cache.insert(SpaceTreeNode.new(self, nil, node_address))
|
151
|
+
@cache.clear
|
152
|
+
@root_address = SpaceTreeNode::create(self).node_address
|
147
153
|
end
|
148
154
|
|
149
155
|
# Check if there is a space in the free space lists that matches the
|
@@ -152,27 +158,37 @@ module PEROBS
|
|
152
158
|
# @param [Integer] size Length of the space in bytes
|
153
159
|
# @return [Boolean] True if space is found, false otherwise
|
154
160
|
def has_space?(address, size)
|
155
|
-
|
161
|
+
root.has_space?(address, size)
|
156
162
|
end
|
157
163
|
|
158
164
|
# Check if the index is OK and matches the flat_file data (if given).
|
159
165
|
# @param flat_file [FlatFile] Flat file to compare with
|
160
166
|
# @return True if space list matches, flase otherwise
|
161
167
|
def check(flat_file = nil)
|
162
|
-
|
163
|
-
@
|
168
|
+
sync
|
169
|
+
return false unless @nodes.check
|
170
|
+
root.check(flat_file, @nodes.total_entries)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Iterate over all entries and yield address and size.
|
174
|
+
def each
|
175
|
+
root.each do |node, mode, stack|
|
176
|
+
if mode == :on_enter
|
177
|
+
yield(node.blob_address, node.size)
|
178
|
+
end
|
179
|
+
end
|
164
180
|
end
|
165
181
|
|
166
182
|
# Complete internal tree data structure as textual tree.
|
167
183
|
# @return [String]
|
168
184
|
def to_s
|
169
|
-
|
185
|
+
root.to_tree_s
|
170
186
|
end
|
171
187
|
|
172
188
|
# Convert the tree into an Array of [address, size] touples.
|
173
189
|
# @return [Array]
|
174
190
|
def to_a
|
175
|
-
|
191
|
+
root.to_a
|
176
192
|
end
|
177
193
|
|
178
194
|
end
|