class Pocolog::Logfiles

Low-level access to (consistent) set of logfiles.

The pocolog logfiles can be split during recording in order to limit each file's size. This class allows to provide a list of files (to #open) and have a uniform access to the data.

Files are indexed (i.e. a .idx file gets generated along with the log file) to provide quick random access.

A higher level access is provided by the streams (DataStream), that can be retrieved with stream, stream_from_type and stream_from_index

Format

Pocolog files are made of:

See the tasks/logging.hh file in Rock's tools/logger package for a detailed description of each block's layout.

Constants

BLOCK_HEADER_SIZE

The size of the generic block header

BlockInfo

Structure that stores additional information about a block

COMPRESSION_MIN_SIZE

Data blocks of less than COMPRESSION_MIN_SIZE are never compressed

COMPRESSION_THRESHOLD

If the size gained by compressing is below this value, do not save in compressed form

DATA_BLOCK_HEADER_FORMAT

Formatting string for Array.pack to create a data block

DATA_HEADER_SIZE

The size of a data header, excluding the generic block header

DataHeader
FORMAT_VERSION

Format version ID. Increment this when the file format changes in a non-backward-compatible way

TIME_PADDING
TIME_SIZE

The size of a time in a block header

Attributes

basename[RW]

The basename for creating new log files. The files names are

#{basename}.#{index}.log
block_info[R]

The BlockInfo instance storing information about the last block read

endian_swap[R]

True if the host byte order is not the same than the file byte order

io[R]

An array of IO objects representing the underlying files

registry[R]

The type registry for these logfiles, as a Typelib::Registry instance

Public Class Methods

append(basename) click to toggle source

Open an already existing set of log files or create it

# File lib/pocolog/file.rb, line 219
def self.append(basename)
    io = []
    i = 0
    while File.readable?(path = "#{basename}.#{i}.log")
        io << File.open(path, 'a+')
        i += 1
    end

    if io.empty?
        return create(basename)
    end

    file = Logfiles.new(*io)
    file.basename = basename
    file
end
compress(from_io, to_io) click to toggle source
# File lib/pocolog/convert.rb, line 77
def self.compress(from_io, to_io)
    from = Logfiles.new(from_io)
    from.read_prologue
    write_prologue(to_io, from.endian_swap ^ Pocolog.big_endian?)

    compressed = [1].pack('C')
    buffer = "".encode('BINARY')
    from.each_block(true) do |block_info|
        if block_info.type != DATA_BLOCK || block_info.payload_size < COMPRESSION_MIN_SIZE
            copy_block(block_info, from_io, to_io, buffer)
        else
            # Get the data header
            data_header = from.data_header
            if data_header.compressed
                copy_block(block_info, from_io, to_io, buffer)
                next
            end

            compressed = Zlib::Deflate.deflate(from.data)
            delta = data_header.size - compressed.size
            if Float(delta) / data_header.size > COMPRESSION_THRESHOLD
                # Save it in compressed form
                payload_size = DATA_HEADER_SIZE + compressed.size
                to_io.write([block_info.type, block_info.index, payload_size].pack('CxvV'))
                from_io.seek(block_info.pos + BLOCK_HEADER_SIZE)
                from_io.read(TIME_SIZE * 2, buffer)
                to_io.write(buffer << [compressed.size, 1].pack('VC'))
                to_io.write(compressed)
            else
                copy_block(block_info, from_io, to_io, buffer)
            end
        end
    end
end
copy_block(block_info, from_io, to_io, buffer) { |buffer| ... } click to toggle source
# File lib/pocolog/convert.rb, line 12
def self.copy_block(block_info, from_io, to_io, buffer)
    # copy the block as-is
    from_io.seek(block_info.pos)
    buffer = from_io.read(BLOCK_HEADER_SIZE + block_info.payload_size, buffer)
    if block_given?
        buffer = yield(buffer)
    end
    to_io.write(buffer)
end
create(basename, registry = nil) click to toggle source

Create an empty log file using basename to build its name. Namely, it will create a new file named <basename>.0.log. Then, calls to new_file would create <basename>.1.log and so on

