perobs 2.5.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,8 +29,8 @@ require 'zlib'
29
29
 
30
30
  require 'perobs/Log'
31
31
  require 'perobs/FlatFileBlobHeader'
32
- require 'perobs/IndexTree'
33
- require 'perobs/FreeSpaceManager'
32
+ require 'perobs/BTree'
33
+ require 'perobs/SpaceTree'
34
34
 
35
35
  module PEROBS
36
36
 
@@ -39,34 +39,59 @@ module PEROBS
39
39
  # blob data bytes.
40
40
  class FlatFile
41
41
 
42
+ # The number of entries in a single BTree node of the index file.
43
+ INDEX_BTREE_ORDER = 65
44
+
42
45
  # Create a new FlatFile object for a database in the given path.
43
46
  # @param dir [String] Directory path for the data base file
44
47
  def initialize(dir)
45
48
  @db_dir = dir
46
49
  @f = nil
47
- @index = IndexTree.new(dir)
48
- @space_list = FreeSpaceManager.new(dir)
50
+ @index = BTree.new(@db_dir, 'index', INDEX_BTREE_ORDER)
51
+ @space_list = SpaceTree.new(@db_dir)
49
52
  end
50
53
 
51
54
  # Open the flat file for reading and writing.
52
55
  def open
53
56
  file_name = File.join(@db_dir, 'database.blobs')
57
+ new_db_created = false
54
58
  begin
55
59
  if File.exist?(file_name)
56
60
  @f = File.open(file_name, 'rb+')
57
61
  else
58
- PEROBS.log.info 'New database.blobs file created'
62
+ PEROBS.log.info "New FlatFile database '#{file_name}' created"
59
63
  @f = File.open(file_name, 'wb+')
64
+ new_db_created = true
60
65
  end
61
66
  rescue IOError => e
62
- PEROBS.log.fatal "Cannot open flat file database #{file_name}: " +
67
+ PEROBS.log.fatal "Cannot open FlatFile database #{file_name}: " +
63
68
  e.message
64
69
  end
65
70
  unless @f.flock(File::LOCK_NB | File::LOCK_EX)
66
- PEROBS.log.fatal 'Database is locked by another process'
71
+ PEROBS.log.fatal "FlatFile database '#{file_name}' is locked by " +
72
+ "another process"
73
+ end
74
+
75
+ begin
76
+ @index.open(!new_db_created)
77
+ @space_list.open
78
+ rescue FatalError
79
+ # Ensure that the index is really closed.
80
+ @index.close
81
+ # Erase it completely
82
+ @index.erase
83
+ # Then create it again.
84
+ @index.open
85
+
86
+ # Ensure that the spaces list is really closed.
87
+ @space_list.close
88
+ # Erase it completely
89
+ @space_list.erase
90
+ # Then create it again
91
+ @space_list.open
92
+
93
+ regenerate_index_and_spaces
67
94
  end
68
- @index.open
69
- @space_list.open
70
95
  end
71
96
 
72
97
  # Close the flat file. This method must be called to ensure that all data
@@ -74,10 +99,13 @@ module PEROBS
74
99
  def close
75
100
  @space_list.close
76
101
  @index.close
77
- @f.flush
78
- @f.flock(File::LOCK_UN)
79
- @f.close
80
- @f = nil
102
+
103
+ if @f
104
+ @f.flush
105
+ @f.flock(File::LOCK_UN)
106
+ @f.close
107
+ @f = nil
108
+ end
81
109
  end
82
110
 
83
111
  # Force outstanding data to be written to the filesystem.
@@ -87,6 +115,7 @@ module PEROBS
87
115
  rescue IOError => e
88
116
  PEROBS.log.fatal "Cannot sync flat file database: #{e.message}"
89
117
  end
118
+ @index.sync
90
119
  end
91
120
 
92
121
  # Delete the blob for the specified ID.
@@ -105,16 +134,10 @@ module PEROBS
105
134
  # @param addr [Integer] Address of the blob to delete
