class LaunchDarkly::FileDataSourceImpl

@private

Public Class Methods

new(feature_store, logger, options={}) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 127
def initialize(feature_store, logger, options={})
  @feature_store = feature_store
  @logger = logger
  @paths = options[:paths] || []
  if @paths.is_a? String
    @paths = [ @paths ]
  end
  @auto_update = options[:auto_update]
  if @auto_update && LaunchDarkly.have_listen? && !options[:force_polling] # force_polling is used only for tests
    # We have seen unreliable behavior in the 'listen' gem in JRuby 9.1 (https://github.com/guard/listen/issues/449).
    # Therefore, on that platform we'll fall back to file polling instead.
    if defined?(JRUBY_VERSION) && JRUBY_VERSION.start_with?("9.1.")
      @use_listen = false
    else
      @use_listen = true
    end
  end
  @poll_interval = options[:poll_interval] || 1
  @initialized = Concurrent::AtomicBoolean.new(false)
  @ready = Concurrent::Event.new
end

Public Instance Methods

initialized?() click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 149
def initialized?
  @initialized.value
end
start() click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 153
def start
  ready = Concurrent::Event.new
  
  # We will return immediately regardless of whether the file load succeeded or failed -
  # the difference can be detected by checking "initialized?"
  ready.set

  load_all

  if @auto_update
    # If we're going to watch files, then the start event will be set the first time we get
    # a successful load.
    @listener = start_listener
  end

  ready
end
stop() click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 171
def stop
  @listener.stop if !@listener.nil?
end

Private Instance Methods

add_item(all_data, kind, item) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 226
def add_item(all_data, kind, item)
  items = all_data[kind]
  raise ArgumentError, "Received unknown item kind #{kind} in add_data" if items.nil? # shouldn't be possible since we preinitialize the hash
  key = item[:key].to_sym
  if !items[key].nil?
    raise ArgumentError, "#{kind[:namespace]} key \"#{item[:key]}\" was used more than once"
  end
  items[key] = item
end
load_all() click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 177
def load_all
  all_data = {
    FEATURES => {},
    SEGMENTS => {}
  }
  @paths.each do |path|
    begin
      load_file(path, all_data)
    rescue => exn
      Util.log_exception(@logger, "Unable to load flag data from \"#{path}\"", exn)
      return
    end
  end
  @feature_store.init(all_data)
  @initialized.make_true
end
load_file(path, all_data) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 194
def load_file(path, all_data)
  parsed = parse_content(IO.read(path))
  (parsed[:flags] || {}).each do |key, flag|
    add_item(all_data, FEATURES, flag)
  end
  (parsed[:flagValues] || {}).each do |key, value|
    add_item(all_data, FEATURES, make_flag_with_value(key.to_s, value))
  end
  (parsed[:segments] || {}).each do |key, segment|
    add_item(all_data, SEGMENTS, segment)
  end
end
make_flag_with_value(key, value) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 236
def make_flag_with_value(key, value)
  {
    key: key,
    on: true,
    fallthrough: { variation: 0 },
    variations: [ value ]
  }
end
parse_content(content) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 207
def parse_content(content)
  # We can use the Ruby YAML parser for both YAML and JSON (JSON is a subset of YAML and while
  # not all YAML parsers handle it correctly, we have verified that the Ruby one does, at least
  # for all the samples of actual flag data that we've tested).
  symbolize_all_keys(YAML.safe_load(content))
end
start_listener() click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 245
def start_listener
  resolved_paths = @paths.map { |p| Pathname.new(File.absolute_path(p)).realpath.to_s }
  if @use_listen
    start_listener_with_listen_gem(resolved_paths)
  else
    FileDataSourcePoller.new(resolved_paths, @poll_interval, self.method(:load_all), @logger)
  end
end
start_listener_with_listen_gem(resolved_paths) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 254
def start_listener_with_listen_gem(resolved_paths)
  path_set = resolved_paths.to_set
  dir_paths = resolved_paths.map{ |p| File.dirname(p) }.uniq
  opts = { latency: @poll_interval }
  l = Listen.to(*dir_paths, opts) do |modified, added, removed|
    paths = modified + added + removed
    if paths.any? { |p| path_set.include?(p) }
      load_all
    end
  end
  l.start
  l
end
symbolize_all_keys(value) click to toggle source
# File lib/ldclient-rb/file_data_source.rb, line 214
def symbolize_all_keys(value)
  # This is necessary because YAML.load doesn't have an option for parsing keys as symbols, and
  # the SDK expects all objects to be formatted that way.
  if value.is_a?(Hash)
    value.map{ |k, v| [k.to_sym, symbolize_all_keys(v)] }.to_h
  elsif value.is_a?(Array)
    value.map{ |v| symbolize_all_keys(v) }
  else
    value
  end
end