# File lib/pocolog/file.rb, line 206
def self.create(basename, registry = nil)
    if !registry
        registry = Typelib::Registry.new
        Typelib::Registry.add_standard_cxx_types(registry)
    end
    file = Logfiles.new(registry)
    file.basename = basename
    file.new_file

    file
end
from_version_1(from, to_io, big_endian) click to toggle source

Converts a version 1 logfile. Modifications:

  • no prologue

  • no compressed flag on data blocks

  • time was written as [type, sec, usec, padding], with each field a 32-bit integer

# File lib/pocolog/convert.rb, line 27
def self.from_version_1(from, to_io, big_endian)
    write_prologue(to_io, big_endian)
    from_io = from.rio

    buffer = ""
    uncompressed = [0].pack('C')
    from.each_block(true, false) do |block_info|
        if block_info.type == STREAM_BLOCK
            copy_block(block_info, from_io, to_io, buffer)
        elsif block_info.type == CONTROL_BLOCK
            # remove the fields in time structure
            to_io.write([block_info.type, block_info.index, block_info.payload_size - 16].pack('CxvV'))
            from_io.seek(block_info.pos + BLOCK_HEADER_SIZE + 4)
            to_io.write(from_io.read(8))
            from_io.seek(4, IO::SEEK_CUR)
            to_io.write(from_io.read(1))
            from_io.seek(4, IO::SEEK_CUR)
            to_io.write(from_io.read(8))
        else
            size_offset = - 16 + 1

            to_io.write([block_info.type, block_info.index, block_info.payload_size + size_offset].pack('CxvV'))
            from_io.seek(block_info.pos + BLOCK_HEADER_SIZE + 4)
            to_io.write(from_io.read(8))
            from_io.seek(8, IO::SEEK_CUR)
            to_io.write(from_io.read(8))
            from_io.seek(4, IO::SEEK_CUR)
            to_io.write(from_io.read(4))
            to_io.write(uncompressed)
            from_io.read(block_info.payload_size - (DATA_HEADER_SIZE - size_offset), buffer)
            to_io.write(buffer)
        end
    end
end
open(io1, io2) click to toggle source
open(io1, io2, registry)

This is usually not used directly. Most users want to use Pocolog.open to read existing file(s), and Pocolog.create to create new ones.

Creates a new Logfiles object to read the given IO objects. If the last argument is a Typelib::Registry instance, update this registry with the type definitions found in the logfile.

Providing a type registry guarantees that you get an error if the logfile's types do not match the type definitions found in the registry.

# File lib/pocolog/file.rb, line 102
def initialize(*io)
    if io.last.kind_of?(Typelib::Registry)
        @registry = io.pop
    end

    @io          = io
    @io_size     = io.map { |rio| rio.stat.size }
    @streams     = nil
    @block_info  = BlockInfo.new
    @compress    = true
    @data_header_buffer = ""
    rewind
    if io.empty?
        # When opening existing files, @streams is going to be
        # initialized in #streams. However, if we are creating a new set
        # (i.e. io.empty? == true), we also need to tell the system that
        # there currently are no streams available.
        @streams = Array.new
    else
        read_prologue
    end
end
normalize_metadata(metadata) click to toggle source
# File lib/pocolog/file.rb, line 831
def self.normalize_metadata(metadata)
    result = Hash.new
    metadata.each do |k, v|
        result[k.to_str] = v
    end
    result
end
open(pattern, registry = nil) click to toggle source

Opens a set of file. pattern can be a globbing pattern, in which case all the matching files will be opened as a log sequence

# File lib/pocolog/file.rb, line 191
def self.open(pattern, registry = nil)
    io = Dir.enum_for(:glob, pattern).sort.map { |name| File.open(name) }
    if io.empty?
        raise ArgumentError, "no files matching '#{pattern}'"
    end

    if registry
        io << registry
    end
    new(*io)