106
135
  # @param id [Integer] ID of the blob to delete
107
136
  def delete_obj_by_address(addr, id)
108
- @index.delete_value(id)
137
+ @index.remove(id)
109
138
  header = FlatFileBlobHeader.read_at(@f, addr, id)
110
- begin
111
- @f.seek(addr)
112
- @f.write([ 0 ].pack('C'))
113
- @f.flush
114
- @space_list.add_space(addr, header.length)
115
- rescue IOError => e
116
- PEROBS.log.fatal "Cannot erase blob for ID #{header.id}: #{e.message}"
117
- end
139
+ header.clear_flags
140
+ @space_list.add_space(addr, header.length)
118
141
  end
119
142
 
120
143
  # Delete all unmarked objects.
@@ -136,12 +159,23 @@ module PEROBS
136
159
  deleted_ids
137
160
  end
138
161
 
139
- # Write the given object into the file. This method assumes that no other
140
- # entry with the given ID exists already in the file.
162
+ # Write the given object into the file. This method never uses in-place
163
+ # updates for existing objects. A new copy is inserted first and only when
164
+ # the insert was successful, the old copy is deleted and the index
165
+ # updated.
141
166
  # @param id [Integer] ID of the object
142
167
  # @param raw_obj [String] Raw object as String
143
168
  # @return [Integer] position of the written blob in the blob file
144
169
  def write_obj_by_id(id, raw_obj)
170
+ # Check if we have already an object with the given ID. We'll mark it as
171
+ # outdated and save the header for later deletion. In case this
172
+ # operation is aborted or interrupted we ensure that we either have the
173
+ # old or the new version available.
174
+ if (old_addr = find_obj_addr_by_id(id))
175
+ old_header = FlatFileBlobHeader.read_at(@f, old_addr)
176
+ old_header.set_outdated_flag
177
+ end
178
+
145
179
  crc = checksum(raw_obj)
146
180
 
147
181
  # If the raw_obj is larger then 256 characters we will compress it to
@@ -168,12 +202,12 @@ module PEROBS
168
202
  "blob space (#{header.length})."
169
203
  end
170
204
  if header.is_valid?
171
- PEROBS.log.fatal "Entry (mark: #{header.mark}) is already used."
205
+ PEROBS.log.fatal "Entry (flags: #{header.flags}) is already used."
172
206
  end
173
207
  end
174
- @f.seek(addr)
175
- FlatFileBlobHeader.new(compressed ? (1 << 2) | 1 : 1, raw_obj.length,
176
- id, crc).write(@f)
208
+ flags = 1 << FlatFileBlobHeader::VALID_FLAG_BIT
209
+ flags |= (1 << FlatFileBlobHeader::COMPRESSED_FLAG_BIT) if compressed
210
+ FlatFileBlobHeader.new(@f, addr, flags, raw_obj.length, id, crc).write
177
211
  @f.write(raw_obj)
178
212
  if length != -1 && raw_obj.length < length
179
213
  # The new object was not appended and it did not completely fill the
@@ -186,12 +220,20 @@ module PEROBS
186
220
  end
187
221
  space_address = @f.pos
188
222
  space_length = length - FlatFileBlobHeader::LENGTH - raw_obj.length
189
- FlatFileBlobHeader.new(0, space_length, 0, 0).write(@f)
223
+ FlatFileBlobHeader.new(@f, space_address, 0, space_length,
224
+ 0, 0).write
190
225
  # Register the new space with the space list.
191
226
  @space_list.add_space(space_address, space_length) if space_length > 0
192
227
  end
193
- @f.flush
194
- @index.put_value(id, addr)
228
+ if old_addr
229
+ # If we had an existing object stored for the ID we have to mark
230
+ # this entry as deleted now.
231
+ old_header.clear_flags
232
+ else
233
+ @f.flush
234
+ end
235
+ # Once the blob has been written we can update the index as well.
236
+ @index.insert(id, addr)
195
237
  rescue IOError => e
