perobs 3.0.1 → 4.3.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.
- 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
|