end
rename_streams(from_io, to_io, mappings) click to toggle source
# File lib/pocolog/convert.rb, line 112
def self.rename_streams(from_io, to_io, mappings)
    from = Logfiles.new(from_io)
    from.read_prologue
    write_prologue(to_io, from.endian_swap ^ Pocolog.big_endian?)

    buffer = ""
    from.each_block(true) do |block_info|
        if block_info.type == STREAM_BLOCK
            stream = from.read_stream_declaration
            write_stream_declaration(to_io, stream.index,
                    mappings[stream.name] || stream.name,
                    stream.type, nil, stream.metadata)
        else
            copy_block(block_info, from_io, to_io, buffer)
        end
    end
end
to_new_format(from_io, to_io, big_endian = nil) click to toggle source
# File lib/pocolog/convert.rb, line 62
def self.to_new_format(from_io, to_io, big_endian = nil)
    from = Logfiles.new(from_io)
    from.read_prologue

rescue MissingPrologue
    # This is format version 1. Need either --little-endian or --big-endian
    if big_endian.nil?
        raise "#{from_io.path} looks like a v1 log file. You must specify either --little-endian or --big-endian"
    end
    puts "#{from_io.path}: format v1 in #{big_endian ? "big endian" : "little endian"}"
    from_version_1(from, to_io, big_endian)

rescue ObsoleteVersion
end
valid_file?(file) click to toggle source

Returns true if file is a valid, up-to-date, pocolog file

# File lib/pocolog/file.rb, line 76
def self.valid_file?(file)
    Logfiles.new(file)
    true
rescue
    false
end
write_block(wio, type, index, payload) click to toggle source

Formats a block and writes it to io

# File lib/pocolog/file.rb, line 817
def self.write_block(wio, type, index, payload)
    wio << [type, index, payload.size].pack('CxvV')
    wio << payload
    return wio
end
write_data_block(io, stream_index, rt, lg, compress, data) click to toggle source
# File lib/pocolog/file.rb, line 953
def self.write_data_block(io, stream_index, rt, lg, compress, data)
    payload = [rt.tv_sec, rt.tv_usec, lg.tv_sec, lg.tv_usec,
        data.length, compress, data
    ].pack("#{DATA_BLOCK_HEADER_FORMAT}a#{data.size}")
    write_block(io, DATA_BLOCK, stream_index, payload)
end
write_prologue(to_io, big_endian = nil) click to toggle source
# File lib/pocolog/convert.rb, line 4
def self.write_prologue(to_io, big_endian = nil)
    to_io.write(MAGIC)
    if big_endian.nil?
        big_endian = Pocolog.big_endian?
    end
    to_io.write(*[FORMAT_VERSION, big_endian ? 1 : 0].pack('xVV'))
end
write_stream_declaration(wio, index, name, type_name, type_registry = nil, metadata = Hash.new) click to toggle source

Encodes and writes a stream declaration block to wio

# File lib/pocolog/file.rb, line 840
def self.write_stream_declaration(wio, index, name, type_name, type_registry = nil, metadata = Hash.new)
    if type_name.respond_to?(:name)
        type_registry ||= type_name.registry.minimal(type_name.name).to_xml
        type_name  = type_name.name
    end

    metadata = normalize_metadata(metadata)
    metadata = YAML.dump(metadata)
    payload = [DATA_STREAM, name.size, name, 
        type_name.size, type_name,
        type_registry.size, type_registry,
        metadata.size, metadata
    ].pack("CVa#{name.size}Va#{type_name.size}Va#{type_registry.size}Va#{metadata.size}")
    write_block(wio, STREAM_BLOCK, index, payload)
end

Public Instance Methods

advance(index) click to toggle source

Reads the next data sample in the file, and returns its header. Returns nil if the end of file has been reached. Unlike next, it does not decodes the data payload.

# File lib/pocolog/file.rb, line 360
def advance(index)
    each_data_block(index, false) do
        return data_header
    end
    nil
end
close() click to toggle source

Close the underlying IO objects

# File lib/pocolog/file.rb, line 130
def close
    io.each { |file| file.close }
end
closed?() click to toggle source
# File lib/pocolog/file.rb, line 125
def closed?
    @io.all? { |io| io.closed? }
end
create_stream(name, type, metadata = Hash.new) click to toggle source

Explicitely creates a new stream named name, of the given type and metadata

