perobs 2.4.2 → 2.5.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/lib/perobs/FlatFile.rb +139 -123
- data/lib/perobs/FlatFileBlobHeader.rb +144 -0
- data/lib/perobs/FlatFileDB.rb +19 -4
- data/lib/perobs/IndexTreeNode.rb +2 -2
- data/lib/perobs/Store.rb +0 -1
- data/lib/perobs/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9df83ee37d61319185f94bcaf64a1f48083280fd
|
4
|
+
data.tar.gz: 130fbd021bfe32cad6b45e6b4063f0093552d8f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1e61da3e76e0bb4e965ca00c0d4b97105fe5a37f1272cc9a4b0d997d0f8a521547c22c3318ad0bc17eabf9059c54d7d616ad287f1d48743c9f126e17c66ec97
|
7
|
+
data.tar.gz: 0d86ab15a8ffdb18a1b8df4f27a5f2e0fc4f9619f040934ec7904b4e17bc453b383be424190951f2c2d6f1fcb309398913aba6111a360ba908a428f1629c7b67
|
data/lib/perobs/FlatFile.rb
CHANGED
@@ -28,36 +28,17 @@
|
|
28
28
|
require 'zlib'
|
29
29
|
|
30
30
|
require 'perobs/Log'
|
31
|
+
require 'perobs/FlatFileBlobHeader'
|
31
32
|
require 'perobs/IndexTree'
|
32
33
|
require 'perobs/FreeSpaceManager'
|
33
34
|
|
34
35
|
module PEROBS
|
35
36
|
|
36
37
|
# The FlatFile class manages the storage file of the FlatFileDB. It contains
|
37
|
-
# a sequence of blobs Each blob consists of
|
38
|
-
# blob data bytes.
|
39
|
-
#
|
40
|
-
# 1 Byte: Mark byte.
|
41
|
-
# Bit 0: 0 deleted entry, 1 valid entry
|
42
|
-
# Bit 1: 0 unmarked, 1 marked
|
43
|
-
# Bit 2 - 7: reserved, must be 0
|
44
|
-
# 8 bytes: Length of the data blob in bytes
|
45
|
-
# 8 bytes: ID of the value in the data blob
|
46
|
-
# 4 bytes: CRC32 checksum of the data blob
|
47
|
-
#
|
48
|
-
# If the bit 0 of the mark byte is 0, only the length is valid. The blob is
|
49
|
-
# empty. Only of bit 0 is set then entry is valid.
|
38
|
+
# a sequence of blobs Each blob consists of header and the actual
|
39
|
+
# blob data bytes.
|
50
40
|
class FlatFile
|
51
41
|
|
52
|
-
# Utility class to hold all the data that is stored in a blob header.
|
53
|
-
class Header < Struct.new(:mark, :length, :id, :crc)
|
54
|
-
end
|
55
|
-
|
56
|
-
# The 'pack()' format of the header.
|
57
|
-
BLOB_HEADER_FORMAT = 'CQQL'
|
58
|
-
# The length of the header in bytes.
|
59
|
-
BLOB_HEADER_LENGTH = 21
|
60
|
-
|
61
42
|
# Create a new FlatFile object for a database in the given path.
|
62
43
|
# @param dir [String] Directory path for the data base file
|
63
44
|
def initialize(dir)
|
@@ -125,13 +106,13 @@ module PEROBS
|
|
125
106
|
# @param id [Integer] ID of the blob to delete
|
126
107
|
def delete_obj_by_address(addr, id)
|
127
108
|
@index.delete_value(id)
|
128
|
-
header =
|
109
|
+
header = FlatFileBlobHeader.read_at(@f, addr, id)
|
129
110
|
begin
|
130
111
|
@f.seek(addr)
|
131
112
|
@f.write([ 0 ].pack('C'))
|
132
113
|
@f.flush
|
133
114
|
@space_list.add_space(addr, header.length)
|
134
|
-
rescue => e
|
115
|
+
rescue IOError => e
|
135
116
|
PEROBS.log.fatal "Cannot erase blob for ID #{header.id}: #{e.message}"
|
136
117
|
end
|
137
118
|
end
|
@@ -142,10 +123,10 @@ module PEROBS
|
|
142
123
|
t = Time.now
|
143
124
|
|
144
125
|
deleted_ids = []
|
145
|
-
each_blob_header do |pos,
|
146
|
-
if
|
147
|
-
delete_obj_by_address(pos,
|
148
|
-
deleted_ids <<
|
126
|
+
each_blob_header do |pos, header|
|
127
|
+
if header.is_valid? && !header.is_marked?
|
128
|
+
delete_obj_by_address(pos, header.id)
|
129
|
+
deleted_ids << header.id
|
149
130
|
end
|
150
131
|
end
|
151
132
|
defragmentize
|
@@ -161,11 +142,23 @@ module PEROBS
|
|
161
142
|
# @param raw_obj [String] Raw object as String
|
162
143
|
# @return [Integer] position of the written blob in the blob file
|
163
144
|
def write_obj_by_id(id, raw_obj)
|
145
|
+
crc = checksum(raw_obj)
|
146
|
+
|
147
|
+
# If the raw_obj is larger then 256 characters we will compress it to
|
148
|
+
# safe some space in the database file. For smaller strings the
|
149
|
+
# performance impact of compression is not compensated by writing
|
150
|
+
# less data to the storage.
|
151
|
+
compressed = false
|
152
|
+
if raw_obj.length > 256
|
153
|
+
raw_obj = Zlib.deflate(raw_obj)
|
154
|
+
compressed = true
|
155
|
+
end
|
156
|
+
|
164
157
|
addr, length = find_free_blob(raw_obj.length)
|
165
158
|
begin
|
166
159
|
if length != -1
|
167
160
|
# Just a safeguard so we don't overwrite current data.
|
168
|
-
header =
|
161
|
+
header = FlatFileBlobHeader.read_at(@f, addr)
|
169
162
|
if header.length != length
|
170
163
|
PEROBS.log.fatal "Length in free list (#{length}) and header " +
|
171
164
|
"(#{header.length}) don't match."
|
@@ -174,26 +167,26 @@ module PEROBS
|
|
174
167
|
PEROBS.log.fatal "Object (#{raw_obj.length}) is longer than " +
|
175
168
|
"blob space (#{header.length})."
|
176
169
|
end
|
177
|
-
if header.
|
178
|
-
PEROBS.log.fatal "
|
170
|
+
if header.is_valid?
|
171
|
+
PEROBS.log.fatal "Entry (mark: #{header.mark}) is already used."
|
179
172
|
end
|
180
173
|
end
|
181
174
|
@f.seek(addr)
|
182
|
-
|
183
|
-
|
175
|
+
FlatFileBlobHeader.new(compressed ? (1 << 2) | 1 : 1, raw_obj.length,
|
176
|
+
id, crc).write(@f)
|
184
177
|
@f.write(raw_obj)
|
185
178
|
if length != -1 && raw_obj.length < length
|
186
179
|
# The new object was not appended and it did not completely fill the
|
187
180
|
# free space. So we have to write a new header to mark the remaining
|
188
181
|
# empty space.
|
189
|
-
unless length - raw_obj.length >=
|
182
|
+
unless length - raw_obj.length >= FlatFileBlobHeader::LENGTH
|
190
183
|
PEROBS.log.fatal "Not enough space to append the empty space " +
|
191
184
|
"header (space: #{length} bytes, object: #{raw_obj.length} " +
|
192
185
|
"bytes)."
|
193
186
|
end
|
194
187
|
space_address = @f.pos
|
195
|
-
space_length = length -
|
196
|
-
|
188
|
+
space_length = length - FlatFileBlobHeader::LENGTH - raw_obj.length
|
189
|
+
FlatFileBlobHeader.new(0, space_length, 0, 0).write(@f)
|
197
190
|
# Register the new space with the space list.
|
198
191
|
@space_list.add_space(space_address, space_length) if space_length > 0
|
199
192
|
end
|
@@ -230,21 +223,31 @@ module PEROBS
|
|
230
223
|
# @param id [Integer] ID of the data blob
|
231
224
|
# @return [String] Raw object data
|
232
225
|
def read_obj_by_address(addr, id)
|
233
|
-
header =
|
226
|
+
header = FlatFileBlobHeader.read_at(@f, addr, id)
|
234
227
|
if header.id != id
|
235
228
|
PEROBS.log.fatal "Database index corrupted: Index for object " +
|
236
229
|
"#{id} points to object with ID #{header.id}"
|
237
230
|
end
|
231
|
+
|
232
|
+
buf = nil
|
233
|
+
|
238
234
|
begin
|
239
|
-
@f.seek(addr +
|
235
|
+
@f.seek(addr + FlatFileBlobHeader::LENGTH)
|
240
236
|
buf = @f.read(header.length)
|
241
|
-
|
242
|
-
PEROBS.log.fatal "Checksum failure while reading blob ID #{id}"
|
243
|
-
end
|
244
|
-
return buf
|
245
|
-
rescue => e
|
237
|
+
rescue IOError => e
|
246
238
|
PEROBS.log.fatal "Cannot read blob for ID #{id}: #{e.message}"
|
247
239
|
end
|
240
|
+
|
241
|
+
# Uncompress the data if the compression bit is set in the mark byte.
|
242
|
+
if header.is_compressed?
|
243
|
+
buf = Zlib.inflate(buf)
|
244
|
+
end
|
245
|
+
|
246
|
+
if checksum(buf) != header.crc
|
247
|
+
PEROBS.log.fatal "Checksum failure while reading blob ID #{id}"
|
248
|
+
end
|
249
|
+
|
250
|
+
buf
|
248
251
|
end
|
249
252
|
|
250
253
|
# Mark the object with the given ID.
|
@@ -259,12 +262,12 @@ module PEROBS
|
|
259
262
|
# @param addr [Integer] Offset in the file
|
260
263
|
# @param id [Integer] ID of the object
|
261
264
|
def mark_obj_by_address(addr, id)
|
262
|
-
header =
|
265
|
+
header = FlatFileBlobHeader.read_at(@f, addr, id)
|
263
266
|
begin
|
264
267
|
@f.seek(addr)
|
265
|
-
@f.write([ header.mark |
|
268
|
+
@f.write([ header.mark | (1 << 1) ].pack('C'))
|
266
269
|
@f.flush
|
267
|
-
rescue => e
|
270
|
+
rescue IOError => e
|
268
271
|
PEROBS.log.fatal "Marking of FlatFile blob with ID #{id} " +
|
269
272
|
"failed: #{e.message}"
|
270
273
|
end
|
@@ -274,8 +277,8 @@ module PEROBS
|
|
274
277
|
# @param id [Integer] ID of the object
|
275
278
|
def is_marked_by_id?(id)
|
276
279
|
if (addr = find_obj_addr_by_id(id))
|
277
|
-
header =
|
278
|
-
return
|
280
|
+
header = FlatFileBlobHeader.read_at(@f, addr, id)
|
281
|
+
return header.is_marked?
|
279
282
|
end
|
280
283
|
|
281
284
|
false
|
@@ -289,16 +292,16 @@ module PEROBS
|
|
289
292
|
total_blob_count = 0
|
290
293
|
marked_blob_count = 0
|
291
294
|
|
292
|
-
each_blob_header do |pos,
|
295
|
+
each_blob_header do |pos, header|
|
293
296
|
total_blob_count += 1
|
294
|
-
if
|
297
|
+
if header.is_valid? && header.is_marked?
|
295
298
|
# Clear all valid and marked blocks.
|
296
299
|
marked_blob_count += 1
|
297
300
|
begin
|
298
301
|
@f.seek(pos)
|
299
|
-
@f.write([ mark & 0b11111101 ].pack('C'))
|
302
|
+
@f.write([ header.mark & 0b11111101 ].pack('C'))
|
300
303
|
@f.flush
|
301
|
-
rescue => e
|
304
|
+
rescue IOError => e
|
302
305
|
PEROBS.log.fatal "Unmarking of FlatFile blob with ID #{blob_id} " +
|
303
306
|
"failed: #{e.message}"
|
304
307
|
end
|
@@ -317,10 +320,10 @@ module PEROBS
|
|
317
320
|
t = Time.now
|
318
321
|
PEROBS.log.info "Defragmenting FlatFile"
|
319
322
|
# Iterate over all entries.
|
320
|
-
each_blob_header do |pos,
|
323
|
+
each_blob_header do |pos, header|
|
321
324
|
# Total size of the current entry
|
322
|
-
entry_bytes =
|
323
|
-
if
|
325
|
+
entry_bytes = FlatFileBlobHeader::LENGTH + header.length
|
326
|
+
if header.is_valid?
|
324
327
|
# We have found a valid entry.
|
325
328
|
valid_blobs += 1
|
326
329
|
if distance > 0
|
@@ -332,14 +335,14 @@ module PEROBS
|
|
332
335
|
@f.seek(pos - distance)
|
333
336
|
@f.write(buf)
|
334
337
|
# Update the index with the new position
|
335
|
-
@index.put_value(
|
338
|
+
@index.put_value(header.id, pos - distance)
|
336
339
|
# Mark the space between the relocated current entry and the
|
337
340
|
# next valid entry as deleted space.
|
338
|
-
|
339
|
-
|
341
|
+
FlatFileBlobHeader.new(0, distance - FlatFileBlobHeader::LENGTH,
|
342
|
+
0, 0).write(@f)
|
340
343
|
@f.flush
|
341
|
-
rescue => e
|
342
|
-
PEROBS.log.fatal "Error while moving blob for ID #{
|
344
|
+
rescue IOError => e
|
345
|
+
PEROBS.log.fatal "Error while moving blob for ID #{header.id}: " +
|
343
346
|
e.message
|
344
347
|
end
|
345
348
|
end
|
@@ -361,6 +364,35 @@ module PEROBS
|
|
361
364
|
sync
|
362
365
|
end
|
363
366
|
|
367
|
+
# This method iterates over all entries in the FlatFile and removes the
|
368
|
+
# entry and inserts it again. This is useful to update all entries in
|
369
|
+
# cased the storage format has changed.
|
370
|
+
def refresh
|
371
|
+
# This iteration might look scary as we iterate over the entries while
|
372
|
+
# while we are rearranging them. Re-inserted items may be inserted
|
373
|
+
# before or at the current entry and this is fine. They also may be
|
374
|
+
# inserted after the current entry and will be re-read again unless they
|
375
|
+
# are inserted after the original file end.
|
376
|
+
file_size = @f.size
|
377
|
+
PEROBS.log.info "Refreshing the DB..."
|
378
|
+
t = Time.now
|
379
|
+
each_blob_header do |pos, header|
|
380
|
+
if header.is_valid?
|
381
|
+
buf = read_obj_by_address(pos, header.id)
|
382
|
+
delete_obj_by_address(pos, header.id)
|
383
|
+
write_obj_by_id(header.id, buf)
|
384
|
+
end
|
385
|
+
|
386
|
+
# Some re-inserted blobs may be inserted after the original file end.
|
387
|
+
# No need to process those blobs again.
|
388
|
+
break if pos >= file_size
|
389
|
+
end
|
390
|
+
PEROBS.log.info "DB refresh completed in #{Time.now - t} seconds"
|
391
|
+
|
392
|
+
# Reclaim the space saved by compressing entries.
|
393
|
+
defragmentize
|
394
|
+
end
|
395
|
+
|
364
396
|
def check(repair = false)
|
365
397
|
return unless @f
|
366
398
|
|
@@ -370,24 +402,28 @@ module PEROBS
|
|
370
402
|
|
371
403
|
# First check the database blob file. Each entry should be readable and
|
372
404
|
# correct.
|
373
|
-
each_blob_header do |pos,
|
374
|
-
if
|
405
|
+
each_blob_header do |pos, header|
|
406
|
+
if header.is_valid?
|
375
407
|
# We have a non-deleted entry.
|
376
408
|
begin
|
377
|
-
@f.seek(pos +
|
378
|
-
buf = @f.read(length)
|
379
|
-
if
|
409
|
+
@f.seek(pos + FlatFileBlobHeader::LENGTH)
|
410
|
+
buf = @f.read(header.length)
|
411
|
+
# Uncompress the data if the compression bit is set in the mark
|
412
|
+
# byte.
|
413
|
+
buf = Zlib.inflate(buf) if header.is_compressed?
|
414
|
+
|
415
|
+
if header.crc && checksum(buf) != header.crc
|
380
416
|
if repair
|
381
417
|
PEROBS.log.error "Checksum failure while checking blob " +
|
382
|
-
"with ID #{id}. Deleting object."
|
383
|
-
delete_obj_by_address(pos,
|
418
|
+
"with ID #{header.id}. Deleting object."
|
419
|
+
delete_obj_by_address(pos, header.id)
|
384
420
|
else
|
385
421
|
PEROBS.log.fatal "Checksum failure while checking blob " +
|
386
|
-
"with ID #{id}"
|
422
|
+
"with ID #{header.id}"
|
387
423
|
end
|
388
424
|
end
|
389
|
-
rescue => e
|
390
|
-
PEROBS.log.fatal "Check of blob with ID #{
|
425
|
+
rescue IOError => e
|
426
|
+
PEROBS.log.fatal "Check of blob with ID #{header.id} failed: " +
|
391
427
|
e.message
|
392
428
|
end
|
393
429
|
end
|
@@ -416,32 +452,33 @@ module PEROBS
|
|
416
452
|
@index.clear
|
417
453
|
@space_list.clear
|
418
454
|
|
419
|
-
each_blob_header do |pos,
|
420
|
-
if
|
421
|
-
@
|
455
|
+
each_blob_header do |pos, header|
|
456
|
+
if header.is_valid?
|
457
|
+
@index.put_value(header.id, pos)
|
422
458
|
else
|
423
|
-
@
|
459
|
+
@space_list.add_space(pos, header.length) if header.length > 0
|
424
460
|
end
|
425
461
|
end
|
426
462
|
end
|
427
463
|
|
428
464
|
def has_space?(address, size)
|
429
|
-
header =
|
465
|
+
header = FlatFileBlobHeader.read_at(@f, address)
|
430
466
|
header.length == size
|
431
467
|
end
|
432
468
|
|
433
469
|
def has_id_at?(id, address)
|
434
|
-
header =
|
470
|
+
header = FlatFileBlobHeader.read_at(@f, address)
|
435
471
|
header.id == id
|
436
472
|
end
|
437
473
|
|
438
474
|
def inspect
|
439
475
|
s = '['
|
440
|
-
each_blob_header do |pos,
|
441
|
-
s << "{ :pos => #{pos}, :mark => #{mark}, " +
|
442
|
-
":length => #{length}, :id => #{
|
443
|
-
|
444
|
-
|
476
|
+
each_blob_header do |pos, header|
|
477
|
+
s << "{ :pos => #{pos}, :mark => #{header.mark}, " +
|
478
|
+
":length => #{header.length}, :id => #{header.id}, " +
|
479
|
+
":crc => #{header.crc}"
|
480
|
+
if header.is_valid?
|
481
|
+
s << ", :value => #{@f.read(header.length)}"
|
445
482
|
end
|
446
483
|
s << " }\n"
|
447
484
|
end
|
@@ -452,26 +489,19 @@ module PEROBS
|
|
452
489
|
|
453
490
|
private
|
454
491
|
|
455
|
-
def
|
456
|
-
|
492
|
+
def each_blob_header(&block)
|
493
|
+
pos = 0
|
457
494
|
begin
|
458
|
-
@f.seek(
|
459
|
-
|
460
|
-
|
495
|
+
@f.seek(0)
|
496
|
+
while (header = FlatFileBlobHeader.read(@f))
|
497
|
+
yield(pos, header)
|
498
|
+
|
499
|
+
pos += FlatFileBlobHeader::LENGTH + header.length
|
500
|
+
@f.seek(pos)
|
501
|
+
end
|
502
|
+
rescue IOError => e
|
461
503
|
PEROBS.log.fatal "Cannot read blob in flat file DB: #{e.message}"
|
462
504
|
end
|
463
|
-
if buf.nil? || buf.length != BLOB_HEADER_LENGTH
|
464
|
-
PEROBS.log.fatal "Cannot read blob header " +
|
465
|
-
"#{id ? "for ID #{id} " : ''}at address " +
|
466
|
-
"#{addr}"
|
467
|
-
end
|
468
|
-
header = Header.new(*buf.unpack(BLOB_HEADER_FORMAT))
|
469
|
-
if id && header.id != id
|
470
|
-
PEROBS.log.fatal "Mismatch between FlatFile index and blob file " +
|
471
|
-
"found for entry with ID #{id}/#{header.id}"
|
472
|
-
end
|
473
|
-
|
474
|
-
return header
|
475
505
|
end
|
476
506
|
|
477
507
|
def find_free_blob(bytes)
|
@@ -480,7 +510,7 @@ module PEROBS
|
|
480
510
|
# We have not found any suitable space. Return the end of the file.
|
481
511
|
return [ @f.size, -1 ]
|
482
512
|
end
|
483
|
-
if size == bytes || size -
|
513
|
+
if size == bytes || size - FlatFileBlobHeader::LENGTH >= bytes
|
484
514
|
return [ address, size ]
|
485
515
|
end
|
486
516
|
|
@@ -490,7 +520,8 @@ module PEROBS
|
|
490
520
|
|
491
521
|
# We need a space that is large enough to hold the bytes and the gap
|
492
522
|
# header.
|
493
|
-
@space_list.get_space(bytes +
|
523
|
+
@space_list.get_space(bytes + FlatFileBlobHeader::LENGTH) ||
|
524
|
+
[ @f.size, -1 ]
|
494
525
|
end
|
495
526
|
|
496
527
|
def checksum(raw_obj)
|
@@ -498,19 +529,20 @@ module PEROBS
|
|
498
529
|
end
|
499
530
|
|
500
531
|
def cross_check_entries
|
501
|
-
each_blob_header do |pos,
|
502
|
-
if
|
503
|
-
if length > 0
|
504
|
-
unless @space_list.has_space?(pos, length)
|
532
|
+
each_blob_header do |pos, header|
|
533
|
+
if !header.is_valid?
|
534
|
+
if header.length > 0
|
535
|
+
unless @space_list.has_space?(pos, header.length)
|
505
536
|
PEROBS.log.error "FlatFile has free space " +
|
506
|
-
"(addr: #{pos}, len: #{length}) that is not in
|
537
|
+
"(addr: #{pos}, len: #{header.length}) that is not in " +
|
538
|
+
"FreeSpaceManager"
|
507
539
|
return false
|
508
540
|
end
|
509
541
|
end
|
510
542
|
else
|
511
|
-
unless @index.get_value(
|
543
|
+
unless @index.get_value(header.id) == pos
|
512
544
|
PEROBS.log.error "FlatFile blob at address #{pos} is listed " +
|
513
|
-
"in index with address #{@index.get_value(
|
545
|
+
"in index with address #{@index.get_value(header.id)}"
|
514
546
|
return false
|
515
547
|
end
|
516
548
|
end
|
@@ -519,22 +551,6 @@ module PEROBS
|
|
519
551
|
true
|
520
552
|
end
|
521
553
|
|
522
|
-
def each_blob_header(&block)
|
523
|
-
pos = 0
|
524
|
-
begin
|
525
|
-
@f.seek(0)
|
526
|
-
while (buf = @f.read(BLOB_HEADER_LENGTH))
|
527
|
-
mark, length, id, crc = buf.unpack(BLOB_HEADER_FORMAT)
|
528
|
-
yield(pos, mark, length, id, crc)
|
529
|
-
|
530
|
-
pos += BLOB_HEADER_LENGTH + length
|
531
|
-
@f.seek(pos)
|
532
|
-
end
|
533
|
-
rescue IOError => e
|
534
|
-
PEROBS.log.fatal "Cannot read blob in flat file DB: #{e.message}"
|
535
|
-
end
|
536
|
-
end
|
537
|
-
|
538
554
|
end
|
539
555
|
|
540
556
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = FlatFileBlobHeader.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
|
+
|
30
|
+
module PEROBS
|
31
|
+
|
32
|
+
# The FlatFile blob header has the following structure:
|
33
|
+
#
|
34
|
+
# 1 Byte: Mark byte.
|
35
|
+
# Bit 0: 0 deleted entry, 1 valid entry
|
36
|
+
# Bit 1: 0 unmarked, 1 marked
|
37
|
+
# Bit 2: 0 uncompressed data, 1 compressed data
|
38
|
+
# Bit 3 - 7: reserved, must be 0
|
39
|
+
# 8 bytes: Length of the data blob in bytes
|
40
|
+
# 8 bytes: ID of the value in the data blob
|
41
|
+
# 4 bytes: CRC32 checksum of the data blob
|
42
|
+
#
|
43
|
+
# If the bit 0 of the mark byte is 0, only the length is valid. The blob is
|
44
|
+
# empty. Only of bit 0 is set then entry is valid.
|
45
|
+
class FlatFileBlobHeader
|
46
|
+
|
47
|
+
# The 'pack()' format of the header.
|
48
|
+
FORMAT = 'CQQL'
|
49
|
+
# The length of the header in bytes.
|
50
|
+
LENGTH = 21
|
51
|
+
|
52
|
+
attr_reader :mark, :length, :id, :crc
|
53
|
+
|
54
|
+
# Create a new FlatFileBlobHeader with the given mark, length, id and crc.
|
55
|
+
# @param mark [Fixnum] 8 bit number, see above
|
56
|
+
# @param length [Fixnum] length of the header in bytes
|
57
|
+
# @param id [Integer] ID of the blob entry
|
58
|
+
# @param crc [Fixnum] CRC32 checksum of the blob entry
|
59
|
+
def initialize(mark, length, id, crc)
|
60
|
+
@mark = mark
|
61
|
+
@length = length
|
62
|
+
@id = id
|
63
|
+
@crc = crc
|
64
|
+
end
|
65
|
+
|
66
|
+
# Read the header from the given File.
|
67
|
+
# @param file [File]
|
68
|
+
# @return FlatFileBlobHeader
|
69
|
+
def FlatFileBlobHeader::read(file)
|
70
|
+
begin
|
71
|
+
buf = file.read(LENGTH)
|
72
|
+
rescue IOError => e
|
73
|
+
PEROBS.log.fatal "Cannot read blob header in flat file DB: #{e.message}"
|
74
|
+
end
|
75
|
+
|
76
|
+
return nil unless buf
|
77
|
+
|
78
|
+
FlatFileBlobHeader.new(*buf.unpack(FORMAT))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Read the header from the given File.
|
82
|
+
# @param file [File]
|
83
|
+
# @param addr [Integer] address in the file to start reading
|
84
|
+
# @param id [Integer] Optional ID that the header should have
|
85
|
+
# @return FlatFileBlobHeader
|
86
|
+
def FlatFileBlobHeader::read_at(file, addr, id = nil)
|
87
|
+
buf = nil
|
88
|
+
begin
|
89
|
+
file.seek(addr)
|
90
|
+
buf = file.read(LENGTH)
|
91
|
+
rescue IOError => e
|
92
|
+
PEROBS.log.fatal "Cannot read blob in flat file DB: #{e.message}"
|
93
|
+
end
|
94
|
+
if buf.nil? || buf.length != LENGTH
|
95
|
+
PEROBS.log.fatal "Cannot read blob header " +
|
96
|
+
"#{id ? "for ID #{id} " : ''}at address " +
|
97
|
+
"#{addr}"
|
98
|
+
end
|
99
|
+
header = FlatFileBlobHeader.new(*buf.unpack(FORMAT))
|
100
|
+
if id && header.id != id
|
101
|
+
PEROBS.log.fatal "Mismatch between FlatFile index and blob file " +
|
102
|
+
"found for entry with ID #{id}/#{header.id}"
|
103
|
+
end
|
104
|
+
|
105
|
+
return header
|
106
|
+
end
|
107
|
+
|
108
|
+
# Write the header to a given File.
|
109
|
+
# @param file [File]
|
110
|
+
def write(file)
|
111
|
+
begin
|
112
|
+
file.write([ @mark, @length, @id, @crc].pack(FORMAT))
|
113
|
+
rescue IOError => e
|
114
|
+
PEROBS.log.fatal "Cannot write blob header into flat file DB: " +
|
115
|
+
e.message
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return true if the header is for a non-empty blob.
|
120
|
+
def is_valid?
|
121
|
+
bit_set?(0)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return true if the blob has been marked.
|
125
|
+
def is_marked?
|
126
|
+
bit_set?(1)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Return true if the blob contains compressed data.
|
130
|
+
def is_compressed?
|
131
|
+
bit_set?(2)
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def bit_set?(n)
|
137
|
+
mask = 1 << n
|
138
|
+
@mark & mask == mask
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
data/lib/perobs/FlatFileDB.rb
CHANGED
@@ -41,7 +41,7 @@ module PEROBS
|
|
41
41
|
|
42
42
|
# This version number increases whenever the on-disk format changes in a
|
43
43
|
# way that requires conversion actions after an update.
|
44
|
-
VERSION =
|
44
|
+
VERSION = 2
|
45
45
|
|
46
46
|
attr_reader :max_blob_size
|
47
47
|
|
@@ -57,7 +57,7 @@ module PEROBS
|
|
57
57
|
# Create the database directory if it doesn't exist yet.
|
58
58
|
ensure_dir_exists(@db_dir)
|
59
59
|
PEROBS.log.open(File.join(@db_dir, 'log'))
|
60
|
-
|
60
|
+
check_version_and_upgrade
|
61
61
|
|
62
62
|
# Read the existing DB config.
|
63
63
|
@config = get_hash('config')
|
@@ -202,9 +202,9 @@ module PEROBS
|
|
202
202
|
|
203
203
|
private
|
204
204
|
|
205
|
-
def
|
205
|
+
def check_version_and_upgrade
|
206
206
|
version_file = File.join(@db_dir, 'version')
|
207
|
-
version =
|
207
|
+
version = 1
|
208
208
|
|
209
209
|
if File.exist?(version_file)
|
210
210
|
begin
|
@@ -214,6 +214,7 @@ module PEROBS
|
|
214
214
|
"'#{version_file}': " + e.message
|
215
215
|
end
|
216
216
|
else
|
217
|
+
# Early versions of PEROBS did not have a version file.
|
217
218
|
write_version_file(version_file)
|
218
219
|
end
|
219
220
|
|
@@ -221,6 +222,20 @@ module PEROBS
|
|
221
222
|
PEROBS.log.fatal "Cannot downgrade the FlatFile database from " +
|
222
223
|
"version #{version} to version #{VERSION}"
|
223
224
|
end
|
225
|
+
|
226
|
+
if version == 1
|
227
|
+
# Version 1 had no support for data compression. Make sure all entries
|
228
|
+
# are compressed to save space.
|
229
|
+
open
|
230
|
+
@flat_file.refresh
|
231
|
+
close
|
232
|
+
end
|
233
|
+
|
234
|
+
# After a successful upgrade change the version number in the DB as
|
235
|
+
# well.
|
236
|
+
if version < VERSION
|
237
|
+
write_version_file(version_file)
|
238
|
+
end
|
224
239
|
end
|
225
240
|
|
226
241
|
def write_version_file(version_file)
|
data/lib/perobs/IndexTreeNode.rb
CHANGED
@@ -190,8 +190,8 @@ module PEROBS
|
|
190
190
|
# Recursively check this node and all sub nodes. Compare the found
|
191
191
|
# ID/address pairs with the corresponding entry in the given FlatFile.
|
192
192
|
# @param flat_file [FlatFile]
|
193
|
-
# @tree_level [Fixnum] Assumed level in the tree. Must correspond
|
194
|
-
#
|
193
|
+
# @param tree_level [Fixnum] Assumed level in the tree. Must correspond
|
194
|
+
# with @nibble_idx
|
195
195
|
# @return [Boolean] true if no errors were found, false otherwise
|
196
196
|
def check(flat_file, tree_level)
|
197
197
|
if tree_level >= 16
|
data/lib/perobs/Store.rb
CHANGED
@@ -160,7 +160,6 @@ module PEROBS
|
|
160
160
|
|
161
161
|
# Copy the store content into a new Store. The arguments are identical to
|
162
162
|
# Store.new().
|
163
|
-
# @param data_base [String] the name of the database
|
164
163
|
# @param options [Hash] various options to affect the operation of the
|
165
164
|
def copy(dir, options = {})
|
166
165
|
# Make sure all objects are persisted.
|
data/lib/perobs/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,6 +74,7 @@ files:
|
|
74
74
|
- lib/perobs/DynamoDB.rb
|
75
75
|
- lib/perobs/FixedSizeBlobFile.rb
|
76
76
|
- lib/perobs/FlatFile.rb
|
77
|
+
- lib/perobs/FlatFileBlobHeader.rb
|
77
78
|
- lib/perobs/FlatFileDB.rb
|
78
79
|
- lib/perobs/FreeSpaceManager.rb
|
79
80
|
- lib/perobs/Handle.rb
|