196
238
  PEROBS.log.fatal "Cannot write blob for ID #{id} to FlatFileDB: " +
197
239
  e.message
@@ -204,7 +246,7 @@ module PEROBS
204
246
  # @param id [Integer] ID of the object
205
247
  # @return [Integer] Offset in the flat file or nil if not found
206
248
  def find_obj_addr_by_id(id)
207
- @index.get_value(id)
249
+ @index.get(id)
208
250
  end
209
251
 
210
252
  # Read the object with the given ID.
@@ -238,9 +280,14 @@ module PEROBS
238
280
  PEROBS.log.fatal "Cannot read blob for ID #{id}: #{e.message}"
239
281
  end
240
282
 
241
- # Uncompress the data if the compression bit is set in the mark byte.
283
+ # Uncompress the data if the compression bit is set in the flags byte.
242
284
  if header.is_compressed?
243
- buf = Zlib.inflate(buf)
285
+ begin
286
+ buf = Zlib.inflate(buf)
287
+ rescue Zlib::BufError, Zlib::DataError
288
+ PEROBS.log.fatal "Corrupted compressed block with ID " +
289
+ "#{header.id} found."
290
+ end
244
291
  end
245
292
 
246
293
  if checksum(buf) != header.crc
@@ -262,15 +309,7 @@ module PEROBS
262
309
  # @param addr [Integer] Offset in the file
263
310
  # @param id [Integer] ID of the object
264
311
  def mark_obj_by_address(addr, id)
265
- header = FlatFileBlobHeader.read_at(@f, addr, id)
266
- begin
267
- @f.seek(addr)
268
- @f.write([ header.mark | (1 << 1) ].pack('C'))
269
- @f.flush
270
- rescue IOError => e
271
- PEROBS.log.fatal "Marking of FlatFile blob with ID #{id} " +
272
- "failed: #{e.message}"
273
- end
312
+ FlatFileBlobHeader.read_at(@f, addr, id).set_mark_flag
274
313
  end
275
314
 
276
315
  # Return true if the object with the given ID is marked, false otherwise.
@@ -297,14 +336,7 @@ module PEROBS
297
336
  if header.is_valid? && header.is_marked?
298
337
  # Clear all valid and marked blocks.
299
338
  marked_blob_count += 1
300
- begin
301
- @f.seek(pos)
302
- @f.write([ header.mark & 0b11111101 ].pack('C'))
303
- @f.flush
304
- rescue IOError => e
305
- PEROBS.log.fatal "Unmarking of FlatFile blob with ID #{blob_id} " +
306
- "failed: #{e.message}"
307
- end
339
+ header.clear_mark_flag
308
340
  end
309
341
  end
310
342
  PEROBS.log.info "#{marked_blob_count} marks in #{total_blob_count} " +
@@ -315,6 +347,7 @@ module PEROBS
315
347
  # implementation. No additional space will be needed on the file system.
316
348
  def defragmentize
317
349
  distance = 0
350
+ new_file_size = 0
318
351
  deleted_blobs = 0
319
352
  valid_blobs = 0
320
353
  t = Time.now
@@ -335,17 +368,19 @@ module PEROBS
335
368
  @f.seek(pos - distance)
336
369
  @f.write(buf)
337
370
  # Update the index with the new position
338
- @index.put_value(header.id, pos - distance)
371
+ @index.insert(header.id, pos - distance)
339
372
  # Mark the space between the relocated current entry and the
340
373
  # next valid entry as deleted space.
341
- FlatFileBlobHeader.new(0, distance - FlatFileBlobHeader::LENGTH,
342
- 0, 0).write(@f)
374
+ FlatFileBlobHeader.new(@f, @f.pos, 0,
375
+ distance - FlatFileBlobHeader::LENGTH,
376
+ 0, 0).write
343
377
  @f.flush
344
378
  rescue IOError => e
345
379
  PEROBS.log.fatal "Error while moving blob for ID #{header.id}: " +