# File lib/pocolog/file.rb, line 900
def create_stream(name, type, metadata = Hash.new)
    if type.respond_to?(:to_str)
        type = registry.get(type)
    end

    typename  = type.name
    registry = type.registry.minimal(type.name).to_xml

    @streams ||= Array.new
    new_index = @streams.size
    write_stream_declaration(new_index, name, type.name, registry, metadata)

    stream = DataStream.new(self, new_index, name, typename, registry, metadata)
    @streams << stream
    stream
end
data(data_header = nil, buffer = nil) click to toggle source

Returns the raw data payload of the current block

# File lib/pocolog/file.rb, line 787
def data(data_header = nil, buffer = nil)
    if @data && !data_header then @data
    else
        data_header ||= self.data_header
        data_header.io.seek(data_header.payload_pos)
        data = data_header.io.read(data_header.size, buffer)
        if data_header.compressed
            # Payload is compressed
            data = Zlib::Inflate.inflate(data)
        end
        if !data_header
            @data = data
        end
        data
    end
end
data_header() click to toggle source

Reads the header of a data block. This sets the @data_header instance variable to a new DataHeader object describing the last read block. If you want to keep a reference on a data block, and read it later, do the following

block = file.data_header.dup
[do something, including reading the file]
data  = file.data(block)
# File lib/pocolog/file.rb, line 733
def data_header
    if @data_header.updated
        @data_header
    else
        data_block_pos = rio.tell
        expected_header_size = TIME_SIZE * 2 + 5
        result = rio.read(expected_header_size)
        if result.size != expected_header_size
            raise NotEnoughData, "expected to have #{expected_header_size} bytes remaining, but got only #{@data_header_buffer.size}, you may want to try running pocolog-repair on this file"
        end

        rt_sec, rt_usec, lg_sec, lg_usec, data_size, compressed =
            result.unpack('VVVVVC')
        rt = Time.at(rt_sec, rt_usec)
        lg = Time.at(lg_sec, lg_usec)
        payload_pos = data_block_pos + TIME_SIZE * 2 + 5

        size = payload_pos + data_size - data_block_pos
        expected = block_info.payload_size
        if size != expected
            if rio.respond_to?(:path)
                file = " in #{rio.path}"
            end
            raise NotEnoughData, "payload#{file} at position #{data_block_pos} was expected to be #{expected} bytes, but found #{size}, you may want to try running pocolog-repair on this file"
        end

        @data_header.io  = rio
        @data_header.block_pos   = @block_info.pos
        @data_header.payload_pos = payload_pos
        @data_header.rt = rt
        @data_header.lg = lg
        @data_header.size = data_size
        @data_header.compressed = (compressed != 0)
        @data_header.updated = true
        @data_header
    end
end
declared_stream?(index) click to toggle source

True if there is a stream index

# File lib/pocolog/file.rb, line 629
def declared_stream?(index)
    @streams && (@streams.size > index && @streams[index]) 
end
each_block(rewind = true, with_prologue = true) { |block_info| ... } click to toggle source

Yields a BlockInfo instance for each block found in the file set.

If rewind is true, rewind the file to the first block before iterating.

The with_prologue option specifies whether the prologue should be read after rewind. It is meant to be used internally to upgrade old files.

This is not meant for direct use. Use each_data_block instead.

# File lib/pocolog/file.rb, line 317
def each_block(rewind = true, with_prologue = true)
    self.rewind if rewind
    while !eof?
        io = self.rio
        if @next_block_pos == 0 && with_prologue
            read_prologue
        else
            io.seek(@next_block_pos)
        end

        @data = nil
        @data_header.updated = false

        if !read_block_header
            next_io
            next
        end

        yield(@block_info)
    end
rescue EOFError
end
each_data_block([stream_index[, rewind]]) do |stream_index| click to toggle source
end

Yields for each data block in stream stream_index, or in all streams if stream_index is nil. The block header can be retrieved using data_header, and the sample by using data

If rewind is true, starts at the beginning of the file. Otherwise, start at the current position

The with_prologue parameter is a backward compatibility feature that allowed to read old files that did not have a prologue.

