module Daybreak
# Records define how data is serialized and read from disk.
class Record
# Thrown when either key or data is missing
class UnnacceptableDataError < Exception; end
# Thrown when there is a CRC mismatch between the data from the disk
# and what was written to disk previously.
class CorruptDataError < Exception; end
include Locking
attr_accessor :key, :data
def initialize(key = nil, data = nil)
@key = key
@data = data
end
# Read a record from an open io source, check the CRC, and set @key
# and @data.
# @param [#read] io an IO instance to read from
def read(io)
lock io do
@key = read_bytes(io)
@data = read_bytes(io)
crc = io.read(4)
raise CorruptDataError, "CRC mismatch #{crc} should be #{crc_string}" unless crc == crc_string
end
self
end
# The serialized representation of the key value pair plus the CRC.
# @return [String]
def representation
raise UnnacceptableDataError, "key and data must be defined" if @key.nil? || @data.nil?
byte_string + crc_string
end
# Create a new record to read from IO.
# @param [#read] io an IO instance to read from
def self.read(io)
new.read(io)
end
private
def byte_string
@byte_string ||= part(@key) + part(@data)
end
def crc_string
[Zlib.crc32(byte_string, 0)].pack('N')
end
def read_bytes(io)
raw = io.read(4)
length = raw.unpack('N')[0]
io.read(length)
end
def part(data)
[data.bytesize].pack('N') + data
end
end
end