346
380
  e.message
347
381
  end
348
382
  end
383
+ new_file_size = pos + FlatFileBlobHeader::LENGTH + header.length
349
384
  else
350
385
  deleted_blobs += 1
351
386
  distance += entry_bytes
@@ -357,7 +392,7 @@ module PEROBS
357
392
  "#{'%.1f' % (distance.to_f / @f.size * 100.0)}% reclaimed"
358
393
 
359
394
  @f.flush
360
- @f.truncate(@f.size - distance)
395
+ @f.truncate(new_file_size)
361
396
  @f.flush
362
397
  @space_list.clear
363
398
 
@@ -393,56 +428,116 @@ module PEROBS
393
428
  defragmentize
394
429
  end
395
430
 
431
+ # Check (and repair) the FlatFile.
432
+ # @param repair [Boolean] True if errors should be fixed.
433
+ # @return [Integer] Number of errors found
396
434
  def check(repair = false)
397
- return unless @f
435
+ errors = 0
436
+ return errors unless @f
398
437
 
399
438
  t = Time.now
400
439
  PEROBS.log.info "Checking FlatFile database" +
401
440
  "#{repair ? ' in repair mode' : ''}..."
402
441
 
403
442
  # First check the database blob file. Each entry should be readable and
404
- # correct.
443
+ # correct and all IDs must be unique. We use a shadow index to keep
444
+ # track of the already found IDs.
445
+ new_index = BTree.new(@db_dir, 'new-index', INDEX_BTREE_ORDER)
446
+ new_index.erase
447
+ new_index.open
448
+
405
449
  each_blob_header do |pos, header|
406
450
  if header.is_valid?
407
451
  # We have a non-deleted entry.
408
452
  begin
409
453
  @f.seek(pos + FlatFileBlobHeader::LENGTH)
410
454
  buf = @f.read(header.length)
455
+ if buf.length != header.length
456
+ PEROBS.log.error "Premature end of file in blob with ID " +
457
+ "#{header.id}."
458
+ discard_damaged_blob(header) if repair
459
+ errors += 1
460
+ next
461
+ end
462
+
411
463
  # Uncompress the data if the compression bit is set in the mark
412
464
  # byte.
413
- buf = Zlib.inflate(buf) if header.is_compressed?
465
+ if header.is_compressed?
466
+ begin
467
+ buf = Zlib.inflate(buf)
468
+ rescue Zlib::BufError, Zlib::DataError
469
+ PEROBS.log.error "Corrupted compressed block with ID " +
470
+ "#{header.id} found."
471
+ discard_damaged_blob(header) if repair
472
+ errors += 1
473
+ next
474
+ end
475
+ end
414
476
 
415
477
  if header.crc && checksum(buf) != header.crc
416
- if repair
417
- PEROBS.log.error "Checksum failure while checking blob " +
418
- "with ID #{header.id}. Deleting object."
419
- delete_obj_by_address(pos, header.id)
420
- else
421
- PEROBS.log.fatal "Checksum failure while checking blob " +
422
- "with ID #{header.id}"
423
- end
478
+ PEROBS.log.error "Checksum failure while checking blob " +
479
+ "with ID #{header.id}"
480
+ discard_damaged_blob(header) if repair
481
+ errors += 1
482
+ next
424
483
  end
425
484
  rescue IOError => e
426
485
  PEROBS.log.fatal "Check of blob with ID #{header.id} failed: " +
427
486
  e.message
428
487
  end