# File lib/pocolog/file.rb, line 436
def each_data_block(stream_index = nil, rewind = true, with_prologue = true)
    each_block(rewind) do |block_info|
        if handle_block(block_info) == DATA_BLOCK
            if !stream_index || stream_index == block_info.index
                yield(block_info.index)
            end
        end
    end

rescue EOFError
rescue
    if !rio
        raise $!
    elsif !rio.closed?
        raise $!, "#{$!.message} at position #{rio.pos}", $!.backtrace
    end
end
eof?() click to toggle source

True if we read the last block in the file set

# File lib/pocolog/file.rb, line 300
def eof?; @io.size == @rio end
file_size() click to toggle source

Returns the file size of the IO object currently used

# File lib/pocolog/file.rb, line 306
def file_size; @io_size[@rio] end
has_stream?(name) click to toggle source

Returns true if name is the name of an existing stream

# File lib/pocolog/file.rb, line 933
def has_stream?(name)
    !!stream(name)
rescue ArgumentError
end
joint_stream(use_rt, *names) click to toggle source

Creates a JointStream object on the streams whose names are given. The returned object is used to coherently iterate on the samples of the given streams (i.e. it will yield samples that are valid at the same time)

# File lib/pocolog/file.rb, line 942
def joint_stream(use_rt, *names)
    streams = names.map do |n|
        stream(n)
    end
    JointStream.new(use_rt, *streams)
end
load_index_file(index_filename) click to toggle source

Load the given index file. Returns nil if the index file does not match the files in the file set.

# File lib/pocolog/file.rb, line 496
def load_index_file(index_filename)
    # Look for an index. If it is found, load it and use it.
    return unless File.readable?(index_filename)
    Pocolog.info "loading file info from #{index_filename}... "
    index_data = File.read(index_filename)
    file_info, stream_info =
        begin Marshal.load(index_data)
        rescue Exception => e
            if e.kind_of?(Interrupt)
                raise
            else
                raise InvalidIndex, "cannot unmarshal index data (#{e.message})"
            end
        end

    if file_info.size != @io.size
        raise InvalidIndex, "invalid index file: file set changed"
    end
    coherent = file_info.enum_for(:each_with_index).all? do |(size, _time), idx|
        size == File.size(@io[idx].path)
    end
    if !coherent
        raise InvalidIndex, "invalid index file: file size is different"
    end

    stream_info.each_with_index do |info, idx|
        if(!info.respond_to?("version") || info.version != StreamInfo::STREAM_INFO_VERSION || !info.declaration_block)
            raise InvalidIndex, "old index file found"
        end

        @rio, pos = info.declaration_block
        if read_one_block(pos, @rio).type != STREAM_BLOCK
            raise InvalidIndex, "invalid declaration_block reference in index"
        end

        # Read the stream declaration block and then update the
        # info attribute of the stream object
        if !info.empty?
            @rio, pos = info.interval_io[0]
            if read_one_block(pos, @rio).type != DATA_BLOCK
                raise InvalidIndex, "invalid start IO reference in index"
            end

            if block_info.index != idx
                raise InvalidIndex, "invalid interval_io: stream index mismatch for #{@streams[idx].name}. Expected #{idx}, got #{data_block_index}."
            end

            if !info.index.sane?
                raise InvalidIndex, "index failed internal sanity check"
            end

            @streams[idx].instance_variable_set(:@info, info)
        end
    end
    return @streams.compact

rescue InvalidIndex => e
    Pocolog.warn "invalid index file #{index_filename}: #{e.message}"
    nil
end
new_file(filename = nil) click to toggle source

Continue writing logs in a new file. See basename to know how files are named

# File lib/pocolog/file.rb, line 179
def new_file(filename = nil)
    name = filename || "#{basename}.#{@io.size}.log"
    io = File.new(name, 'w')
    Logfiles.write_prologue(io)
    @io << io
    streams.each_with_index do |s, i|
        write_stream_declaration(i, s.name, s.type.name, registry.to_xml)
    end
end
next_io() click to toggle source

Continue reading on the next IO object, or raise EOFError if we are currently reading the last one

# File lib/pocolog/file.rb, line 289
def next_io
    @rio += 1
    if @io.size == @rio
        raise EOFError
    else
        read_prologue
        rio
    end
