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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7913287f5998dfad64a99f239723ecb905798fe9
4
- data.tar.gz: d667fe5eab1c96b4003640c4d924d97393fdf4b3
3
+ metadata.gz: 9df83ee37d61319185f94bcaf64a1f48083280fd
4
+ data.tar.gz: 130fbd021bfe32cad6b45e6b4063f0093552d8f3
5
5
  SHA512:
6
- metadata.gz: 0140b0b11d4f6f7c5f01743d5e361cf045c4533ab339ee92e32e72b4ddca7046684a6ac00445667c0a08d953946cc0fd93d6563a9b3482759e436151d0a548df
7
- data.tar.gz: 5b6b9101321994f98f42c153c788bfece79183a074baf01d1e2ed377a0ff929b3308947b272bca1892807e425abfd6d2decd208f07179151fbca703d76eaa8ac
6
+ metadata.gz: a1e61da3e76e0bb4e965ca00c0d4b97105fe5a37f1272cc9a4b0d997d0f8a521547c22c3318ad0bc17eabf9059c54d7d616ad287f1d48743c9f126e17c66ec97
7
+ data.tar.gz: 0d86ab15a8ffdb18a1b8df4f27a5f2e0fc4f9619f040934ec7904b4e17bc453b383be424190951f2c2d6f1fcb309398913aba6111a360ba908a428f1629c7b67
@@ -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 a 25 byte header and the actual
38
- # blob data bytes. The header has the following structure:
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 = read_blob_header(addr, id)
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, mark, length, blob_id, crc|
146
- if (mark & 3 == 1)
147
- delete_obj_by_address(pos, blob_id)
148
- deleted_ids << blob_id
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 = read_blob_header(addr)
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.mark != 0
178
- PEROBS.log.fatal "Mark (#{header.mark}) is not 0."
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
- @f.write([ 1, raw_obj.length, id, checksum(raw_obj)].
183
- pack(BLOB_HEADER_FORMAT))
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 >= BLOB_HEADER_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 - BLOB_HEADER_LENGTH - raw_obj.length
196
- @f.write([ 0, space_length, 0, 0 ].pack(BLOB_HEADER_FORMAT))
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 = read_blob_header(addr, id)
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 + BLOB_HEADER_LENGTH)
235
+ @f.seek(addr + FlatFileBlobHeader::LENGTH)
240
236
  buf = @f.read(header.length)
241
- if checksum(buf) != header.crc
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 = read_blob_header(addr, id)
265
+ header = FlatFileBlobHeader.read_at(@f, addr, id)
263
266
  begin
264
267
  @f.seek(addr)
265
- @f.write([ header.mark | 2 ].pack('C'))
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 = read_blob_header(addr, id)
278
- return (header.mark & 2) == 2
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, mark, length, blob_id, crc|
295
+ each_blob_header do |pos, header|
293
296
  total_blob_count += 1
294
- if (mark & 3 == 3)
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, mark, length, blob_id, crc|
323
+ each_blob_header do |pos, header|
321
324
  # Total size of the current entry
322
- entry_bytes = BLOB_HEADER_LENGTH + length
323
- if (mark & 1 == 1)
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(blob_id, pos - distance)
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
- @f.write([ 0, distance - BLOB_HEADER_LENGTH, 0, 0 ].
339
- pack(BLOB_HEADER_FORMAT))
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 #{blob_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, mark, length, blob_id, crc|
374
- if (mark & 1 == 1)
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 + BLOB_HEADER_LENGTH)
378
- buf = @f.read(length)
379
- if crc && checksum(buf) != crc
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, blob_id)
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 #{blob_id} failed: " +
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, mark, length, id, crc|
420
- if mark == 0
421
- @space_list.add_space(pos, length) if length > 0
455
+ each_blob_header do |pos, header|
456
+ if header.is_valid?
457
+ @index.put_value(header.id, pos)
422
458
  else
423
- @index.put_value(id, pos)
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 = read_blob_header(address)
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 = read_blob_header(address)
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, mark, length, blob_id, crc|
441
- s << "{ :pos => #{pos}, :mark => #{mark}, " +
442
- ":length => #{length}, :id => #{blob_id}, :crc => #{crc}"
443
- if mark != 0
444
- s << ", :value => #{@f.read(length)}"
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 read_blob_header(addr, id = nil)
456
- buf = nil
492
+ def each_blob_header(&block)
493
+ pos = 0
457
494
  begin
458
- @f.seek(addr)
459
- buf = @f.read(BLOB_HEADER_LENGTH)
460
- rescue => e
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 - BLOB_HEADER_LENGTH >= bytes
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 + BLOB_HEADER_LENGTH) || [ @f.size, -1 ]
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, mark, length, blob_id, crc|
502
- if mark == 0
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 FreeSpaceManager"
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(blob_id) == pos
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(blob_id)}"
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
+
@@ -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 = 1
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
- check_version
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 check_version
205
+ def check_version_and_upgrade
206
206
  version_file = File.join(@db_dir, 'version')
207
- version = 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)
@@ -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 with
194
- # @nibble_idx
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.
@@ -1,4 +1,4 @@
1
1
  module PEROBS
2
2
  # The version number
3
- VERSION = "2.4.2"
3
+ VERSION = "2.5.0"
4
4
  end
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.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-14 00:00:00.000000000 Z
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