class F4R::Encoder::RegistryBuilder

{Encoder} requires a properly built {Registry} to be able to encode.

Attributes

registry[R]

@return [Registry]

Public Class Methods

new(records, source) click to toggle source
# File lib/f4r.rb, line 1431
def initialize(records, source)
  @records = records
  @source = source
  source ? clone_definitions : build_definitions
end

Private Instance Methods

build_definitions() click to toggle source

Try to build definitions with the most accurate binary structure.

# File lib/f4r.rb, line 1488
      def build_definitions
        @registry = Registry.new(Definition::Header.new)

        largest_records = @records.
          group_by { |record| record[:message_name] }.
          inject({}) do |r, rcrds|
          r[rcrds[0]] = rcrds[1].sort_by { |rf| rf[:fields].count }.last
          r
        end

        largest_records.each do |name, record|
          global_message = GlobalFit.messages.find do |m|
            m[:name] == name
          end

          definition = @registry.definition(record)

          unless definition

            record_header = Definition::RecordHeader.new
            record_header.normal = 0
            record_header.message_type = 1
            record_header.local_message_type = record[:local_message_number]

            definition = Definition::Record.new
            definition.field_count = record[:fields].count
            definition.global_message_number = global_message[:number]

            record[:fields].each_with_index do |(field_name, _), index|
              global_field = global_message[:fields].
                find { |f| f[:field_name] == field_name }

              field_type = global_field[:field_type].to_sym
              global_type = GlobalFit.types[field_type]

              # Check in GlobalFit first as types can be anything form
              # strings to files, exercise, water, etc...
              base_type = GlobalFit.base_types.find do |dt|
                dt[:fit] == (global_type ? global_type[:base_type].to_sym : field_type)
              end

              unless base_type
                Log.warn <<~WARN
                  Data type "#{field[:field_type]}" is not a valid type for field
                  "#{field[:field_name]} (#{field[:filed_number]})".
                WARN
              end

              field = definition.data_fields[index]

              field.field_definition_number = global_field[:field_def]
              field.byte_count = 0 # set on build_records
              field.endian_ability = base_type[:endian]
              field.base_type_number = base_type[:number]
            end

            @registry.definitions << {
              local_message_number: record[:local_message_number],
              message_name: definition.global_message[:name],
              header: record_header,
              record: definition
            }
          end
        end

        build_records
      end
build_records() click to toggle source

Build and add/fix records' binary data/format.

@return [Hash] fixed/validated records

# File lib/f4r.rb, line 1561
def build_records
  fixed_strings = {}

  @records.each do |record|
    definition = registry.definition(record)
    definition = definition && definition[:record]

    fields = {}

    definition.data_fields.each do |field|
      record_field = record[:fields][field.name]

      if record_field && !record_field[:value].nil?
        value = record_field[:value]
      else
        value = field.base_type_definition[:undef]

        sibling = field_sibling(field)
        if sibling.is_a?(Array)
          value = [field.base_type_definition[:undef]] * sibling.size
        end
      end

      unless from_source?
        field.byte_count = field.base_type_definition[:bytes]

        if field.base_type_definition[:bindata] == :string
          if fixed_strings[record[:message_name]] &&
              fixed_strings[record[:message_name]][field.name]
            largest_string = fixed_strings[record[:message_name]][field.name]
          else
            largest_string = @records.
              select {|rd| rd[:message_name] == record[:message_name] }.
              map do |rd|
              rd[:fields][field.name] &&
                rd[:fields][field.name][:value].to_s.length
            end.compact.sort.last

            fixed_strings[record[:message_name]] = {}
            fixed_strings[record[:message_name]][field.name] = largest_string
          end

          field.byte_count = ((largest_string / 8) * 8) + 8
        end

        if value.is_a?(Array)
          field.byte_count *= value.size
        end
      end

      if field.base_type_definition[:bindata] == :string
        opts = {length: field.byte_count.snapshot}
        value = BinData::String.new(value, opts).snapshot
      end

      fields[field.name] = {
        value: value,
        base_type: field.base_type_definition,
        properties: field.global_message_field,
        definition: field
      }
    end

    registry.records << {
      message_name: definition.global_message[:name],
      message_number: definition.global_message[:number],
      local_message_number: record[:local_message_number],
      fields: fields
    }
  end
end
clone_definitions() click to toggle source

Decode source FIT file that will be used to provide binary structure for the FIT file to be created.

@param [String] source path to FIT file

# File lib/f4r.rb, line 1445
def clone_definitions
  io = ::File.open(@source, 'rb')

  begin
    until io.eof?
      offset = io.pos

      header = Definition::Header.read(io)
      @registry = Registry.new(header)

      while io.pos < offset + header.file_size
        record_header = Definition::RecordHeader.read(io)

        local_message_number = record_header.local_message_type.snapshot

        if record_header.for_new_definition?
          definition = Definition::Record.read(io)

          @registry.definitions << {
            local_message_number: local_message_number,
            message_name: definition.global_message[:name],
            header: record_header,
            record: definition,
          }
        else
          @registry.definitions.reverse.find do |d|
            d[:local_message_number] == local_message_number
          end[:record].read_data(io)
        end
      end

      io.seek(2, :CUR)
    end
  ensure
    io.close
  end

  build_records
end
field_sibling(field) click to toggle source

Helper method for finding a field's sibling.

This is mostly because we can't trust base_type on arrays.

@param [RecordField] field @return [Array,Integer,String] sibling

# File lib/f4r.rb, line 1641
def field_sibling(field)
  sibling = @records.find do |rd|
    rd[:message_name] == field.global_message[:name] &&
      rd[:fields].keys.include?(field.name)
  end

  sibling && sibling[:fields][field.name][:value]
end
from_source?() click to toggle source

@return [Boolean]

# File lib/f4r.rb, line 1653
def from_source?
  !@source.nil?
end