end
open() click to toggle source
# File lib/pocolog/file.rb, line 134
def open
    @io = io.map do |file|
        if file.closed?
            File.open(file.path)
        else file
        end
    end
end
read_one_block(pos = nil, rio = nil) click to toggle source

Reads one block at the specified position and returns the block type (equal to block_info.type). If the block is a control or stream block, also call the relevant parsing methods.

See seek for the meaning of pos and rio. If both are nil, reads the sample at the current position.

# File lib/pocolog/file.rb, line 346
def read_one_block(pos = nil, rio = nil)
    if pos
        seek(pos, rio)
    end
    each_block(false) do |block_info|
        handle_block(block_info)
        return block_info
    end
    nil
end
read_one_data_payload(rio, position, buffer = nil) click to toggle source
# File lib/pocolog/file.rb, line 804
def read_one_data_payload(rio, position, buffer = nil)
    io = @io[rio]
    io.seek(position + BLOCK_HEADER_SIZE + TIME_SIZE * 2)
    data_size, compressed = io.read(5).unpack('VC')
    data = io.read(data_size, buffer)
    if compressed != 0
        # Payload is compressed
        data = Zlib::Inflate.inflate(data)
    end
    data
end
rewind() click to toggle source

Returns at the beginning of the first file of the file set

# File lib/pocolog/file.rb, line 247
def rewind
    @rio     = 0
    @time_base      = []
    @time_offset    = []
    @next_block_pos = 0

    @data_header = DataHeader.new
    @data = nil
end
rio() click to toggle source

Returns the IO object currently used for reading

# File lib/pocolog/file.rb, line 302
def rio; @io[@rio] end
seek(data_header) → position click to toggle source
seek(raw_position[, rio]) → position

Seeks in the file at the given position. In the first form, seeks to the place where the #data_header is stored. In the second form, seeks to the given raw position, and optionally changes the current IO object (rio is an index in the set of IOs given to initialize)

# File lib/pocolog/file.rb, line 162
def seek(pos, rio = nil)
    if pos.kind_of?(DataHeader)
        unless io_index = @io.index(pos.io)
            raise "#{pos} does not come from this log fileset"
        end
        @rio = io_index
        @next_block_pos = pos.block_pos
    else
        raise ArgumentError, "need rio argument, if pos is not a DataHeader" unless rio
        @rio = rio
        @next_block_pos = pos
    end
    nil
end
stream(name, type = nil, create = false) click to toggle source

Returns the DataStream object for name, registry and type. Optionally creates it.

If create is false, raises ArgumentError if the stream does not exist.

# File lib/pocolog/file.rb, line 922
def stream(name, type = nil, create = false)
    if s = streams.find { |s| s.name == name }
        s.registry # load the registry NOW
        return s
    elsif !type || !create
        raise ArgumentError, "no such stream #{name}"
    end
    create_stream(name, type)
end
stream_aligner(use_rt = false) click to toggle source

Creates a stream aligner on all streams of this logfile

# File lib/pocolog/file.rb, line 976
def stream_aligner(use_rt = false)
    StreamAligner.new(use_rt, *streams.compact)
end
stream_from_index(index) click to toggle source

Returns a stream from its index

# File lib/pocolog/file.rb, line 558
def stream_from_index(index)
    @streams[index]
end
stream_from_type(type) click to toggle source

Returns a stream of the given type, if there is only one. The type can be given by its name or through a Typelib::Type subclass

If there is no match or multiple matches, raises ArgumentError.

# File lib/pocolog/file.rb, line 887
def stream_from_type(type)
    matches = streams_from_type(type)
    if matches.empty?
        raise ArgumentError, "there is no stream in this file with the required type"
    elsif matches.size > 1
        raise ArgumentError, "there is more than one stream in this file with the required type"
    else
        return matches.first
    end
end
streams() click to toggle source

Loads and returns the set of data streams found in this file. Will lazily build an index file when required.