488
+
489
+ # Check if the ID has already been found in the file.
490
+ if (previous_address = new_index.get(header.id))
491
+ PEROBS.log.error "Multiple blobs for ID #{header.id} found. " +
492
+ "Addresses: #{previous_address}, #{pos}"
493
+ previous_header = FlatFileBlobHeader.read_at(@f, previous_address,
494
+ header.id)
495
+ if repair
496
+ # We have two blobs with the same ID and we must discard one of
497
+ # them.
498
+ if header.is_outdated?
499
+ discard_damaged_blob(header)
500
+ elsif previous_header.is_outdated?
501
+ discard_damaged_blob(previous_header)
502
+ else
503
+ PEROBS.log.error "None of the blobs with same ID have " +
504
+ "the outdated flag set. Deleting the smaller one."
505
+ discard_damaged_blob(header.length < previous_header.length ?
506
+ header : previous_header)
507
+ end
508
+ next
509
+ end
510
+ else
511
+ # ID is unique so far. Add it to the shadow index.
512
+ new_index.insert(header.id, pos)
513
+ end
514
+
429
515
  end
430
516
  end
517
+ # We no longer need the new index.
518
+ new_index.close
519
+ new_index.erase
431
520
 
432
521
  # Now we check the index data. It must be correct and the entries must
433
522
  # match the blob file. All entries in the index must be in the blob file
434
523
  # and vise versa.
435
524
  begin
436
- unless @index.check(self) && @space_list.check(self) &&
437
- cross_check_entries
525
+ index_ok = @index.check do |id, address|
526
+ has_id_at?(id, address)
527
+ end
528
+ unless index_ok && @space_list.check(self) && cross_check_entries
438
529
  regenerate_index_and_spaces if repair
439
530
  end
440
531
  rescue PEROBS::FatalError
532
+ errors += 1
441
533
  regenerate_index_and_spaces if repair
442
534
  end
443
535
 
444
536
  sync if repair
445
- PEROBS.log.info "check_db completed in #{Time.now - t} seconds"
537
+ PEROBS.log.info "check_db completed in #{Time.now - t} seconds. " +
538
+ "#{errors} errors found."
539
+
540
+ errors
446
541
  end
447
542
 
448
543
  # This method clears the index tree and the free space list and
@@ -454,7 +549,7 @@ module PEROBS
454
549
 
455
550
  each_blob_header do |pos, header|
456
551
  if header.is_valid?
457
- @index.put_value(header.id, pos)
552
+ @index.insert(header.id, pos)
458
553
  else
459
554
  @space_list.add_space(pos, header.length) if header.length > 0
460
555
  end
@@ -474,7 +569,7 @@ module PEROBS
474
569
  def inspect
475
570
  s = '['
476
571
  each_blob_header do |pos, header|
477
- s << "{ :pos => #{pos}, :mark => #{header.mark}, " +
572
+ s << "{ :pos => #{pos}, :flags => #{header.flags}, " +
478
573
  ":length => #{header.length}, :id => #{header.id}, " +
479
574
  ":crc => #{header.crc}"
480
575
  if header.is_valid?
@@ -485,8 +580,6 @@ module PEROBS
485
580
  s + ']'
486
581
  end
487
582
 
488
-
489
-
490
583
  private
491
584
 
492
585
  def each_blob_header(&block)
@@ -529,6 +622,8 @@ module PEROBS
529
622
  end
530
623
 
531
624
  def cross_check_entries
625
+ errors = 0
626
+
532
627
  each_blob_header do |pos, header|
533
628
  if !header.is_valid?
534
629
  if header.length > 0
@@ -536,19 +631,25 @@ module PEROBS
536
631
  PEROBS.log.error "FlatFile has free space " +
537
632
  "(addr: #{pos}, len: #{header.length}) that is not in " +
538
633
  "FreeSpaceManager"
539
- return false
634
+ errors += 1
540
635
  end
541
636
  end
542
637
  else
543
- unless @index.get_value(header.id) == pos
638
+ unless @index.get(header.id) == pos
544
639
  PEROBS.log.error "FlatFile blob at address #{pos} is listed " +
545
- "in index with address #{@index.get_value(header.id)}"
546
- return false
640
+ "in index with address #{@index.get(header.id)}"
641
+ errors += 1
547
642
  end
548
643
  end
549
644
  end
550
645
 
