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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/perobs/Array.rb +17 -4
- data/lib/perobs/BTreeBlob.rb +22 -23
- data/lib/perobs/BTreeDB.rb +11 -12
- data/lib/perobs/Cache.rb +5 -4
- data/lib/perobs/DataBase.rb +19 -7
- data/lib/perobs/DynamoDB.rb +1 -1
- data/lib/perobs/FixedSizeBlobFile.rb +189 -0
- data/lib/perobs/FlatFile.rb +513 -0
- data/lib/perobs/FlatFileDB.rb +249 -0
- data/lib/perobs/FreeSpaceManager.rb +204 -0
- data/lib/perobs/Hash.rb +17 -4
- data/lib/perobs/IndexTree.rb +164 -0
- data/lib/perobs/IndexTreeNode.rb +296 -0
- data/lib/perobs/Log.rb +125 -0
- data/lib/perobs/Object.rb +10 -11
- data/lib/perobs/ObjectBase.rb +18 -5
- data/lib/perobs/StackFile.rb +137 -0
- data/lib/perobs/Store.rb +85 -19
- data/lib/perobs/TreeDB.rb +276 -0
- data/lib/perobs/version.rb +1 -1
- data/test/Array_spec.rb +11 -2
- data/test/FixedSizeBlobFile_spec.rb +91 -0
- data/test/FlatFileDB_spec.rb +56 -0
- data/test/FreeSpaceManager_spec.rb +91 -0
- data/test/Hash_spec.rb +11 -2
- data/test/IndexTree_spec.rb +118 -0
- data/test/Object_spec.rb +29 -17
- data/test/StackFile_spec.rb +113 -0
- data/test/Store_spec.rb +37 -3
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cb73944d6f6f8968d868d89e19e229dfecd5fc8
|
4
|
+
data.tar.gz: d9c7598cd06bd8b88678eaec6f71f494eb1a0c1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: befb35a3bc54b7f261889bd7b6495aaaa51c75f50cbb91fef4a657fda323bacb8080f91934062d2ae28e751ab23bc6130ec033fd802d54ae4c67a86b092442a0
|
7
|
+
data.tar.gz: 013e98cd4fe0fce2063ac8a204a33152f1e96c8e22786870046a66a6aa5d6d941444c3b7caaff319404d5d59a0ad2449fec5638266b883e833833a76131b90b2
|
data/README.md
CHANGED
data/lib/perobs/Array.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# = Array.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
|
@@ -49,7 +50,7 @@ module PEROBS
|
|
49
50
|
:&, :*, :+, :-, :==, :[], :<=>, :at, :abbrev, :assoc, :bsearch, :collect,
|
50
51
|
:combination, :compact, :count, :cycle, :dclone, :drop, :drop_while,
|
51
52
|
:each, :each_index, :empty?, :eql?, :fetch, :find_index, :first,
|
52
|
-
:flatten, :frozen?, :hash, :include?, :index, :
|
53
|
+
:flatten, :frozen?, :hash, :include?, :index, :join, :last,
|
53
54
|
:length, :map, :pack, :permutation, :pretty_print, :pretty_print_cycle,
|
54
55
|
:product, :rassoc, :reject, :repeated_combination,
|
55
56
|
:repeated_permutation, :reverse, :reverse_each, :rindex, :rotate,
|
@@ -107,6 +108,7 @@ module PEROBS
|
|
107
108
|
@data.delete_if do |v|
|
108
109
|
v && v.respond_to?(:is_poxreference?) && v.id == id
|
109
110
|
end
|
111
|
+
@store.cache.cache_write(self)
|
110
112
|
end
|
111
113
|
|
112
114
|
# Restore the persistent data from a single data structure.
|
@@ -118,6 +120,17 @@ module PEROBS
|
|
118
120
|
POXReference.new(@store, v.id) : v }
|
119
121
|
end
|
120
122
|
|
123
|
+
# Textual dump for debugging purposes
|
124
|
+
# @return [String]
|
125
|
+
def inspect
|
126
|
+
"<#{self.class}:#{@_id}>\n[\n" +
|
127
|
+
@data.map do |v|
|
128
|
+
" " + (v.respond_to?(:is_poxreference?) ?
|
129
|
+
"<PEROBS::ObjectBase:#{v._id}>" : v.inspect)
|
130
|
+
end.join(",\n") +
|
131
|
+
"\n]\n"
|
132
|
+
end
|
133
|
+
|
121
134
|
private
|
122
135
|
|
123
136
|
def _serialize
|
@@ -129,9 +142,9 @@ module PEROBS
|
|
129
142
|
# objects should not be used directly. The library only exposes them
|
130
143
|
# via POXReference proxy objects.
|
131
144
|
if v.is_a?(ObjectBase)
|
132
|
-
|
145
|
+
PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
|
133
146
|
"It is stored in a PEROBS::Array at index #{@data.index(v)}. " +
|
134
|
-
'Have you used self() instead of myself() to' +
|
147
|
+
'Have you used self() instead of myself() to ' +
|
135
148
|
"get the reference of this PEROBS object?\n" +
|
136
149
|
v.inspect
|
137
150
|
end
|
data/lib/perobs/BTreeBlob.rb
CHANGED
@@ -27,6 +27,8 @@
|
|
27
27
|
|
28
28
|
require 'zlib'
|
29
29
|
|
30
|
+
require 'perobs/Log'
|
31
|
+
|
30
32
|
module PEROBS
|
31
33
|
|
32
34
|
# This class manages the usage of the data blobs in the corresponding
|
@@ -77,7 +79,7 @@ module PEROBS
|
|
77
79
|
crc32 = Zlib.crc32(raw, 0)
|
78
80
|
start_address = reserve_bytes(id, bytes, crc32)
|
79
81
|
if write_to_blobs_file(raw, start_address) != bytes
|
80
|
-
|
82
|
+
PEROBS.log.fatal 'Object length does not match written bytes'
|
81
83
|
end
|
82
84
|
write_index
|
83
85
|
end
|
@@ -118,8 +120,8 @@ module PEROBS
|
|
118
120
|
end
|
119
121
|
|
120
122
|
unless found
|
121
|
-
|
122
|
-
|
123
|
+
PEROBS.log.fatal "Cannot find an entry for ID #{'%016X' % id} " +
|
124
|
+
"#{id} to mark"
|
123
125
|
end
|
124
126
|
|
125
127
|
write_index
|
@@ -136,8 +138,7 @@ module PEROBS
|
|
136
138
|
end
|
137
139
|
|
138
140
|
return false if ignore_errors
|
139
|
-
|
140
|
-
"Cannot find an entry for ID #{'%016X' % id} to check"
|
141
|
+
PEROBS.log.fatal "Cannot find an entry for ID #{'%016X' % id} to check"
|
141
142
|
end
|
142
143
|
|
143
144
|
# Remove all entries from the index that have not been marked.
|
@@ -165,7 +166,7 @@ module PEROBS
|
|
165
166
|
# @return [TrueClass/FalseClass] Always true right now
|
166
167
|
def check(repair = false)
|
167
168
|
# Determine size of the data blobs file.
|
168
|
-
data_file_size = File.
|
169
|
+
data_file_size = File.exist?(@blobs_file_name) ?
|
169
170
|
File.size(@blobs_file_name) : 0
|
170
171
|
|
171
172
|
next_start = 0
|
@@ -173,7 +174,7 @@ module PEROBS
|
|
173
174
|
@entries.each do |entry|
|
174
175
|
# Entries should never overlap
|
175
176
|
if prev_entry && next_start > entry[START]
|
176
|
-
|
177
|
+
PEROBS.log.fatal
|
177
178
|
"#{@dir}: Index entries are overlapping\n" +
|
178
179
|
"ID: #{'%016X' % prev_entry[ID]} " +
|
179
180
|
"Start: #{prev_entry[START]} " +
|
@@ -185,7 +186,7 @@ module PEROBS
|
|
185
186
|
|
186
187
|
# Entries must fit within the data file
|
187
188
|
if next_start > data_file_size
|
188
|
-
|
189
|
+
PEROBS.log.fatal
|
189
190
|
"#{@dir}: Entry for ID #{'%016X' % entry[ID]} " +
|
190
191
|
"goes beyond 'data' file " +
|
191
192
|
"size (#{data_file_size})\n" +
|
@@ -209,8 +210,8 @@ module PEROBS
|
|
209
210
|
begin
|
210
211
|
File.write(@blobs_file_name, raw, address)
|
211
212
|
rescue => e
|
212
|
-
|
213
|
-
|
213
|
+
PEROBS.log.fatal "Cannot write blobs file #{@blobs_file_name}: " +
|
214
|
+
e.message
|
214
215
|
end
|
215
216
|
end
|
216
217
|
|
@@ -221,13 +222,12 @@ module PEROBS
|
|
221
222
|
begin
|
222
223
|
raw = File.read(@blobs_file_name, entry[BYTES], entry[START])
|
223
224
|
rescue => e
|
224
|
-
|
225
|
-
|
225
|
+
PEROBS.log.fatal "Cannot read blobs file #{@blobs_file_name}: " +
|
226
|
+
e.message
|
226
227
|
end
|
227
228
|
if Zlib.crc32(raw, 0) != entry[CRC]
|
228
|
-
|
229
|
-
|
230
|
-
"Checksum mismatch"
|
229
|
+
PEROBS.log.fatal "BTreeBlob for object #{entry[ID]} has been " +
|
230
|
+
"corrupted: Checksum mismatch"
|
231
231
|
end
|
232
232
|
|
233
233
|
raw
|
@@ -296,7 +296,7 @@ module PEROBS
|
|
296
296
|
entry_bytes = 29
|
297
297
|
entry_format = 'QQQCL'
|
298
298
|
restore_crc = false
|
299
|
-
if File.
|
299
|
+
if File.exist?(@index_file_name)
|
300
300
|
begin
|
301
301
|
File.open(@index_file_name, 'rb') do |f|
|
302
302
|
# Since version 2.3.0, all index files start with a header.
|
@@ -338,8 +338,8 @@ module PEROBS
|
|
338
338
|
begin
|
339
339
|
raw = File.read(@blobs_file_name, e[BYTES], e[START])
|
340
340
|
rescue => e
|
341
|
-
|
342
|
-
"
|
341
|
+
PEROBS.log.fatal "Cannot read blobs file " +
|
342
|
+
"#{@blobs_file_name}: #{e.message}"
|
343
343
|
end
|
344
344
|
e[CRC] = Zlib.crc32(raw)
|
345
345
|
end
|
@@ -348,8 +348,8 @@ module PEROBS
|
|
348
348
|
end
|
349
349
|
end
|
350
350
|
rescue => e
|
351
|
-
|
352
|
-
|
351
|
+
PEROBS.log.fatal "BTreeBlob file #{@index_file_name} corrupted: " +
|
352
|
+
e.message
|
353
353
|
end
|
354
354
|
end
|
355
355
|
end
|
@@ -364,9 +364,8 @@ module PEROBS
|
|
364
364
|
end
|
365
365
|
end
|
366
366
|
rescue => e
|
367
|
-
|
368
|
-
|
369
|
-
e.message
|
367
|
+
PEROBS.log.fatal "Cannot write BTreeBlob index file " +
|
368
|
+
"#{@index_file_name}: " + e.message
|
370
369
|
end
|
371
370
|
end
|
372
371
|
|
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -27,6 +27,7 @@
|
|
27
27
|
|
28
28
|
require 'fileutils'
|
29
29
|
|
30
|
+
require 'perobs/Log'
|
30
31
|
require 'perobs/DataBase'
|
31
32
|
require 'perobs/BTreeBlob'
|
32
33
|
|
@@ -69,15 +70,15 @@ module PEROBS
|
|
69
70
|
# Check and set @dir_bits, the number of bits used for each tree level.
|
70
71
|
@dir_bits = options[:dir_bits] || 12
|
71
72
|
if @dir_bits < 4 || @dir_bits > 14
|
72
|
-
|
73
|
-
|
73
|
+
PEROBS.log.fatal "dir_bits option (#{@dir_bits}) must be between 4 " +
|
74
|
+
"and 12"
|
74
75
|
end
|
75
76
|
check_option('dir_bits')
|
76
77
|
|
77
78
|
@max_blob_size = options[:max_blob_size] || 32
|
78
79
|
if @max_blob_size < 4 || @max_blob_size > 128
|
79
|
-
|
80
|
-
"
|
80
|
+
PEROBS.log.fatal "max_blob_size option (#{@max_blob_size}) must be " +
|
81
|
+
"between 4 and 128"
|
81
82
|
end
|
82
83
|
check_option('max_blob_size')
|
83
84
|
|
@@ -115,8 +116,7 @@ module PEROBS
|
|
115
116
|
begin
|
116
117
|
File.write(file_name, hash.to_json)
|
117
118
|
rescue => e
|
118
|
-
|
119
|
-
"Cannot write hash file '#{file_name}': #{e.message}"
|
119
|
+
PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
@@ -125,13 +125,12 @@ module PEROBS
|
|
125
125
|
# @return [Hash] A Hash that maps String objects to strings or numbers.
|
126
126
|
def get_hash(name)
|
127
127
|
file_name = File.join(@db_dir, name + '.json')
|
128
|
-
return ::Hash.new unless File.
|
128
|
+
return ::Hash.new unless File.exist?(file_name)
|
129
129
|
|
130
130
|
begin
|
131
131
|
json = File.read(file_name)
|
132
132
|
rescue => e
|
133
|
-
|
134
|
-
"Cannot read hash file '#{file_name}': #{e.message}"
|
133
|
+
PEROBS.log.fatal "Cannot read hash file '#{file_name}': #{e.message}"
|
135
134
|
end
|
136
135
|
JSON.parse(json, :create_additions => true)
|
137
136
|
end
|
@@ -219,8 +218,8 @@ module PEROBS
|
|
219
218
|
dir_bits = id & @dir_mask
|
220
219
|
dir_name = File.join(dir_name, @dir_format_string % dir_bits)
|
221
220
|
|
222
|
-
if Dir.
|
223
|
-
if File.
|
221
|
+
if Dir.exist?(dir_name)
|
222
|
+
if File.exist?(File.join(dir_name, 'index'))
|
224
223
|
# The directory is a blob directory and not a BTree node dir.
|
225
224
|
return BTreeBlob.new(dir_name, self)
|
226
225
|
end
|
@@ -260,7 +259,7 @@ module PEROBS
|
|
260
259
|
# contrast to BTree node directories that only contain other
|
261
260
|
# directories.
|
262
261
|
index_file = File.join(dir_name, 'index')
|
263
|
-
File.
|
262
|
+
File.exist?(index_file)
|
264
263
|
end
|
265
264
|
|
266
265
|
end
|
data/lib/perobs/Cache.rb
CHANGED
@@ -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/Store'
|
29
30
|
|
30
31
|
module PEROBS
|
@@ -55,7 +56,7 @@ module PEROBS
|
|
55
56
|
# to increase performance.
|
56
57
|
if obj.respond_to?(:is_poxreference?)
|
57
58
|
# If this condition triggers, we have a bug in the library.
|
58
|
-
|
59
|
+
PEROBS.log.fatal "POXReference objects should never be cached"
|
59
60
|
end
|
60
61
|
@reads[index(obj)] = obj
|
61
62
|
end
|
@@ -67,7 +68,7 @@ module PEROBS
|
|
67
68
|
# to increase performance.
|
68
69
|
if obj.respond_to?(:is_poxreference?)
|
69
70
|
# If this condition triggers, we have a bug in the library.
|
70
|
-
|
71
|
+
PEROBS.log.fatal "POXReference objects should never be cached"
|
71
72
|
end
|
72
73
|
|
73
74
|
if @transaction_stack.empty?
|
@@ -147,7 +148,7 @@ module PEROBS
|
|
147
148
|
def end_transaction
|
148
149
|
case @transaction_stack.length
|
149
150
|
when 0
|
150
|
-
|
151
|
+
PEROBS.log.fatal 'No ongoing transaction to end'
|
151
152
|
when 1
|
152
153
|
# All transactions completed successfully. Write all modified objects
|
153
154
|
# into the backend storage.
|
@@ -169,7 +170,7 @@ module PEROBS
|
|
169
170
|
# the transaction started.
|
170
171
|
def abort_transaction
|
171
172
|
if @transaction_stack.empty?
|
172
|
-
|
173
|
+
PEROBS.log.fatal 'No ongoing transaction to abort'
|
173
174
|
end
|
174
175
|
@transaction_stack.pop.each do |id|
|
175
176
|
@transaction_objects[id]._restore(@transaction_stack.length)
|
data/lib/perobs/DataBase.rb
CHANGED
@@ -32,6 +32,7 @@ require 'json/add/struct'
|
|
32
32
|
require 'yaml'
|
33
33
|
require 'fileutils'
|
34
34
|
|
35
|
+
require 'perobs/Log'
|
35
36
|
require 'perobs/ObjectBase'
|
36
37
|
|
37
38
|
module PEROBS
|
@@ -39,11 +40,23 @@ module PEROBS
|
|
39
40
|
# Base class for all storage back-ends.
|
40
41
|
class DataBase
|
41
42
|
|
43
|
+
# Create a new DataBase object. This method must be overwritten by the
|
44
|
+
# deriving classes and then called via their constructor.
|
42
45
|
def initialize(serializer = :json)
|
43
46
|
@serializer = serializer
|
44
47
|
@config = {}
|
45
48
|
end
|
46
49
|
|
50
|
+
# A dummy open method. Deriving classes must overload them to insert their
|
51
|
+
# open/close semantics.
|
52
|
+
def open
|
53
|
+
end
|
54
|
+
|
55
|
+
# A dummy close method. Deriving classes must overload them to insert their
|
56
|
+
# open/close semantics.
|
57
|
+
def close
|
58
|
+
end
|
59
|
+
|
47
60
|
# Serialize the given object using the object serializer.
|
48
61
|
# @param obj [ObjectBase] Object to serialize
|
49
62
|
# @return [String] Serialized version
|
@@ -58,8 +71,8 @@ module PEROBS
|
|
58
71
|
YAML.dump(obj)
|
59
72
|
end
|
60
73
|
rescue => e
|
61
|
-
|
62
|
-
|
74
|
+
PEROBS.log.fatal "Cannot serialize object as #{@serializer}: " +
|
75
|
+
e.message
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
@@ -77,9 +90,8 @@ module PEROBS
|
|
77
90
|
YAML.load(raw)
|
78
91
|
end
|
79
92
|
rescue => e
|
80
|
-
|
81
|
-
|
82
|
-
e.message
|
93
|
+
PEROBS.log.fatal "Cannot de-serialize object with #{@serializer} " +
|
94
|
+
"parser: " + e.message
|
83
95
|
end
|
84
96
|
end
|
85
97
|
|
@@ -106,11 +118,11 @@ module PEROBS
|
|
106
118
|
|
107
119
|
# Ensure that we have a directory to store the DB items.
|
108
120
|
def ensure_dir_exists(dir)
|
109
|
-
unless Dir.
|
121
|
+
unless Dir.exist?(dir)
|
110
122
|
begin
|
111
123
|
Dir.mkdir(dir)
|
112
124
|
rescue IOError => e
|
113
|
-
|
125
|
+
PEROBS.log.fatal "Cannote create DB directory '#{dir}': #{e.message}"
|
114
126
|
end
|
115
127
|
end
|
116
128
|
end
|
data/lib/perobs/DynamoDB.rb
CHANGED
@@ -0,0 +1,189 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = FixedSizeBlobFile.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
|
+
# This class implements persistent storage space for fixed size data blobs.
|
34
|
+
# The blobs can be stored and retrieved and can be deleted again. The
|
35
|
+
# FixedSizeBlobFile manages the storage of the blobs and free storage
|
36
|
+
# spaces. The files grows and shrinks as needed. A blob is referenced by its
|
37
|
+
# address.
|
38
|
+
class FixedSizeBlobFile
|
39
|
+
|
40
|
+
# Create a new stack file in the given directory with the given file name.
|
41
|
+
# @param dir [String] Directory
|
42
|
+
# @param name [String] File name
|
43
|
+
# @param entry_bytes [Fixnum] Number of bytes each entry must have
|
44
|
+
def initialize(dir, name, entry_bytes)
|
45
|
+
@file_name = File.join(dir, name + '.blobs')
|
46
|
+
@entry_bytes = entry_bytes
|
47
|
+
@free_list = StackFile.new(dir, name + '-freelist', 8)
|
48
|
+
@f = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# Open the blob file.
|
52
|
+
def open
|
53
|
+
begin
|
54
|
+
if File.exist?(@file_name)
|
55
|
+
@f = File.open(@file_name, 'rb+')
|
56
|
+
else
|
57
|
+
@f = File.open(@file_name, 'wb+')
|
58
|
+
end
|
59
|
+
rescue IOError => e
|
60
|
+
PEROBS.log.fatal "Cannot open blob file #{@file_name}: #{e.message}"
|
61
|
+
end
|
62
|
+
@free_list.open
|
63
|
+
end
|
64
|
+
|
65
|
+
# Close the blob file. This method must be called before the program is
|
66
|
+
# terminated to avoid data loss.
|
67
|
+
def close
|
68
|
+
@free_list.close
|
69
|
+
begin
|
70
|
+
@f.flush
|
71
|
+
@f.close
|
72
|
+
rescue IOError => e
|
73
|
+
PEROBS.log.fatal "Cannot close blob file #{@file_name}: #{e.message}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Flush out all unwritten data.
|
78
|
+
def sync
|
79
|
+
@free_list.sync
|
80
|
+
begin
|
81
|
+
@f.sync
|
82
|
+
rescue IOError => e
|
83
|
+
PEROBS.log.fatal "Cannot sync blob file #{@file_name}: #{e.message}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Delete all data.
|
88
|
+
def clear
|
89
|
+
@f.truncate(0)
|
90
|
+
@f.flush
|
91
|
+
@free_list.clear
|
92
|
+
end
|
93
|
+
|
94
|
+
def empty?
|
95
|
+
sync
|
96
|
+
@f.size == 0
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return the address of a free blob storage space. Addresses start at 0
|
100
|
+
# and increase linearly.
|
101
|
+
# @return [Fixnum] address of a free blob space
|
102
|
+
def free_address
|
103
|
+
if (bytes = @free_list.pop)
|
104
|
+
# Return an entry from the free list.
|
105
|
+
return bytes.unpack('Q')[0]
|
106
|
+
else
|
107
|
+
# There is currently no free entry. Return the address at the end of
|
108
|
+
# the file.
|
109
|
+
offset_to_address(@f.size)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Store the given byte blob at the specified address. If the blob space is
|
114
|
+
# already in use the content will be overwritten.
|
115
|
+
# @param address [Fixnum] Address to store the blob
|
116
|
+
# @param bytes [String] bytes to store
|
117
|
+
def store_blob(address, bytes)
|
118
|
+
if bytes.length != @entry_bytes
|
119
|
+
PEROBS.log.fatal "All stack entries must be #{@entry_bytes} " +
|
120
|
+
"long. This entry is #{bytes.length} bytes long."
|
121
|
+
end
|
122
|
+
begin
|
123
|
+
@f.seek(address_to_offset(address))
|
124
|
+
# The first byte is tha flag byte. It's set to 1 for cells that hold a
|
125
|
+
# blob. 0 for empty cells.
|
126
|
+
@f.write([ 1 ].pack('C'))
|
127
|
+
@f.write(bytes)
|
128
|
+
@f.flush
|
129
|
+
rescue IOError => e
|
130
|
+
PEROBS.log.fatal "Cannot store blob at address #{address}: #{e.message}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Retrieve a blob from the given address.
|
135
|
+
# @param address [Fixnum] Address to store the blob
|
136
|
+
# @return [String] blob bytes
|
137
|
+
def retrieve_blob(address)
|
138
|
+
begin
|
139
|
+
if (offset = address_to_offset(address)) >= @f.size
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
|
143
|
+
@f.seek(address_to_offset(address))
|
144
|
+
if (@f.read(1).unpack('C')[0] != 1)
|
145
|
+
return nil
|
146
|
+
end
|
147
|
+
bytes = @f.read(@entry_bytes)
|
148
|
+
rescue IOError => e
|
149
|
+
PEROBS.log.fatal "Cannot retrieve blob at adress #{address}: " +
|
150
|
+
e.message
|
151
|
+
end
|
152
|
+
|
153
|
+
bytes
|
154
|
+
end
|
155
|
+
|
156
|
+
# Delete the blob at the given address.
|
157
|
+
# @param address [Fixnum] Address of blob to delete
|
158
|
+
def delete_blob(address)
|
159
|
+
begin
|
160
|
+
@f.seek(address_to_offset(address))
|
161
|
+
if (@f.read(1).unpack('C')[0] != 1)
|
162
|
+
PEROBS.log.fatal "There is no blob stored at address #{address}"
|
163
|
+
end
|
164
|
+
@f.seek(address_to_offset(address))
|
165
|
+
@f.write([ 0 ].pack('C'))
|
166
|
+
rescue IOError => e
|
167
|
+
PEROBS.log.fatal "Cannot delete blob at address #{address}: " +
|
168
|
+
e.message
|
169
|
+
end
|
170
|
+
# Add the address to the free list.
|
171
|
+
@free_list.push([ address ].pack('Q'))
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
# Translate a blob address to the actual offset in the file.
|
177
|
+
def address_to_offset(address)
|
178
|
+
address * (1 + @entry_bytes)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Translate the file offset to the address of a blob.
|
182
|
+
def offset_to_address(offset)
|
183
|
+
offset / (1 + @entry_bytes)
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|