# File lib/pocolog/file.rb, line 564
def streams
    return @streams.compact if @streams

    index_filename = File.basename(@io[0].path, File.extname(@io[0].path)) + ".idx"
    index_filename = File.join(File.dirname(@io[0].path), index_filename)
    if streams = load_index_file(index_filename)
        return streams
    end
    
    # No index file. Compute it.
    Pocolog.info "building index #{index_filename} ..."
    each_data_block(nil, true) do |stream_index|
        # The stream object itself is built when the declaration block
        # has been found
        s    = @streams[stream_index]
        if s.nil? 
            Pocolog.warn "Got empty Streamline. Seems file is corrupted, skipping this" 
        else
            info = s.info
            info.interval_io[1] = [@rio, block_info.pos]
            info.interval_io[0] ||= info.interval_io[1]

            info.index.add_sample_to_index(@rio, data_header.block_pos, data_header.lg)                   
            info.size += 1
        end
    end

    if !@streams
        Pocolog.info "done"
        return []
    end

    @streams.each do |s|
        next unless s

        #set correct time interval
        stream_info = s.info
        if !stream_info.empty?
            @rio, pos = stream_info.interval_io[0]
            rio.seek(pos + BLOCK_HEADER_SIZE)
            stream_info.interval_rt[0] = read_time
            stream_info.interval_lg[0] = read_time
            @rio, pos = stream_info.interval_io[1] || stream_info.interval_io[0]
            rio.seek(pos + BLOCK_HEADER_SIZE)
            stream_info.interval_rt[1] = read_time
            stream_info.interval_lg[1] = read_time          
        end
    end

    file_info   = @io.map { |io| [File.size(io.path), io.mtime] }
    stream_info = @streams.compact.map { |s| s.info }

    begin
        File.open(index_filename, 'w') do |io|
            Marshal.dump([file_info, stream_info], io)
        end
    rescue
        FileUtils.rm_f index_filename
        raise
    end
    Pocolog.info "done"
    @streams.compact
end
streams_from_type(type) click to toggle source

Returns all streams of the given type. The type can be given by its name or through a Typelib::Type subclass

# File lib/pocolog/file.rb, line 875
def streams_from_type(type)
    if type.respond_to?(:name)
        type = type.name
    end

    streams.find_all { |s| s.type.name == type }
end
sub_field(offset, size, data_header = nil) click to toggle source
# File lib/pocolog/file.rb, line 771
def sub_field(offset, size, data_header = nil)
    data_header ||= self.data_header
    if data_header.compressed
        raise "field access on compressed files is unsupported"
    end
    data_header.io.seek(data_header.payload_pos + offset)
    data = data_header.io.read(size)
    data
end
tell() click to toggle source

Returns the current position in the current IO

This is the position of the next block.

# File lib/pocolog/file.rb, line 152
def tell; @next_block_pos end
time_base() click to toggle source

Returns the Time object which describes the 'zero' of this data set

# File lib/pocolog/file.rb, line 635
def time_base
    if @time_base.empty?
        Time.at(0)
    else
        @time_base.last[1] 
    end
end
time_offset() click to toggle source

Returns the offset from time_base

# File lib/pocolog/file.rb, line 644
def time_offset
    if @time_offset.empty? then 0
    else @time_offset.last[1] 
    end
end
validate_data_block() click to toggle source
# File lib/pocolog/file.rb, line 781
def validate_data_block
    data_header = self.data_header
    data_header.io.seek(data_header.payload_pos + data_header.size)
end
wio() click to toggle source

Returns the IO object currently used for writing

# File lib/pocolog/file.rb, line 304
def wio; @io.last end
write_block(type,index,payload) click to toggle source

Write a raw block. type is the block type (either CONTROL_BLOCK, DATA_BLOCK or STREAM_BLOCK), index the stream index for stream and data blocks and the control block type for control blocs. payload is the block's payload.

# File lib/pocolog/file.rb, line 827
def write_block(type,index,payload)
    return Logfiles.write_block(wio,type,index,payload)
end
write_stream_declaration(index, name, type, registry, metadata) click to toggle source

Writes a stream declaration to the current write IO

# File lib/pocolog/file.rb, line 867
def write_stream_declaration(index, name, type, registry, metadata)
    do_write do
        Logfiles.write_stream_declaration(wio, index, name, type, registry, metadata)
    end
end