551
- true
646
+ errors == 0
647
+ end
648
+
649
+ def discard_damaged_blob(header)
650
+ PEROBS.log.error "Discarding corrupted data blob for ID #{header.id} " +
651
+ "at offset #{header.addr}"
652
+ header.clear_flags
552
653
  end
553
654
 
554
655
  end
@@ -31,16 +31,17 @@ module PEROBS
31
31
 
32
32
  # The FlatFile blob header has the following structure:
33
33
  #
34
- # 1 Byte: Mark byte.
34
+ # 1 Byte: Flags byte.
35
35
  # Bit 0: 0 deleted entry, 1 valid entry
36
36
  # Bit 1: 0 unmarked, 1 marked
37
37
  # Bit 2: 0 uncompressed data, 1 compressed data
38
- # Bit 3 - 7: reserved, must be 0
38
+ # Bit 3: 0 current entry, 1 outdated entry
39
+ # Bit 4 - 7: reserved, must be 0
39
40
  # 8 bytes: Length of the data blob in bytes
40
41
  # 8 bytes: ID of the value in the data blob
41
42
  # 4 bytes: CRC32 checksum of the data blob
42
43
  #
43
- # If the bit 0 of the mark byte is 0, only the length is valid. The blob is
44
+ # If the bit 0 of the flags byte is 0, only the length is valid. The blob is
44
45
  # empty. Only of bit 0 is set then entry is valid.
45
46
  class FlatFileBlobHeader
46
47
 
@@ -48,16 +49,24 @@ module PEROBS
48
49
  FORMAT = 'CQQL'
49
50
  # The length of the header in bytes.
50
51
  LENGTH = 21
52
+ VALID_FLAG_BIT = 0
53
+ MARK_FLAG_BIT = 1
54
+ COMPRESSED_FLAG_BIT = 2
55
+ OUTDATED_FLAG_BIT = 3
51
56
 
52
- attr_reader :mark, :length, :id, :crc
57
+ attr_reader :addr, :flags, :length, :id, :crc
53
58
 
54
- # Create a new FlatFileBlobHeader with the given mark, length, id and crc.
55
- # @param mark [Fixnum] 8 bit number, see above
59
+ # Create a new FlatFileBlobHeader with the given flags, length, id and crc.
60
+ # @param file [File] the FlatFile that contains the header
61
+ # @param addr [Integer] the offset address of the header in the file
62
+ # @param flags [Fixnum] 8 bit number, see above
56
63
  # @param length [Fixnum] length of the header in bytes
57
64
  # @param id [Integer] ID of the blob entry
58
65
  # @param crc [Fixnum] CRC32 checksum of the blob entry
59
- def initialize(mark, length, id, crc)
60
- @mark = mark
66
+ def initialize(file, addr, flags, length, id, crc)
67
+ @file = file
68
+ @addr = addr
69
+ @flags = flags
61
70
  @length = length
62
71
  @id = id
63
72
  @crc = crc
@@ -68,14 +77,22 @@ module PEROBS
68
77
  # @return FlatFileBlobHeader
69
78
  def FlatFileBlobHeader::read(file)
70
79
  begin
80
+ addr = file.pos
71
81
  buf = file.read(LENGTH)
72
82
  rescue IOError => e
73
- PEROBS.log.fatal "Cannot read blob header in flat file DB: #{e.message}"
83
+ PEROBS.log.error "Cannot read blob header in flat file DB: #{e.message}"
84
+ return nil
74
85
  end
75
86
 
76
87
  return nil unless buf
77
88
 
78
- FlatFileBlobHeader.new(*buf.unpack(FORMAT))
89
+ if buf.length != LENGTH
90
+ PEROBS.log.error "Incomplete FlatFileBlobHeader: Only #{buf.length} " +
91
+ "bytes of #{LENGTH} could be read"
92
+ return nil
93
+ end
94
+
95
+ FlatFileBlobHeader.new(file, addr, *buf.unpack(FORMAT))
79
96
  end
80
97
 
81
98
  # Read the header from the given File.
@@ -96,7 +113,7 @@ module PEROBS
96
113
  "#{id ? "for ID #{id} " : ''}at address " +
97
114
  "#{addr}"
98
115
  end
99
- header = FlatFileBlobHeader.new(*buf.unpack(FORMAT))
116
+ header = FlatFileBlobHeader.new(file, addr, *buf.unpack(FORMAT))
100
117
  if id && header.id != id
101
118
  PEROBS.log.fatal "Mismatch between FlatFile index and blob file " +
102
119
  "found for entry with ID #{id}/#{header.id}"
@@ -107,35 +124,93 @@ module PEROBS
107
124
 
108
125
  # Write the header to a given File.
109
126
  # @param file [File]
110
- def write(file)
127
+ def write
111
128
  begin
112
- file.write([ @mark, @length, @id, @crc].pack(FORMAT))
129
+ @file.seek(@addr)
130
+ @file.write([ @flags, @length, @id, @crc].pack(FORMAT))
113
131
  rescue IOError => e
114
132
  PEROBS.log.fatal "Cannot write blob header into flat file DB: " +
115
133
  e.message
116
134
  end
117
135
  end
118
136
 
137
+ # Reset all the flags bit to 0. This marks the blob as invalid.
138
+ # @param file [File] The file handle of the blob file.
139
+ # @param addr [Integer] The address of the header
140
+ def clear_flags
141
+ begin
142
+ @file.seek(@addr)
143
+ @file.write([ 0 ].pack('C'))
144
+ @file.flush
145
+ rescue IOError => e
146
+ PEROBS.log.fatal "Clearing flags of FlatFileBlobHeader with ID " +
147
+ "#{@id} failed: #{e.message}"
148
+ end
149
+ end
150
+
119
151
  # Return true if the header is for a non-empty blob.
120
152
  def is_valid?
121
- bit_set?(0)
153
+ bit_set?(VALID_FLAG_BIT)
122
154
  end
123
155
 
124
156
  # Return true if the blob has been marked.
125
157
  def is_marked?
126
- bit_set?(1)
158
+ bit_set?(MARK_FLAG_BIT)
159
+ end
160
+
161
+ # Set the mark bit.
162
+ def set_mark_flag
163
+ set_flag(MARK_FLAG_BIT)
164
+ write_flags
165
+ end
166
+
167
+ # Clear the mark bit.
168
+ def clear_mark_flag
169
+ clear_flag(MARK_FLAG_BIT)
170
+ write_flags
127
171
  end
128
172
 
129
173
  # Return true if the blob contains compressed data.
130
174
  def is_compressed?
131
- bit_set?(2)
175
+ bit_set?(COMPRESSED_FLAG_BIT)
176
+ end
177
+
178
+ # Set the outdated bit. The entry will be invalid as soon as the current
179
+ # transaction has been completed.
180
+ def set_outdated_flag
181
+ set_flag(OUTDATED_FLAG_BIT)
182
+ write_flags
183
+ end
184
+
185
+ # Return true if the blob contains outdated data.
186
+ def is_outdated?
187
+ bit_set?(OUTDATED_FLAG_BIT)
132
188
  end
133
189
 
134
190
  private
135
191
 
192
+ def write_flags
193
+ begin
194
+ @file.seek(@addr)
195
+ @file.write([ @flags ].pack('C'))
196
+ @file.flush
197
+ rescue IOError => e
198
+ PEROBS.log.fatal "Writing flags of FlatFileBlobHeader with ID #{@id} " +
199
+ "failed: #{e.message}"
200
+ end
201
+ end
202
+
136
203
  def bit_set?(n)
137
204
  mask = 1 << n
138
- @mark & mask == mask
205
+ @flags & mask == mask
206
+ end
207
+
208
+ def set_flag(n)
209
+ @flags |= (1 << n)
210
+ end
211
+
212
+ def clear_flag(n)
213
+ @flags &= ~(1 << n) & 0xFF
139
214
  end
140
215
 
141
216
  end