Add docs for remaining format functions

Add docs for remaining format functions

defmodule Benchee.Conversion.Format do
  @moduledoc """
  Functions for formatting values and their unit labels. Different domains
  handle this task differently, for example durations and counts.

  See `Benchee.Conversion.Count` and `Benchee.Conversion.Duration` for examples
  """

  @doc """
  Formats a number as a string, with a unit label. See `Benchee.Conversion.Count`
  and `Benchee.Conversion.Duration` for examples
  """
  @callback format(number) :: String.t

  @doc """
  The label for a given unit, as a String.  See `Benchee.Conversion.Count`
  and `Benchee.Conversion.Duration` for examples
  """
  @callback label(Benchee.Conversion.Scale.unit) :: String.t

  @doc """
  A string that appears between a value and a unit label when formatted as a
  String. For example, a space: `5.67 M` or an empty string: `5.67M`
  """
  @callback separator :: String.t

  # Generic formatting functions

  @doc """
  Formats a unit value with specified label and separator
  """
  def format({count, _unit}, label, separator) do
    separator = separator(label, separator)
    "~.#{float_precision(count)}f~ts~ts"
    |> :io_lib.format([count, separator, label])
    |> to_string
  end

  @doc """
  Formats a unit value with the label and separator supplied by `module`. The
  specified module should provide `label/1` and `separator/0` functions
  """
  def format({count, unit}, module) do
    format({count, unit}, module.label(unit), module.separator)
  end

  @doc """
  Scales a number to the most appropriate unit, and formats the scaled value
  with the label and separator supplied by `module`. The
  specified module should provide `label/1` and `separator/0` functions
  """
  def format(number, module) do
    number
    |> module.scale
    |> format(module)
  end

  @doc """
  Fetches a label from a map of units
  """
  def label(units, unit) do
    units
    |> Map.fetch!(unit)
    |> Map.fetch!(:short)
  end

  # Returns the separator, or an empty string if there isn't a label
  defp separator(label, separator) do
    case label do
      "" -> ""
      _  -> separator
    end
  end

  defp float_precision(float) when float < 0.01, do: 5
  defp float_precision(float) when float < 0.1, do: 4
  defp float_precision(float) when float < 0.2, do: 3
  defp float_precision(_float), do: 2

end

defmodule Benchee.Conversion.Format do
  @moduledoc """
  Functions for formatting values and their unit labels. Different domains
  handle this task differently, for example durations and counts.

  See `Benchee.Conversion.Count` and `Benchee.Conversion.Duration` for examples
  """

  @doc """
  Formats a number as a string, with a unit label. See `Benchee.Conversion.Count`
  and `Benchee.Conversion.Duration` for examples
  """
  @callback format(number) :: String.t

  @doc """
  The label for a given unit, as a String.  See `Benchee.Conversion.Count`
  and `Benchee.Conversion.Duration` for examples
  """
  @callback label(Benchee.Conversion.Scale.unit) :: String.t

  @doc """
  A string that appears between a value and a unit label when formatted as a
  String. For example, a space: `5.67 M` or an empty string: `5.67M`
  """
  @callback separator :: String.t

  # Generic formatting functions

  @doc """
  Formats a unit value with specified label and separator
  """
  def format({count, _unit}, label, separator) do
    separator = separator(label, separator)
    "~.#{float_precision(count)}f~ts~ts"
    |> :io_lib.format([count, separator, label])
    |> to_string
  end

  @doc """
  Formats a unit value with the label and separator supplied by `module`. The
  specified module should provide `label/1` and `separator/0` functions
  """
  def format({count, unit}, module) do
    format({count, unit}, module.label(unit), module.separator)
  end

  def format(number, module) do
    number
    |> module.scale
    |> format(module)
  end

  def label(units, unit) do
    units
    |> Map.fetch!(unit)
    |> Map.fetch!(:short)
  end

  # Returns the separator, or an empty string if there isn't a label
  defp separator(label, separator) do
    case label do
      "" -> ""
      _  -> separator
    end
  end

  defp float_precision(float) when float < 0.01, do: 5
  defp float_precision(float) when float < 0.1, do: 4
  defp float_precision(float) when float < 0.2, do: 3
  defp float_precision(_float), do: 2

end


Replace copy/paste code with correct app name

defmodule PactElixir.RustPactMockServerFacade do
  @moduledoc """
  Adapter for the wrapped rust [pact mock server](https://github.com/pact-foundation/pact-reference).
  Functions in this file are replaced by Rustler with their Rust calling
  counterpart. See native/pactmockserver/src/lib.rs for the concrete Rust
  implementation.
  This file is excluded from the coverage tool.
  """

  require Logger

  mix_config = Mix.Project.config()
  version = mix_config[:version]
  github_url = mix_config[:package][:links]["GitHub"]

  rustler_opts = [otp_app: :pact_elixir, crate: "pactmockserver", mode: :release]
  env_config = Application.get_env(rustler_opts[:otp_app], PactElixir, [])

  opts =
    if System.get_env("PACTELIXIR_BUILD") in ["1", "true"] or env_config[:build_from_source] do
      rustler_opts
    else
      case PactElixir.RustPactMockServerFacade.Precompiled.download_or_reuse_nif_file(
             rustler_opts,
             base_url: "#{github_url}/releases/download/v#{version}",
             version: version
           ) do
        {:ok, new_opts} ->
          new_opts

        {:error, error} ->
          error =
            "Error while downloading precompiled NIF: #{error}\n\nSet PACTELIXIR_BUILD=1 env var to compile the NIF from scratch. You can also configure this application to force compilation:\n\n    config :pact_elixir, PactElixir, build_from_source: true\n"

          if Mix.env() == :prod do
            raise error
          else
            Logger.debug(error)
            rustler_opts
          end
      end
    end

  use Rustler, opts

  def create_mock_server(_pact_json, _port), do: throw(:nif_not_loaded)
  def mock_server_mismatches(_port), do: throw(:nif_not_loaded)

  # @spec mock_server_matched(number) :: {:ok, boolean}
  def mock_server_matched(_port), do: throw(:nif_not_loaded)
  def write_pact_file(_port, _dir_path), do: throw(:nif_not_loaded)
  def cleanup_mock_server(_port), do: throw(:nif_not_loaded)
end

defmodule PactElixir.RustPactMockServerFacade do
  @moduledoc """
  Adapter for the wrapped rust [pact mock server](https://github.com/pact-foundation/pact-reference).
  Functions in this file are replaced by Rustler with their Rust calling
  counterpart. See native/pactmockserver/src/lib.rs for the concrete Rust
  implementation.
  This file is excluded from the coverage tool.
  """

  require Logger

  mix_config = Mix.Project.config()
  version = mix_config[:version]
  github_url = mix_config[:package][:links]["GitHub"]

  rustler_opts = [otp_app: :pact_elixir, crate: "pactmockserver", mode: :release]
  env_config = Application.get_env(rustler_opts[:otp_app], PactElixir, [])

  opts =
    if System.get_env("PACTELIXIR_BUILD") in ["1", "true"] or env_config[:build_from_source] do
      rustler_opts
    else
      case PactElixir.RustPactMockServerFacade.Precompiled.download_or_reuse_nif_file(
             rustler_opts,
             base_url: "#{github_url}/releases/download/v#{version}",
             version: version
           ) do
        {:ok, new_opts} ->
          new_opts

        {:error, error} ->
          error =
            "Error while downloading precompiled NIF: #{error}\n\nSet HTML5EVER_BUILD=1 env var to compile the NIF from scratch. You can also configure this application to force compilation:\n\n    config :html5ever, Html5ever, build_from_source: true\n"

          if Mix.env() == :prod do
            raise error
          else
            Logger.debug(error)
            rustler_opts
          end
      end
    end

  use Rustler, opts

  def create_mock_server(_pact_json, _port), do: throw(:nif_not_loaded)
  def mock_server_mismatches(_port), do: throw(:nif_not_loaded)

  # @spec mock_server_matched(number) :: {:ok, boolean}
  def mock_server_matched(_port), do: throw(:nif_not_loaded)
  def write_pact_file(_port, _dir_path), do: throw(:nif_not_loaded)
  def cleanup_mock_server(_port), do: throw(:nif_not_loaded)
end


Explicitly call transaction(_event)() in Decorator tests

defmodule Appsignal.Instrumentation.DecoratorsTest do
  use ExUnit.Case
  import Mock

  alias Appsignal.Transaction

  defmodule Example do
    use Appsignal.Instrumentation.Decorators

    @decorate transaction()
    def transaction do
      bar(123)
    end

    @decorate transaction(:background_job)
    def background_transaction do
      bar(123)
    end

    @decorate transaction()
    def transaction_with_return_value(x) do
      2 * x
    end

    @decorate transaction_event()
    def bar(arg) do
      nested(arg, arg)
    end

    @doc "A moduledoc attribute"
    @decorate transaction_event()
    def nested(_arg1, _arg2) do
    end

  end

  test_with_mock "instrument module function", Appsignal.Transaction, [:passthrough], [] do
    t = Transaction.start("bar", :http_request)
    Example.bar(123)
    assert called Transaction.start_event(t)
    assert called Transaction.finish_event(t, "bar", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example.bar", "", 0)
    assert called Transaction.finish_event(t, "nested", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example.nested", "", 0)
  end

  test_with_mock "instrument transaction", Appsignal.Transaction, [:passthrough], [] do
    Example.transaction
    assert called Transaction.start(:_, :http_request)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#transaction")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  test_with_mock "instrument background transaction", Appsignal.Transaction, [:passthrough], [] do
    Example.background_transaction
    assert called Transaction.start(:_, :background_job)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#background_transaction")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  test_with_mock "instrument transaction with return value", Appsignal.Transaction, [:passthrough], [] do
    result = Example.transaction_with_return_value(123)
    assert 246 == result
    assert called Transaction.start(:_, :http_request)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#transaction_with_return_value")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  defmodule Example2 do
    use Appsignal.Instrumentation.Decorators

    @doc "A moduledoc attribute"
    @decorate transaction_event :http
    def bar(arg) do
      nested(arg, arg)
    end

    @decorate transaction_event :db
    def nested(_arg1, _arg2) do
    end

  end

  test_with_mock "instrument module function with category", Appsignal.Transaction, [:passthrough], [] do
    t = Transaction.start("bar", :http_request)
    Example2.bar(123)
    assert called Transaction.start_event(t)
    assert called Transaction.finish_event(t, "bar.http", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example2.bar", "", 0)
    assert called Transaction.finish_event(t, "nested.db", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example2.nested", "", 0)
  end

end

defmodule Appsignal.Instrumentation.DecoratorsTest do
  use ExUnit.Case
  import Mock

  alias Appsignal.Transaction

  defmodule Example do
    use Appsignal.Instrumentation.Decorators

    @decorate transaction
    def transaction do
      bar(123)
    end

    @decorate transaction(:background_job)
    def background_transaction do
      bar(123)
    end

    @decorate transaction
    def transaction_with_return_value(x) do
      2 * x
    end

    @decorate transaction_event
    def bar(arg) do
      nested(arg, arg)
    end

    @doc "A moduledoc attribute"
    @decorate transaction_event
    def nested(_arg1, _arg2) do
    end

  end

  test_with_mock "instrument module function", Appsignal.Transaction, [:passthrough], [] do
    t = Transaction.start("bar", :http_request)
    Example.bar(123)
    assert called Transaction.start_event(t)
    assert called Transaction.finish_event(t, "bar", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example.bar", "", 0)
    assert called Transaction.finish_event(t, "nested", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example.nested", "", 0)
  end

  test_with_mock "instrument transaction", Appsignal.Transaction, [:passthrough], [] do
    Example.transaction
    assert called Transaction.start(:_, :http_request)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#transaction")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  test_with_mock "instrument background transaction", Appsignal.Transaction, [:passthrough], [] do
    Example.background_transaction
    assert called Transaction.start(:_, :background_job)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#background_transaction")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  test_with_mock "instrument transaction with return value", Appsignal.Transaction, [:passthrough], [] do
    result = Example.transaction_with_return_value(123)
    assert 246 == result
    assert called Transaction.start(:_, :http_request)
    assert called Appsignal.Transaction.set_action(:_, "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example#transaction_with_return_value")
    assert called Appsignal.Transaction.finish(:_)
    assert called Appsignal.Transaction.complete(:_)
  end

  defmodule Example2 do
    use Appsignal.Instrumentation.Decorators

    @doc "A moduledoc attribute"
    @decorate transaction_event :http
    def bar(arg) do
      nested(arg, arg)
    end

    @decorate transaction_event :db
    def nested(_arg1, _arg2) do
    end

  end

  test_with_mock "instrument module function with category", Appsignal.Transaction, [:passthrough], [] do
    t = Transaction.start("bar", :http_request)
    Example2.bar(123)
    assert called Transaction.start_event(t)
    assert called Transaction.finish_event(t, "bar.http", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example2.bar", "", 0)
    assert called Transaction.finish_event(t, "nested.db", "Elixir.Appsignal.Instrumentation.DecoratorsTest.Example2.nested", "", 0)
  end

end


Fix init timeouts and reloads

Fix init timeouts and reloads

Was double resetting init processes, which was doubling time between
retries. Now takes  care of let the current timer expire y detected a
reason to retry. It happens when  the process ends properly and sets the
timer, but then its detected that the  process is down and sends another
timer with  the double time.

require Logger

defmodule Serverboards.Plugin.Init do
  use GenServer

  defstruct [
    id: nil,
    name: nil,
    description: nil,
    command: nil,
    call: nil
  ]

  @max_timeout 60*60*2 # 2h

  def start_link(init, options \\ []) do
    GenServer.start_link(__MODULE__, init, options)
  end

  def stop(init) do
    GenServer.stop(init)
  end

  def from_component(c) do
    command = case String.split(c.extra["command"], "/") do
      [component_id] ->
        [plugin_id, _] = String.split(c.id, "/")
        "#{plugin_id}/#{component_id}"
      [plugin_id, component_id] ->
        "#{plugin_id}/#{component_id}"
    end

    %Serverboards.Plugin.Init{
      id: c.id,
      name: c.name,
      description: c.description,
      command: command,
      call: c.extra["call"]
    }
  end

  ## impl
  def init(init) do
    state = %{
      running: false,
      init: init,
      task: nil,
      timeout: 1,
      timer: nil,
      started_at: nil,
      cmd: nil
    }

    GenServer.cast(self(),{:start})

    {:ok, state}
  end

  def terminate(reason, state) do
    Serverboards.Plugin.Runner.stop(state.cmd)
    Logger.info("Terminated init #{ state.init.id } #{inspect reason}", init: state.init)
    :shutdown
  end

  def handle_cast({:start}, state) do
    Logger.info("Starting init service #{inspect state.init.id}", init: state.init)
    init = state.init
    {:ok, cmd} = Serverboards.Plugin.Runner.start(init.command, "system/init")
    Process.monitor((Serverboards.Plugin.Runner.get cmd).pid)
    #Process.link((Serverboards.Plugin.Runner.get cmd).pid)
    task =  Task.async(Serverboards.Plugin.Runner, :call, [cmd, init.call, []])

    state = %{
      state |
      cmd: cmd,
      running: true,
      task: task,
      started_at: Timex.Duration.now()
    }

    #Logger.debug("It should be running: #{inspect task}, #{inspect state}")

    {:noreply, state}
  end

  defp handle_wait_run(%{started_at: started_at, timeout: timeout } = state) do
    if state.timer do # if inside timer, do not retrigger. Wait for real timer.
      running_for_seconds = case started_at do
        nil -> 0
        _other -> -Timex.Duration.diff(started_at, nil, :seconds)
      end
      timer = Process.send_after(self(), {:restart}, timeout * 1000)

      # Timeout is for next run
      timeout = max(1, min((timeout - running_for_seconds) * 2, @max_timeout)) # 2h

      state = %{
        state |
        timeout: timeout,
        timer: timer,
        started_at: nil,
        task: nil
      }
      Logger.info("Restart init #{state.init.id} in #{state.timeout} seconds.", state: state)
      state
    else
      state
    end
  end

  def handle_info({:DOWN, _ref, :process, _pid, _type}, %{started_at: started_at} = state) when is_nil(started_at) do
    # this is when already finished properly, it may close the cmd.
    # Logger.info("Init \"#{inspect state.init.id}\" down (#{inspect type}).")
    Serverboards.Plugin.Runner.stop(state.cmd)
    # and restart soon
    state = handle_wait_run(state)
    {:noreply, state}
  end
def handle_info({:DOWN, _ref, :process, _pid, _type}, state) do
    # Logger.info("Init \"#{inspect state.init.id}\" down (#{inspect type}).")
    Serverboards.Plugin.Runner.stop(state.cmd)
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({:restart}, state) do
    Logger.debug("Get restart #{inspect self()}")
    Serverboards.Plugin.Runner.stop(state.cmd)
    state = %{
      state |
      timer: nil
    }
    handle_cast({:start}, state)
  end
  def handle_info({_ref, {:error, :unknown_method}}, state) do
    Logger.error("Cant run init, unknown method #{inspect state.init.call}")
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({_ref, {:error, error}}, state) do
    Logger.error("Error running init #{inspect state.init.id}, error: #{inspect error}.", init: state.init, error: error)
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({_ref, {:ok, waits}}, state) when is_number(waits) do
    %{started_at: started_at } = state
    running_for_seconds = if started_at do
      -Timex.Duration.diff(started_at, nil, :seconds)
    else nil end

    timeout = max(1, waits)
    if state.timer do
      Process.cancel_timer(state.timer)
    end
    timer = Process.send_after(self(), {:restart}, timeout * 1000)
    state = %{
      state |
      timeout: timeout,
      timer: timer,
      started_at: nil,
      task: nil
    }
    Logger.info("Init \"#{state.init.id}\" finished properly. Did run for #{inspect running_for_seconds} seconds. #{inspect timeout} secs")
    {:noreply, state}
  end
  def handle_info({:EXIT, _from, :normal}, state) do
    Logger.debug("Stop the external process #{inspect state}")
    Serverboards.Plugin.Runner.stop(state.cmd)
    {:stop, :normal, state}
  end
  def handle_info({:EXIT, _from, :force_stop}, state) do
    Logger.info("Kill the external process #{inspect state.init.command}")
    Serverboards.Plugin.Runner.kill(state.cmd)
    {:stop, :normal, state}
  end
  def handle_info(any, state) do
    Logger.warn("Got info: #{inspect any}")
    {:noreply, state}
  end
end

require Logger

defmodule Serverboards.Plugin.Init do
  use GenServer

  defstruct [
    id: nil,
    name: nil,
    description: nil,
    command: nil,
    call: nil
  ]

  @max_timeout 60*60*2 # 2h

  def start_link(init, options \\ []) do
    GenServer.start_link(__MODULE__, init, options)
  end

  def stop(init) do
    GenServer.stop(init)
  end

  def from_component(c) do
    command = case String.split(c.extra["command"], "/") do
      [component_id] ->
        [plugin_id, _] = String.split(c.id, "/")
        "#{plugin_id}/#{component_id}"
      [plugin_id, component_id] ->
        "#{plugin_id}/#{component_id}"
    end

    %Serverboards.Plugin.Init{
      id: c.id,
      name: c.name,
      description: c.description,
      command: command,
      call: c.extra["call"]
    }
  end

  ## impl
  def init(init) do
    state = %{
      running: false,
      init: init,
      task: nil,
      timeout: 1,
      timer: nil,
      started_at: nil,
      cmd: nil
    }

    GenServer.cast(self(),{:start})

    {:ok, state}
  end

  def terminate(reason, state) do
    Serverboards.Plugin.Runner.stop(state.cmd)
    Logger.info("Terminated init #{ state.init.id } #{inspect reason}", init: state.init)
    :shutdown
  end

  def handle_cast({:start}, state) do
    Logger.info("Starting init service #{inspect state.init.id}", init: state.init)
    init = state.init
    {:ok, cmd} = Serverboards.Plugin.Runner.start(init.command, "system/init")
    Process.monitor((Serverboards.Plugin.Runner.get cmd).pid)
    #Process.link((Serverboards.Plugin.Runner.get cmd).pid)
    task =  Task.async(Serverboards.Plugin.Runner, :call, [cmd, init.call, []])

    state = %{
      state |
      cmd: cmd,
      running: true,
      task: task,
      started_at: Timex.Duration.now()
    }

    #Logger.debug("It should be running: #{inspect task}, #{inspect state}")

    {:noreply, state}
  end

  defp handle_wait_run(%{started_at: started_at, timeout: timeout } = state) do
    running_for_seconds = case started_at do
      nil -> 0
      _other -> -Timex.Duration.diff(started_at, nil, :seconds)
    end
    timeout = max(1, min((timeout - running_for_seconds) * 2, @max_timeout)) # 2h
    if state.timer do
      Process.cancel_timer(state.timer)
    end
    timer = Process.send_after(self(), {:restart}, timeout * 1000)
    state = %{
      state |
      timeout: timeout,
      timer: timer,
      started_at: nil,
      task: nil
    }
    Logger.info("#{inspect self()} Restart init #{state.init.id} in #{state.timeout} seconds.", state: state)
    state
  end

  def handle_info({:DOWN, _ref, :process, _pid, _type}, %{started_at: started_at} = state) when is_nil(started_at) do
    # this is when already finished properly, it may close the cmd.
    # Logger.info("Init \"#{inspect state.init.id}\" down (#{inspect type}).")
    Serverboards.Plugin.Runner.stop(state.cmd)
    # and restart soon
    state = handle_wait_run(state)
    {:noreply, state}
  end
def handle_info({:DOWN, _ref, :process, _pid, _type}, state) do
    # Logger.info("Init \"#{inspect state.init.id}\" down (#{inspect type}).")
    Serverboards.Plugin.Runner.stop(state.cmd)
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({:restart}, state) do
    Logger.debug("Get restart #{inspect self()}")
    Serverboards.Plugin.Runner.stop(state.cmd)
    handle_cast({:start}, state)
  end
  def handle_info({_ref, {:error, :unknown_method}}, state) do
    Logger.error("Cant run init, unknown method #{inspect state.init.call}")
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({_ref, {:error, error}}, state) do
    Logger.error("Error running init #{inspect state.init.id}, error: #{inspect error}.", init: state.init, error: error)
    state = handle_wait_run(state)
    {:noreply, state}
  end
  def handle_info({_ref, {:ok, waits}}, state) when is_number(waits) do
    %{started_at: started_at } = state
    running_for_seconds = if started_at do
      -Timex.Duration.diff(started_at, nil, :seconds)
    else nil end

    timeout = max(1, waits)
    if state.timer do
      Process.cancel_timer(state.timer)
    end
    timer = Process.send_after(self(), {:restart}, timeout * 1000)
    state = %{
      state |
      timeout: timeout,
      timer: timer,
      started_at: nil,
      task: nil
    }
    Logger.info("Init \"#{state.init.id}\" finished properly. Did run for #{inspect running_for_seconds} seconds. Restart in #{state.timeout} seconds.", state: state)
    {:noreply, state}
  end
  def handle_info({:EXIT, _from, :normal}, state) do
    Logger.debug("Stop the external process #{inspect state}")
    Serverboards.Plugin.Runner.stop(state.cmd)
    {:stop, :normal, state}
  end
  def handle_info({:EXIT, _from, :force_stop}, state) do
    Logger.info("Kill the external process #{inspect state.init.command}")
    Serverboards.Plugin.Runner.kill(state.cmd)
    {:stop, :normal, state}
  end
  def handle_info(any, state) do
    Logger.warn("#{inspect self()} Got info: #{inspect any}")
    {:noreply, state}
  end
end


Name the applications and the EventCenter properly

defmodule BaumeisterTest do
  use ExUnit.Case
  doctest Baumeister
  doctest Baumeister.BaumeisterFile

  alias Baumeister.Config
  alias Baumeister.Observer.Delay
  alias Baumeister.Observer.Git
  alias Baumeister.Observer.FailPlugin
  alias Baumeister.Observer.NoopPlugin
  alias Baumeister.Observer.Take
  alias Baumeister.EventCenter
  alias Baumeister.Worker

  alias Baumeister.Test.GitRepos
  alias Baumeister.Test.TestListener
  alias Baumeister.Test.Utils

  alias Experimental.GenStage

  require Logger
  # @moduletag capture_log: true

  # Setup the repository and the paths to their working spaces
  setup do
    Logger.info "Stop the Baumeister App for a fresh start"
    :ok = Application.stop(:baumeister_coordinator)
    repos = GitRepos.make_temp_git_repo_with_some_content()
    Logger.info "Start the Baumeister Application"
    :ok= Application.ensure_started(:baumeister_coordinator)
    Utils.wait_for fn -> nil != Process.whereis(Baumeister.ObserverSupervisor) end

    Logger.info "Start a worker"
    {:ok, worker} = Worker.start_link()
    Logger.info "Worker process is #{inspect worker}"

    # Drain the event queue of old events.
    Logger.info "Clear the event center"
    Utils.wait_for fn -> 0 == EventCenter.clear() end

    {:ok, repos}
  end

  @tag timeout: 1_000
  test "add a new project", %{parent_repo_path: repo_url, parent_repo: repo} do
    # observe the git repo, but only 1 time, and wait 100 ms
    plugs = [{Take, 2}, {Git, repo_url}, {Delay, 100}]
    project = "baumeister_test"
    assert Config.keys() == []

    {:ok, listener} = TestListener.start()
    GenStage.sync_subscribe(listener, to: EventCenter.name())

    :ok = Baumeister.add_project(project, repo_url, plugs)

    # nothing has happend, the listener is still disabled
    assert [] == TestListener.get(listener)
    assert Config.keys() == [project]

    # enable the observer
    :ok = Baumeister.enable(project)
    Logger.info("Project is enabled, observer is running")
    :timer.sleep(100)
    {bmf, _local_os} = Utils.create_bmf("echo Hello")
    {:ok, _} = GitRepos.update_the_bmf(repo, bmf)

    # wait for some events
    # When running a larger test set, there are sometimes some old
    # events still in the queue. Therefore, we filter all events
    # for our current listener for `project`
    Utils.wait_for fn ->
      events = TestListener.get(listener)
      worker_ev_cnt = events
      |> Enum.filter(fn {w, _a, _} -> w == :worker end)
      |> Enum.count()
      stopped_observer? = events
      |>  Enum.any?(fn {_, a, _} -> a == :stopped_observer end)

      # And the condition
      (stopped_observer? and (worker_ev_cnt >= 3))
    end

    {ol, w_list} = listener
    |> TestListener.get()
    |> Enum.partition(fn {_, _, v} -> v == project end)
    obs_actions = ol |> Enum.map(fn {_, a, _} -> a end)
    worker_actions = w_list
    |> Enum.filter(fn {w, _, _} -> w == :worker end)
    |> Enum.map(fn
        {_, :execute, {a, _version, _out}} -> a
        {_, :execute, {a, _version}} -> a end)

    assert obs_actions ==
      [:start_observer, :exec_observer, :exec_observer, :exec_observer, :stopped_observer]

    assert worker_actions == [:start, :ok, :log]

    # After a stop, the project is disabled
    {:ok, p} = Config.config(project)
    assert nil == p.observer
    assert false == p.enabled
  end

  test "Ensure that all required processes are running" do
    obs_sup = Process.whereis(Baumeister.ObserverSupervisor)
    assert nil != obs_sup
    assert 0 == Supervisor.count_children(obs_sup).active
    assert nil != Process.whereis(Baumeister.ObserverTaskSupervisor)
  end

end

defmodule BaumeisterTest do
  use ExUnit.Case
  doctest Baumeister
  doctest Baumeister.BaumeisterFile

  alias Baumeister.Config
  alias Baumeister.Observer.Delay
  alias Baumeister.Observer.Git
  alias Baumeister.Observer.FailPlugin
  alias Baumeister.Observer.NoopPlugin
  alias Baumeister.Observer.Take
  alias Baumeister.EventCenter
  alias Baumeister.Worker

  alias Baumeister.Test.GitRepos
  alias Baumeister.Test.TestListener
  alias Baumeister.Test.Utils

  alias Experimental.GenStage

  require Logger
  # @moduletag capture_log: true

  # Setup the repository and the paths to their working spaces
  setup do
    Logger.info "Stop the Baumeister App for a fresh start"
    :ok = Application.stop(:baumeister)
    repos = GitRepos.make_temp_git_repo_with_some_content()
    Logger.info "Start the Baumeister Application"
    :ok= Application.ensure_started(:baumeister)
    Utils.wait_for fn -> nil != Process.whereis(Baumeister.ObserverSupervisor) end

    Logger.info "Start a worker"
    {:ok, worker} = Worker.start_link()
    Logger.info "Worker process is #{inspect worker}"

    # Drain the event queue of old events.
    Logger.info "Clear the event center"
    Utils.wait_for fn -> 0 == EventCenter.clear() end

    {:ok, repos}
  end

  @tag timeout: 1_000
  test "add a new project", %{parent_repo_path: repo_url, parent_repo: repo} do
    # observe the git repo, but only 1 time, and wait 100 ms
    plugs = [{Take, 2}, {Git, repo_url}, {Delay, 100}]
    project = "baumeister_test"
    assert Config.keys() == []

    {:ok, listener} = TestListener.start()
    GenStage.sync_subscribe(listener, to: EventCenter)

    :ok = Baumeister.add_project(project, repo_url, plugs)

    # nothing has happend, the listener is still disabled
    assert [] == TestListener.get(listener)
    assert Config.keys() == [project]

    # enable the observer
    :ok = Baumeister.enable(project)
    Logger.info("Project is enabled, observer is running")
    :timer.sleep(100)
    {bmf, _local_os} = Utils.create_bmf("echo Hello")
    {:ok, _} = GitRepos.update_the_bmf(repo, bmf)

    # wait for some events
    # When running a larger test set, there are sometimes some old
    # events still in the queue. Therefore, we filter all events
    # for our current listener for `project`
    Utils.wait_for fn ->
      events = TestListener.get(listener)
      worker_ev_cnt = events
      |> Enum.filter(fn {w, _a, _} -> w == :worker end)
      |> Enum.count()
      stopped_observer? = events
      |>  Enum.any?(fn {_, a, _} -> a == :stopped_observer end)

      # And the condition
      (stopped_observer? and (worker_ev_cnt >= 3))
    end

    {ol, w_list} = listener
    |> TestListener.get()
    |> Enum.partition(fn {_, _, v} -> v == project end)
    obs_actions = ol |> Enum.map(fn {_, a, _} -> a end)
    worker_actions = w_list
    |> Enum.filter(fn {w, _, _} -> w == :worker end)
    |> Enum.map(fn
        {_, :execute, {a, _version, _out}} -> a
        {_, :execute, {a, _version}} -> a end)

    assert obs_actions ==
      [:start_observer, :exec_observer, :exec_observer, :exec_observer, :stopped_observer]

    assert worker_actions == [:start, :ok, :log]

    # After a stop, the project is disabled
    {:ok, p} = Config.config(project)
    assert nil == p.observer
    assert false == p.enabled
  end

  test "Ensure that all required processes are running" do
    obs_sup = Process.whereis(Baumeister.ObserverSupervisor)
    assert nil != obs_sup
    assert 0 == Supervisor.count_children(obs_sup).active
    assert nil != Process.whereis(Baumeister.ObserverTaskSupervisor)
  end

end


Change the inactive square symbol to a full block

Again, this makes things a bit more obvious/clean when inspecting
boards.

defmodule LexibombServer.Board.Square do
  @moduledoc """
  """

  defstruct [
    adjacent_bombs: 0,
    bomb?: false,
    revealed?: false,
    tile: "",
  ]

  @inactive "█"

  def deactivate(square) do
    %{square | revealed?: true, tile: @inactive}
  end

  def inactive?(square) do
    square.tile === @inactive
  end

  def reveal(square) do
    %{square | revealed?: true}
  end
end

defimpl Inspect, for: LexibombServer.Board.Square do
  @bomb_symbol "●"
  @adjacent_bomb_symbols { "·", "│", "╎", "┆", "┊", "†", "‡", "¤", "*" }

  def inspect(square, _opts) do
    if square.revealed? do
      "#{bomb_symbol(square)}#{square.tile}" |> String.ljust(3)
    else
      "   "
    end
  end

  defp bomb_symbol(square) do
    if square.bomb? do
      @bomb_symbol
    else
      @adjacent_bomb_symbols |> elem(square.adjacent_bombs)
    end
  end
end

defmodule LexibombServer.Board.Square do
  @moduledoc """
  """

  defstruct [
    adjacent_bombs: 0,
    bomb?: false,
    revealed?: false,
    tile: "",
  ]

  @inactive "#"

  def deactivate(square) do
    %{square | revealed?: true, tile: @inactive}
  end

  def inactive?(square) do
    square.tile === @inactive
  end

  def reveal(square) do
    %{square | revealed?: true}
  end
end

defimpl Inspect, for: LexibombServer.Board.Square do
  @bomb_symbol "●"
  @adjacent_bomb_symbols { "·", "│", "╎", "┆", "┊", "†", "‡", "¤", "*" }

  def inspect(square, _opts) do
    if square.revealed? do
      "#{bomb_symbol(square)}#{square.tile}" |> String.ljust(3)
    else
      "   "
    end
  end

  defp bomb_symbol(square) do
    if square.bomb? do
      @bomb_symbol
    else
      @adjacent_bomb_symbols |> elem(square.adjacent_bombs)
    end
  end
end


Add test for multiple forbidden modules

Add test for multiple forbidden modules

defmodule Credo.Check.Warning.ForbiddenModuleTest do
  use Credo.Test.Case

  @described_check Credo.Check.Warning.ForbiddenModule

  #
  # cases NOT raising issues
  #

  test "it should NOT report with default params" do
    """
    defmodule CredoReplicateIssue do
      alias __MODULE__.Module

      def hello do
        Module.hello()
      end
    end

    defmodule CredoReplicateIssue.Module do
      def hello, do: :world
    end
    """
    |> to_source_file
    |> run_check(@described_check)
    |> refute_issues()
  end

  test "it should NOT report with default params /2" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.ForbiddenModule
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check)
    |> refute_issues()
  end

  #
  # cases raising issues
  #

  test "it should report on inline fully qualified usage" do
    """
    defmodule CredoSampleModule do
      def some_function, do: CredoSampleModule.ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on aliases" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.ForbiddenModule
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on grouped aliases" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.{AllowedModule, ForbiddenModule, ForbiddenModule2}
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule, CredoSampleModule.ForbiddenModule2])
    |> assert_issues()
  end

  test "it should report on import" do
    """
    defmodule CredoSampleModule do
      import CredoSampleModule.ForbiddenModule
      def some_function, do: another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on import only" do
    """
    defmodule CredoSampleModule do
      import CredoSampleModule.ForbiddenModule, only: [another_function: 0]
      def some_function, do: another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should display a custom message" do
    """
    defmodule CredoSampleModule do
      def some_function, do: CredoSampleModule.ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [{CredoSampleModule.ForbiddenModule, "my message"}])
    |> assert_issue(fn issue ->
      expected_message = "my message"

      assert issue.message == expected_message
    end)
  end

  test "it should work with multiple forbidden modules" do
    """
    defmodule CredoSampleModule do
      def some_function, do: CredoSampleModule.ForbiddenModule.another_function()
      def some_function2, do: CredoSampleModule.ForbiddenModule2.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule, CredoSampleModule.ForbiddenModule2])
    |> assert_issues()
  end
end

defmodule Credo.Check.Warning.ForbiddenModuleTest do
  use Credo.Test.Case

  @described_check Credo.Check.Warning.ForbiddenModule

  #
  # cases NOT raising issues
  #

  test "it should NOT report with default params" do
    """
    defmodule CredoReplicateIssue do
      alias __MODULE__.Module

      def hello do
        Module.hello()
      end
    end

    defmodule CredoReplicateIssue.Module do
      def hello, do: :world
    end
    """
    |> to_source_file
    |> run_check(@described_check)
    |> refute_issues()
  end

  test "it should NOT report with default params /2" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.ForbiddenModule
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check)
    |> refute_issues()
  end

  #
  # cases raising issues
  #

  test "it should report on inline fully qualified usage" do
    """
    defmodule CredoSampleModule do
      def some_function, do: CredoSampleModule.ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on aliases" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.ForbiddenModule
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on grouped aliases" do
    """
    defmodule CredoSampleModule do
      alias CredoSampleModule.{AllowedModule, ForbiddenModule, ForbiddenModule2}
      def some_function, do: ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule, CredoSampleModule.ForbiddenModule2])
    |> assert_issues()
  end

  test "it should report on import" do
    """
    defmodule CredoSampleModule do
      import CredoSampleModule.ForbiddenModule
      def some_function, do: another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should report on import only" do
    """
    defmodule CredoSampleModule do
      import CredoSampleModule.ForbiddenModule, only: [another_function: 0]
      def some_function, do: another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [CredoSampleModule.ForbiddenModule])
    |> assert_issue()
  end

  test "it should display a custom message" do
    """
    defmodule CredoSampleModule do
      def some_function, do: CredoSampleModule.ForbiddenModule.another_function()
    end
    """
    |> to_source_file
    |> run_check(@described_check, modules: [{CredoSampleModule.ForbiddenModule, "my message"}])
    |> assert_issue(fn issue ->
      expected_message = "my message"

      assert issue.message == expected_message
    end)
  end
end


use get_application_tracer in Elixir macros

The Erlang macros already properly use get_application_tracer.
The reason to use get_application_tracer is the macros lookup
the tracer for the particular application by module name.

defmodule OpenTelemetry.Tracer do
  @moduledoc """
  This module contains macros for Tracer operations around the lifecycle of the Spans within a Trace.

  The Tracer is able to start a new Span as a child of the active Span of the current process, set
  a different Span to be the current Span by passing the Span's context, end a Span or run a code
  block within the context of a newly started span that is ended when the code block completes.

  The macros `start_span` and `with_span` use the Tracer registered to the Application the module
  is included in. These Tracers are created at boot time for each loaded Application.

      require OpenTelemetry.Tracer

      OpenTelemetry.Tracer.with_span "span-1" do
        ... do something ...
      end
  """

  @type start_opts() :: %{
          optional(:attributes) => OpenTelemetry.attributes(),
          optional(:links) => OpenTelemetry.links(),
          optional(:is_recording) => boolean(),
          optional(:start_time) => :opentelemetry.timestamp(),
          optional(:kind) => OpenTelemetry.span_kind()
        }

  @doc """
  Starts a new span and does not make it the current active span of the current process.

  The current active Span is used as the parent of the created Span.
  """
  defmacro start_span(name, opts \\ quote(do: %{})) do
    quote bind_quoted: [name: name, start_opts: opts] do
      :otel_tracer.start_span(:opentelemetry.get_application_tracer(__MODULE__), name, Map.new(start_opts))
    end
  end

  @doc """
  Starts a new span and does not make it the current active span of the current process.

  The current active Span is used as the parent of the created Span.
  """
  defmacro start_span(ctx, name, opts) do
    quote bind_quoted: [ctx: ctx, name: name, start_opts: opts] do
      :otel_tracer.start_span(
        ctx,
        :opentelemetry.get_application_tracer(__MODULE__),
        name,
        Map.new(start_opts)
      )
    end
  end

  @doc """
  Takes a `t:OpenTelemetry.span_ctx/0` and the Tracer sets it to the currently active Span.
  """
  def set_current_span(span_ctx) do
    :otel_tracer.set_current_span(span_ctx)
  end

  @doc """
  Takes a `t:OpenTelemetry.Ctx.t/0` and the `t:OpenTelemetry.span_ctx/0` and the Tracer sets
  it to the current span in the pass Context.
  """
  def set_current_span(ctx, span_ctx) do
    :otel_tracer.set_current_span(ctx, span_ctx)
  end

  @doc """
  Creates a new span which is set to the currently active Span in the Context of the block.
  The Span is ended automatically when the `block` completes and the Context is what it was
  before the block.

  See `start_span/2` and `end_span/0`.
  """
  defmacro with_span(name, start_opts \\ quote(do: %{}), do: block) do
    quote do
      :otel_tracer.with_span(
        :opentelemetry.get_application_tracer(__MODULE__),
        unquote(name),
        Map.new(unquote(start_opts)),
        fn _ -> unquote(block) end
      )
    end
  end

  @doc """
  Creates a new span which is set to the currently active Span in the Context of the block.
  The Span is ended automatically when the `block` completes and the Context is what it was
  before the block.

  See `start_span/2` and `end_span/0`.
  """
  defmacro with_span(ctx, name, start_opts, do: block) do
    quote do
      :otel_tracer.with_span(
        unquote(ctx),
        :opentelemetry.get_application_tracer(__MODULE__),
        unquote(name),
        Map.new(unquote(start_opts)),
        fn _ -> unquote(block) end
      )
    end
  end

  @doc """
  Returns the currently active `t:OpenTelemetry.span_ctx/0`.
  """
  def current_span_ctx() do
    :otel_tracer.current_span_ctx()
  end

  @doc """
  Returns the `t:OpenTelemetry.span_ctx/0` active in Context `ctx`.
  """
  def current_span_ctx(ctx) do
    :otel_tracer.current_span_ctx(ctx)
  end

  @doc """
  End the Span. Sets the end timestamp for the currently active Span. This has no effect on any
  child Spans that may exist of this Span.

  The Span in the current Context has its `is_recording` set to `false`.
  """
  @spec end_span(:opentelemetry.timestamp() | :undefined) ::
          :opentelemetry.span_ctx() | :undefined
  def end_span(timestamp \\ :undefined) do
    non_recording_span = :otel_span.end_span(:otel_tracer.current_span_ctx(), timestamp)
    _ = :otel_tracer.set_current_span(non_recording_span)
    non_recording_span
  end

  @doc """
  Set an attribute with key and value on the currently active Span.
  """
  @spec set_attribute(OpenTelemetry.attribute_key(), OpenTelemetry.attribute_value()) :: boolean()
  def set_attribute(key, value) do
    :otel_span.set_attribute(
      :otel_tracer.current_span_ctx(),
      key,
      value
    )
  end

  @doc """
  Add a list of attributes to the currently active Span.
  """
  @spec set_attributes(OpenTelemetry.attributes()) :: boolean()
  def set_attributes(attributes) do
    :otel_span.set_attributes(:otel_tracer.current_span_ctx(), attributes)
  end

  @doc """
  Add an event to the currently active Span.
  """
  @spec add_event(OpenTelemetry.event_name(), OpenTelemetry.attributes()) :: boolean()
  def add_event(event, attributes) do
    :otel_span.add_event(
      :otel_tracer.current_span_ctx(),
      event,
      attributes
    )
  end

  @doc """
  Add a list of events to the currently active Span.
  """
  @spec add_events([OpenTelemetry.event()]) :: boolean()
  def add_events(events) do
    :otel_span.add_events(:otel_tracer.current_span_ctx(), events)
  end

  @doc """
  Sets the Status of the currently active Span.

  If used, this will override the default Span Status, which is `:unset`.
  """
  @spec set_status(OpenTelemetry.status()) :: boolean()
  def set_status(status) do
    :otel_span.set_status(:otel_tracer.current_span_ctx(), status)
  end

  @doc """
  Updates the Span name.

  It is highly discouraged to update the name of a Span after its creation. Span name is
  often used to group, filter and identify the logical groups of spans. And often, filtering
  logic will be implemented before the Span creation for performance reasons. Thus the name
  update may interfere with this logic.

  The function name is called UpdateName to differentiate this function from the regular
  property setter. It emphasizes that this operation signifies a major change for a Span
  and may lead to re-calculation of sampling or filtering decisions made previously
  depending on the implementation.
  """
  @spec update_name(String.t()) :: boolean()
  def update_name(name) do
    :otel_span.update_name(:otel_tracer.current_span_ctx(), name)
  end
end

defmodule OpenTelemetry.Tracer do
  @moduledoc """
  This module contains macros for Tracer operations around the lifecycle of the Spans within a Trace.

  The Tracer is able to start a new Span as a child of the active Span of the current process, set
  a different Span to be the current Span by passing the Span's context, end a Span or run a code
  block within the context of a newly started span that is ended when the code block completes.

  The macros `start_span` and `with_span` use the Tracer registered to the Application the module
  is included in. These Tracers are created at boot time for each loaded Application.

      require OpenTelemetry.Tracer

      OpenTelemetry.Tracer.with_span "span-1" do
        ... do something ...
      end
  """

  @type start_opts() :: %{
          optional(:attributes) => OpenTelemetry.attributes(),
          optional(:links) => OpenTelemetry.links(),
          optional(:is_recording) => boolean(),
          optional(:start_time) => :opentelemetry.timestamp(),
          optional(:kind) => OpenTelemetry.span_kind()
        }

  @doc """
  Starts a new span and does not make it the current active span of the current process.

  The current active Span is used as the parent of the created Span.
  """
  defmacro start_span(name, opts \\ quote(do: %{})) do
    quote bind_quoted: [name: name, start_opts: opts] do
      :otel_tracer.start_span(:opentelemetry.get_tracer(__MODULE__), name, Map.new(start_opts))
    end
  end

  @doc """
  Starts a new span and does not make it the current active span of the current process.

  The current active Span is used as the parent of the created Span.
  """
  defmacro start_span(ctx, name, opts) do
    quote bind_quoted: [ctx: ctx, name: name, start_opts: opts] do
      :otel_tracer.start_span(
        ctx,
        :opentelemetry.get_tracer(__MODULE__),
        name,
        Map.new(start_opts)
      )
    end
  end

  @doc """
  Takes a `t:OpenTelemetry.span_ctx/0` and the Tracer sets it to the currently active Span.
  """
  def set_current_span(span_ctx) do
    :otel_tracer.set_current_span(span_ctx)
  end

  @doc """
  Takes a `t:OpenTelemetry.Ctx.t/0` and the `t:OpenTelemetry.span_ctx/0` and the Tracer sets
  it to the current span in the pass Context.
  """
  def set_current_span(ctx, span_ctx) do
    :otel_tracer.set_current_span(ctx, span_ctx)
  end

  @doc """
  Creates a new span which is set to the currently active Span in the Context of the block.
  The Span is ended automatically when the `block` completes and the Context is what it was
  before the block.

  See `start_span/2` and `end_span/0`.
  """
  defmacro with_span(name, start_opts \\ quote(do: %{}), do: block) do
    quote do
      :otel_tracer.with_span(
        :opentelemetry.get_tracer(__MODULE__),
        unquote(name),
        Map.new(unquote(start_opts)),
        fn _ -> unquote(block) end
      )
    end
  end

  @doc """
  Creates a new span which is set to the currently active Span in the Context of the block.
  The Span is ended automatically when the `block` completes and the Context is what it was
  before the block.

  See `start_span/2` and `end_span/0`.
  """
  defmacro with_span(ctx, name, start_opts, do: block) do
    quote do
      :otel_tracer.with_span(
        unquote(ctx),
        :opentelemetry.get_tracer(__MODULE__),
        unquote(name),
        Map.new(unquote(start_opts)),
        fn _ -> unquote(block) end
      )
    end
  end

  @doc """
  Returns the currently active `t:OpenTelemetry.span_ctx/0`.
  """
  def current_span_ctx() do
    :otel_tracer.current_span_ctx()
  end

  @doc """
  Returns the `t:OpenTelemetry.span_ctx/0` active in Context `ctx`.
  """
  def current_span_ctx(ctx) do
    :otel_tracer.current_span_ctx(ctx)
  end

  @doc """
  End the Span. Sets the end timestamp for the currently active Span. This has no effect on any
  child Spans that may exist of this Span.

  The Span in the current Context has its `is_recording` set to `false`.
  """
  @spec end_span(:opentelemetry.timestamp() | :undefined) ::
          :opentelemetry.span_ctx() | :undefined
  def end_span(timestamp \\ :undefined) do
    non_recording_span = :otel_span.end_span(:otel_tracer.current_span_ctx(), timestamp)
    _ = :otel_tracer.set_current_span(non_recording_span)
    non_recording_span
  end

  @doc """
  Set an attribute with key and value on the currently active Span.
  """
  @spec set_attribute(OpenTelemetry.attribute_key(), OpenTelemetry.attribute_value()) :: boolean()
  def set_attribute(key, value) do
    :otel_span.set_attribute(
      :otel_tracer.current_span_ctx(),
      key,
      value
    )
  end

  @doc """
  Add a list of attributes to the currently active Span.
  """
  @spec set_attributes(OpenTelemetry.attributes()) :: boolean()
  def set_attributes(attributes) do
    :otel_span.set_attributes(:otel_tracer.current_span_ctx(), attributes)
  end

  @doc """
  Add an event to the currently active Span.
  """
  @spec add_event(OpenTelemetry.event_name(), OpenTelemetry.attributes()) :: boolean()
  def add_event(event, attributes) do
    :otel_span.add_event(
      :otel_tracer.current_span_ctx(),
      event,
      attributes
    )
  end

  @doc """
  Add a list of events to the currently active Span.
  """
  @spec add_events([OpenTelemetry.event()]) :: boolean()
  def add_events(events) do
    :otel_span.add_events(:otel_tracer.current_span_ctx(), events)
  end

  @doc """
  Sets the Status of the currently active Span.

  If used, this will override the default Span Status, which is `:unset`.
  """
  @spec set_status(OpenTelemetry.status()) :: boolean()
  def set_status(status) do
    :otel_span.set_status(:otel_tracer.current_span_ctx(), status)
  end

  @doc """
  Updates the Span name.

  It is highly discouraged to update the name of a Span after its creation. Span name is
  often used to group, filter and identify the logical groups of spans. And often, filtering
  logic will be implemented before the Span creation for performance reasons. Thus the name
  update may interfere with this logic.

  The function name is called UpdateName to differentiate this function from the regular
  property setter. It emphasizes that this operation signifies a major change for a Span
  and may lead to re-calculation of sampling or filtering decisions made previously
  depending on the implementation.
  """
  @spec update_name(String.t()) :: boolean()
  def update_name(name) do
    :otel_span.update_name(:otel_tracer.current_span_ctx(), name)
  end
end


Fix game_channel

Fix game_channel

defmodule TanxWeb.GameChannel do
  use TanxWeb, :channel

  require Logger

  def join("game:" <> game, %{"name" => player_name, "id" => player}, socket) do
    Tanx.ContinuousGame.rename_player(Tanx.GameSwarm.game_process(game), player, player_name)
    socket =
      socket
      |> assign(:game, game)
      |> assign(:player, player)
    {:ok, %{i: player}, socket}
  end

  def join("game:" <> game, %{"name" => player_name}, socket) do
    {:ok, player} = Tanx.ContinuousGame.add_player(Tanx.GameSwarm.game_process(game), player_name)
    socket =
      socket
      |> assign(:game, game)
      |> assign(:player, player)
    {:ok, %{i: player}, socket}
  end

  def handle_in("view_players", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_players(socket.assigns[:player])
      |> TanxWeb.JsonData.format_players()

    push(socket, "view_players", view)
    {:noreply, socket}
  end

  def handle_in("view_structure", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_static()
      |> TanxWeb.JsonData.format_structure()

    push(socket, "view_structure", view)
    {:noreply, socket}
  end

  def handle_in("view_arena", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_arena(socket.assigns[:player])
      |> TanxWeb.JsonData.format_arena()

    push(socket, "view_arena", view)
    {:noreply, socket}
  end

  def handle_in("rename", %{"name" => name}, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.rename_player(player, name)
    end

    {:noreply, socket}
  end

  def handle_in("launch_tank", %{"entry_point" => entry_point}, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.start_tank(player, entry_point)
    end

    {:noreply, socket}
  end

  def handle_in("self_destruct_tank", _msg, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.destruct_tank(player)
    end

    {:noreply, socket}
  end

  def handle_in("control_tank", %{"button" => button, "down" => down}, socket)
      when button == "left" or button == "right" or button == "backward" or button == "forward" or
             button == "fire" do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.control_tank(player, String.to_atom(button), down)
    end

    {:noreply, socket}
  end

  def handle_in("heartbeat", _msg, socket) do
    {:noreply, socket}
  end

  def handle_in(msg, payload, socket) do
    Logger.error("Unknown message received on game channel: #{inspect(msg)}: #{inspect(payload)}")
    {:noreply, socket}
  end

  intercept(["view_players"])

  def handle_out("view_players", _event, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_players(socket.assigns[:player])
      |> TanxWeb.JsonData.format_players()

    push(socket, "view_players", view)
    {:noreply, socket}
  end

  def handle_out(msg, payload, socket) do
    push(socket, msg, payload)
    {:noreply, socket}
  end

  def terminate(reason, socket) do
    # Logger.info("Connection terminated due to #{inspect(reason)}")

    socket =
      case socket.assigns[:player] do
        nil ->
          socket

        player ->
          socket.assigns[:game]
          |> Tanx.GameSwarm.game_process
          |> Tanx.ContinuousGame.remove_player(player)
          assign(socket, :player, nil)
      end

    {reason, socket}
  end
end

defmodule TanxWeb.GameChannel do
  use TanxWeb, :channel

  require Logger

  def join("game:" <> game, %{"name" => player_name, "id" => player}, socket) do
    Tanx.ContinuousGame.rename_player(Tanx.GameSwarm.game_process(game), player, player_name)
    socket =
      socket
      |> assign(:game, game)
      |> assign(:player, player)
    {:ok, %{i: player}, socket}
  end

  def join("game:" <> game, %{"name" => player_name}, socket) do
    {:ok, player} = Tanx.ContinuousGame.add_player(Tanx.GameSwarm.game_process(game), player_name)
    socket =
      socket
      |> assign(:game, game)
      |> assign(:player, player)
    {:ok, %{i: player}, socket}
  end

  def handle_in("view_players", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_players(socket.assigns[:player])
      |> TanxWeb.JsonData.format_players()

    push(socket, "view_players", view)
    {:noreply, socket}
  end

  def handle_in("view_structure", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_static()
      |> TanxWeb.JsonData.format_structure()

    push(socket, "view_structure", view)
    {:noreply, socket}
  end

  def handle_in("view_arena", _msg, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_arena(socket.assigns[:player])
      |> TanxWeb.JsonData.format_arena()

    push(socket, "view_arena", view)
    {:noreply, socket}
  end

  def handle_in("rename", %{"name" => name}, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.rename_player(game, player, name)
    end

    {:noreply, socket}
  end

  def handle_in("launch_tank", %{"entry_point" => entry_point}, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.start_tank(game, player, entry_point)
    end

    {:noreply, socket}
  end

  def handle_in("self_destruct_tank", _msg, socket) do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.destruct_tank(game, player)
    end

    {:noreply, socket}
  end

  def handle_in("control_tank", %{"button" => button, "down" => down}, socket)
      when button == "left" or button == "right" or button == "backward" or button == "forward" or
             button == "fire" do
    player = socket.assigns[:player]

    if player do
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.control_tank(player, String.to_atom(button), down)
    end

    {:noreply, socket}
  end

  def handle_in("heartbeat", _msg, socket) do
    {:noreply, socket}
  end

  def handle_in(msg, payload, socket) do
    Logger.error("Unknown message received on game channel: #{inspect(msg)}: #{inspect(payload)}")
    {:noreply, socket}
  end

  intercept(["view_players"])

  def handle_out("view_players", _event, socket) do
    view =
      socket.assigns[:game]
      |> Tanx.GameSwarm.game_process
      |> Tanx.ContinuousGame.view_players(socket.assigns[:player])
      |> TanxWeb.JsonData.format_players()

    push(socket, "view_players", view)
    {:noreply, socket}
  end

  def handle_out(msg, payload, socket) do
    push(socket, msg, payload)
    {:noreply, socket}
  end

  def terminate(reason, socket) do
    # Logger.info("Connection terminated due to #{inspect(reason)}")

    socket =
      case socket.assigns[:player] do
        nil ->
          socket

        player ->
          socket.assigns[:game]
          |> Tanx.GameSwarm.game_process
          |> Tanx.ContinuousGame.remove_player(player)
          assign(socket, :player, nil)
      end

    {reason, socket}
  end
end


Revert "fix test errors"

Revert "fix test errors"

This reverts commit d4876946dbfe4732eb02c7fc49ea8c8afd40e21b.

defmodule WeberHttpResponseTest do
  use ExUnit.Case

  test "SimpleResponse test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/weber', [], <<>>, [])
    body = :hackney.body(client)
    assert(body == {:ok, "Main\n"})
    assert(status == 200)
  end

  test "SimpleResponse Include test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/include', [], <<>>, [])
    body = :hackney.body(client)
    assert(body == {:ok, "Main\n"})
    assert(status == 200)
  end

  test "`redirect` in route test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/redirect', [], <<>>, [])
    assert(status == 302)
  end

  test "`content_for` test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/content_for', [], <<>>, [])
    body = :hackney.body(client)
    assert(body == {:ok, "<!DOCTYPE html>\n\n  \n    <title>\n      My Project\n    </title>\n    <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />\n  \n  \n
\n    Hello Weber!      \n
\n  \n "})
  end

end

defmodule WeberHttpResponseTest do
  use ExUnit.Case

  test "SimpleResponse test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/weber', [], <<>>, [])
    {:ok, body, _rest } = :hackney.body(client)
    assert(body == "Main\n")
    assert(status == 200)
  end

  test "SimpleResponse Include test" do
    {:ok, status, _, client} = :hackney.request(:get, 'http://localhost:8080/include', [], <<>>, [])
    {:ok, body, _rest } = :hackney.body(client)
    assert(body == "Main\n")
    assert(status == 200)
  end

  test "`redirect` in route test" do
    {:ok, status, _, _client} = :hackney.request(:get, 'http://localhost:8080/redirect', [], <<>>, [])
    assert(status == 302)
  end

  test "`content_for` test" do
    {:ok, _status, _, client} = :hackney.request(:get, 'http://localhost:8080/content_for', [], <<>>, [])
    {:ok, body, _rest } = :hackney.body(client)
    assert(body == "<!DOCTYPE html>\n\n  \n    <title>\n      My Project\n    </title>\n    <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />\n  \n  \n
\n    Hello Weber!      \n
\n  \n ")
  end

end


from now on 'lastest episodes' are ordered by id descending

defmodule PanWeb.EpisodeFrontendController do
  use Pan.Web, :controller
  alias PanWeb.{Chapter, Episode, Image, Recommendation}

  def show(conn, %{"id" => id}) do
    episode = Repo.get!(Episode, id)
              |> Repo.preload([:podcast, :enclosures, gigs: :persona, recommendations: :user])
              |> Repo.preload(chapters: from(chapter in Chapter, order_by: chapter.start,
                                                                 preload: [recommendations: :user]))

    episode_thumbnail = Repo.get_by(Image, episode_id: episode.id) ||
                        Repo.get_by(Image, podcast_id: episode.podcast.id)

    changeset = Recommendation.changeset(%Recommendation{})
    # options for player: "podlove", "podigee"
    render(conn, "show.html", episode: episode,
                              episode_thumbnail: episode_thumbnail,
                              player: "podlove",
                              changeset: changeset)
  end

  def player(conn, %{"id" => id}) do
    episode = Repo.get!(Episode, id)
              |> Repo.preload([:podcast, :enclosures, gigs: :persona, recommendations: :user])
              |> Repo.preload(chapters: from(chapter in Chapter, order_by: chapter.start))

    episode_thumbnail = Repo.get_by(Image, episode_id: episode.id) ||
                        Repo.get_by(Image, podcast_id: episode.podcast.id)

    # options for player: "podlove", "podigee"
    conn
    |> put_layout("minimal.html")
    |> render("player.html", episode: episode,
                             episode_thumbnail: episode_thumbnail,
                             player: "podlove")
  end

  def index(conn, params) do
    episodes = from(e in Episode, join: p in assoc(e, :podcast),
                                  where: is_false(p.blocked) and
                                         e.publishing_date < ^NaiveDateTime.utc_now(),
                                  order_by: [desc: :id],
                                  preload: [:podcast])
               |> Repo.paginate(page: params["page"], page_size: 10)

    render(conn, "index.html", episodes: episodes)
  end

  def silence(conn, _params) do
    # Just here to silence a weird request from the podlove webplayer
    text conn, nil
  end
end

defmodule PanWeb.EpisodeFrontendController do
  use Pan.Web, :controller
  alias PanWeb.{Chapter, Episode, Image, Recommendation}

  def show(conn, %{"id" => id}) do
    episode = Repo.get!(Episode, id)
              |> Repo.preload([:podcast, :enclosures, gigs: :persona, recommendations: :user])
              |> Repo.preload(chapters: from(chapter in Chapter, order_by: chapter.start,
                                                                 preload: [recommendations: :user]))

    episode_thumbnail = Repo.get_by(Image, episode_id: episode.id) ||
                        Repo.get_by(Image, podcast_id: episode.podcast.id)

    changeset = Recommendation.changeset(%Recommendation{})
    # options for player: "podlove", "podigee"
    render(conn, "show.html", episode: episode,
                              episode_thumbnail: episode_thumbnail,
                              player: "podlove",
                              changeset: changeset)
  end

  def player(conn, %{"id" => id}) do
    episode = Repo.get!(Episode, id)
              |> Repo.preload([:podcast, :enclosures, gigs: :persona, recommendations: :user])
              |> Repo.preload(chapters: from(chapter in Chapter, order_by: chapter.start))

    episode_thumbnail = Repo.get_by(Image, episode_id: episode.id) ||
                        Repo.get_by(Image, podcast_id: episode.podcast.id)

    # options for player: "podlove", "podigee"
    conn
    |> put_layout("minimal.html")
    |> render("player.html", episode: episode,
                             episode_thumbnail: episode_thumbnail,
                             player: "podlove")
  end

  def index(conn, params) do
    episodes = from(e in Episode, join: p in assoc(e, :podcast),
                                  where: is_false(p.blocked) and
                                         e.publishing_date < ^NaiveDateTime.utc_now(),
                                  order_by: [desc: :publishing_date],
                                  preload: [:podcast])
               |> Repo.paginate(page: params["page"], page_size: 10)

    render(conn, "index.html", episodes: episodes)
  end

  def silence(conn, _params) do
    # Just here to silence a weird request from the podlove webplayer
    text conn, nil
  end
end


remove async in init

remove async in init

defmodule NeoscanMonitor.Worker do
  @moduledoc """
  GenServer module responsable to store blocks, states, trasactions and assets,
  Common interface to handle it is NeoscanMonitor.Api module(look there for more info)
  """

  use GenServer
  alias NeoscanMonitor.Utils
  alias NeoscanMonitor.Server
  alias Neoscan.Blocks
  alias Neoscan.Transactions
  alias Neoscan.Addresses
  alias Neoscan.ChainAssets
  alias Neoprice.NeoBtc
  alias Neoprice.NeoUsd
  alias Neoprice.GasBtc
  alias Neoprice.GasUsd

  #starts the genserver
  def start_link do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  #run initial queries and fill state with all info needed in the app,
  #then sends message with new state to server module
  def init(:ok) do
    monitor_nodes = Utils.load()

    blocks = Blocks.home_blocks

    transactions = Transactions.home_transactions
                   |> Utils.add_vouts

    assets = ChainAssets.list_assets
             |> Utils.get_stats

    contracts = Transactions.list_contracts

    stats = Utils.get_general_stats()

    addresses = Addresses.list_latest()
                |> Utils.count_txs

    price = %{
      neo: %{
        btc: NeoBtc.last_price_full(),
        usd: NeoUsd.last_price_full()
      },
      gas: %{
        btc: GasBtc.last_price_full(),
        usd: GasUsd.last_price_full()
      }
    }

    new_state = %{
      :monitor => monitor_nodes,
      :blocks => blocks,
      :transactions => transactions,
      :assets => assets,
      :contracts => contracts,
      :stats => stats,
      :addresses => addresses,
      :price => price,
    }

    Process.send(
      NeoscanMonitor.Server,
      {:state_update, new_state},
      []
    )
    schedule_nodes()
    schedule_update()
    {:ok, new_state}
  end

  #update nodes and stats information
  def handle_info(:update_nodes, state) do
    schedule_nodes() # Reschedule once more
    new_state = Map.merge(
      state,
      %{
        :monitor => Utils.load(),
        :assets => ChainAssets.list_assets
                   |> Utils.get_stats,
        :stats => Utils.get_general_stats(),
        :addresses => Addresses.list_latest()
                      |> Utils.count_txs,
        :price => %{
          neo: %{
            btc: NeoBtc.last_price_full(),
            usd: NeoUsd.last_price_full()
          },
          gas: %{
            btc: GasBtc.last_price_full(),
            usd: GasUsd.last_price_full()
          }
        },
      }
    )

    {:noreply, new_state}
  end

  #updates the state in the server module
  def handle_info(:update, state) do
    schedule_update() # Reschedule once more
    Process.send(Server, {:state_update, state}, [])
    {:noreply, state}
  end

  #handles misterious messages received by unknown caller
  def handle_info({_ref, {:ok, _port, _pid}}, state) do
    {:noreply, state}
  end

  #adds a block to the state
  def handle_cast({:add_block, block}, state) do
    count = Enum.count(state.blocks)
    new_blocks = [
                   %{
                     :index => block.index,
                     :time => block.time,
                     :tx_count => block.tx_count,
                     :hash => block.hash,
                     :size => block.size
                   } | state.blocks
                 ]
                 |> Utils.cut_if_more(count)

    new_state = Map.put(state, :blocks, new_blocks)
    {:noreply, new_state}
  end

  #adds a transaction to the state
  def handle_cast({:add_transaction, transaction, vouts}, state) do
    count = Enum.count(state.transactions)
    clean_vouts = Enum.map(
      vouts,
      fn vout ->
        {:ok, result} = Morphix.atomorphiform(vout)
        Map.merge(
          result,
          %{
            :address_hash => result.address,
            :asset => String.slice(to_string(result.asset), -64..-1),
          }
        )
        |> Map.delete(:address)
      end
    )
    new_transactions = [
                         %{
                           :id => transaction.id,
                           :type => transaction.type,
                           :time => transaction.time,
                           :txid => transaction.txid,
                           :block_height => transaction.block_height,
                           :block_hash => transaction.block_hash,
                           :vin => transaction.vin,
                           :claims => transaction.claims,
                           :sys_fee => transaction.sys_fee,
                           :net_fee => transaction.net_fee,
                           :size => transaction.size,
                           :vouts => clean_vouts,
                         } | state.transactions
                       ]
                       |> Utils.cut_if_more(count)

    new_state = Map.put(state, :transactions, new_transactions)
    {:noreply, new_state}
  end

  #adds an asset to the state
  def handle_cast({:add_asset, asset}, state) do
    new_asset = %{
      :txid => asset.txid,
      :admin => asset.admin,
      :amount => asset.amount,
      :issued => asset.issued,
      :type => asset.type,
      :time => asset.time,
      :name => asset.name,
      :owner => asset.owner,
      :precision => asset.precision,
    }

    new_assets = [new_asset | state.assets]

    new_state = Map.put(state, :assets, new_assets)
    {:noreply, new_state}
  end

  #adds a contract to the state
  def handle_cast({:add_contract, contract, vouts}, state) do
    clean_vouts = Enum.map(
      vouts,
      fn vout ->
        {:ok, result} = Morphix.atomorphiform(vout)
        Map.put(result, :address_hash, result.address)
        |> Map.delete(:address)
      end
    )
    new_contracts = [Map.put(contract, :vouts, clean_vouts) | state.contracts]

    new_state = Map.put(state, :contracts, new_contracts)
    {:noreply, new_state}
  end

  #schedule msgs to perform updates
  defp schedule_nodes do
    Process.send_after(self(), :update_nodes, 30_000) # In 30s
  end

  #schedule msgs to perform updates
  defp schedule_update do
    Process.send_after(self(), :update, 10_000) # In 10s
  end

end

defmodule NeoscanMonitor.Worker do
  @moduledoc """
  GenServer module responsable to store blocks, states, trasactions and assets,
  Common interface to handle it is NeoscanMonitor.Api module(look there for more info)
  """

  use GenServer
  alias NeoscanMonitor.Utils
  alias NeoscanMonitor.Server
  alias Neoscan.Blocks
  alias Neoscan.Transactions
  alias Neoscan.Addresses
  alias Neoscan.ChainAssets
  alias Neoprice.NeoBtc
  alias Neoprice.NeoUsd
  alias Neoprice.GasBtc
  alias Neoprice.GasUsd

  #starts the genserver
  def start_link do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  #run initial queries and fill state with all info needed in the app,
  #then sends message with new state to server module
  def init(:ok) do
    monitor_nodes = Task.async(fn -> Utils.load() end)

    blocks = Task.async(fn -> Blocks.home_blocks end)

    transactions = Task.async(
      fn ->
        Transactions.home_transactions
        |> Utils.add_vouts
      end
    )

    assets = Task.async(
      fn ->
        ChainAssets.list_assets
        |> Utils.get_stats
      end
    )

    contracts = Task.async(fn -> Transactions.list_contracts end)

    stats = Task.async(fn -> Utils.get_general_stats() end)

    addresses = Task.async(
      fn ->
        Addresses.list_latest()
        |> Utils.count_txs
      end
    )

    price = %{
      neo: %{
        btc: NeoBtc.last_price_full(),
        usd: NeoUsd.last_price_full()
      },
      gas: %{
        btc: GasBtc.last_price_full(),
        usd: GasUsd.last_price_full()
      }
    }

    new_state = %{
      :monitor => Task.await(monitor_nodes, 120_000),
      :blocks => Task.await(blocks, 120_000),
      :transactions => Task.await(transactions, 120_000),
      :assets => Task.await(assets, 120_000),
      :contracts => Task.await(contracts, 120_000),
      :stats => Task.await(stats, 120_000),
      :addresses => Task.await(addresses, 120_000),
      :price => price,
    }

    Process.send(
      NeoscanMonitor.Server,
      {:state_update, new_state},
      []
    )
    schedule_nodes()
    schedule_update()
    {:ok, new_state}
  end

  #update nodes and stats information
  def handle_info(:update_nodes, state) do
    schedule_nodes() # Reschedule once more
    new_state = Map.merge(
      state,
      %{
        :monitor => Utils.load(),
        :assets => ChainAssets.list_assets
                   |> Utils.get_stats,
        :stats => Utils.get_general_stats(),
        :addresses => Addresses.list_latest()
                      |> Utils.count_txs,
        :price => %{
          neo: %{
            btc: NeoBtc.last_price_full(),
            usd: NeoUsd.last_price_full()
          },
          gas: %{
            btc: GasBtc.last_price_full(),
            usd: GasUsd.last_price_full()
          }
        },
      }
    )

    {:noreply, new_state}
  end

  #updates the state in the server module
  def handle_info(:update, state) do
    schedule_update() # Reschedule once more
    Process.send(Server, {:state_update, state}, [])
    {:noreply, state}
  end

  #handles misterious messages received by unknown caller
  def handle_info({_ref, {:ok, _port, _pid}}, state) do
    {:noreply, state}
  end

  #adds a block to the state
  def handle_cast({:add_block, block}, state) do
    count = Enum.count(state.blocks)
    new_blocks = [
                   %{
                     :index => block.index,
                     :time => block.time,
                     :tx_count => block.tx_count,
                     :hash => block.hash,
                     :size => block.size
                   } | state.blocks
                 ]
                 |> Utils.cut_if_more(count)

    new_state = Map.put(state, :blocks, new_blocks)
    {:noreply, new_state}
  end

  #adds a transaction to the state
  def handle_cast({:add_transaction, transaction, vouts}, state) do
    count = Enum.count(state.transactions)
    clean_vouts = Enum.map(
      vouts,
      fn vout ->
        {:ok, result} = Morphix.atomorphiform(vout)
        Map.merge(
          result,
          %{
            :address_hash => result.address,
            :asset => String.slice(to_string(result.asset), -64..-1),
          }
        )
        |> Map.delete(:address)
      end
    )
    new_transactions = [
                         %{
                           :id => transaction.id,
                           :type => transaction.type,
                           :time => transaction.time,
                           :txid => transaction.txid,
                           :block_height => transaction.block_height,
                           :block_hash => transaction.block_hash,
                           :vin => transaction.vin,
                           :claims => transaction.claims,
                           :sys_fee => transaction.sys_fee,
                           :net_fee => transaction.net_fee,
                           :size => transaction.size,
                           :vouts => clean_vouts,
                         } | state.transactions
                       ]
                       |> Utils.cut_if_more(count)

    new_state = Map.put(state, :transactions, new_transactions)
    {:noreply, new_state}
  end

  #adds an asset to the state
  def handle_cast({:add_asset, asset}, state) do
    new_asset = %{
      :txid => asset.txid,
      :admin => asset.admin,
      :amount => asset.amount,
      :issued => asset.issued,
      :type => asset.type,
      :time => asset.time,
      :name => asset.name,
      :owner => asset.owner,
      :precision => asset.precision,
    }

    new_assets = [new_asset | state.assets]

    new_state = Map.put(state, :assets, new_assets)
    {:noreply, new_state}
  end

  #adds a contract to the state
  def handle_cast({:add_contract, contract, vouts}, state) do
    clean_vouts = Enum.map(
      vouts,
      fn vout ->
        {:ok, result} = Morphix.atomorphiform(vout)
        Map.put(result, :address_hash, result.address)
        |> Map.delete(:address)
      end
    )
    new_contracts = [Map.put(contract, :vouts, clean_vouts) | state.contracts]

    new_state = Map.put(state, :contracts, new_contracts)
    {:noreply, new_state}
  end

  #schedule msgs to perform updates
  defp schedule_nodes do
    Process.send_after(self(), :update_nodes, 30_000) # In 30s
  end

  #schedule msgs to perform updates
  defp schedule_update do
    Process.send_after(self(), :update, 10_000) # In 10s
  end

end


Cleanup debug logging

Cleanup debug logging

require Logger

defmodule Serverboards.Query do
  @moduledoc ~S"""
  Universal Service Query.

  This module allows to use the services extractors to extract data and
  perform SQL querys.

  This is exported to the users at:
    * `dashboards.widgets.extract widget_id, extra_params`
    * `query.query sql, extra_params, context_data`

  Also available:
   * `query.schema [table]`

  context_data is the dictionary of services and configs. extra_params are
  extra parameters that are allowed to change in the `dashboards.widgets.extract`
  that does not require special permissions to access all data.
  """

  def execute(config, table, quals, columns) do
    extractor = config.extractor

  # Logger.debug("Use extractor #{inspect extractor}")
    [component] = Serverboards.Plugin.Registry.filter_component(id: extractor)

    case Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["extractor"], [config, table, quals, columns], config.user) do
      {:ok, result} ->
        {:ok, %{ columns: result["columns"], rows: result["rows"] }}
      other -> other
    end
  end

  @doc ~S"""
  Returns the list of tables on this extractor
  """
  def schema(config) do
  # Logger.debug("schema #{inspect config}")
    extractor = config.extractor
    case Serverboards.Plugin.Registry.filter_component(id: extractor) do
      [component] ->
        Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["schema"], [config, nil], config.user)
      _ ->
        {:error, :unknown_extractor}
    end

  end

  @doc ~S"""
  Returns the schema of the given table.
  """
  def schema(config, table) do
    extractor = config.extractor
    case Serverboards.Plugin.Registry.filter_component(id: extractor) do
      [component] ->
        res = Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["schema"], [config, table], config.user)
        case res do
          {:ok, %{ "columns" => columns}} ->
            columns = Enum.map(columns, fn
              %{ "name" => name } -> name
              other when is_binary(other) -> other
            end)
            {:ok, %{ columns: columns }}

          other -> other
        end
      _ ->
        {:error, {:unknown_extractor, extractor}}
    end

  end

  def query(query, context) do
    context = Enum.map(context, fn {k,v} ->
      nv = {Serverboards.Query, v}
      {k, nv}
    end) |> Map.new

    # If not starts with SELECT, it is a simple text to use
    if not String.starts_with?(String.upcase(query), "SELECT") do
      {:ok, %{ columns: "?VALUE?", rows: [[query]]} }
    else
      # Logger.debug("Processed context #{inspect context}")

      try do
        with {:ok, %{ columns: columns, rows: rows}} <- ExoSQL.query(query, context) do
          {:ok, %{ columns: columns, rows: rows}}
        end
      catch
        any ->
          {:error, any}
      rescue
        e in MatchError ->
          Logger.error(inspect e)
          {:error, :invalid_sql}
        e in FunctionClauseError ->
          Logger.error(inspect e)
          {:error, :invalid_expression}
        exception ->
          Logger.error("#{inspect exception}: #{inspect System.stacktrace, pretty: true}")
          {:error, "Meditation code: `#{inspect exception}`. Ask for help at the forums."}
      end
    end
  end
end

require Logger

defmodule Serverboards.Query do
  @moduledoc ~S"""
  Universal Service Query.

  This module allows to use the services extractors to extract data and
  perform SQL querys.

  This is exported to the users at:
    * `dashboards.widgets.extract widget_id, extra_params`
    * `query.query sql, extra_params, context_data`

  Also available:
   * `query.schema [table]`

  context_data is the dictionary of services and configs. extra_params are
  extra parameters that are allowed to change in the `dashboards.widgets.extract`
  that does not require special permissions to access all data.
  """

  def execute(config, table, quals, columns) do
    extractor = config.extractor

    Logger.debug("Use extractor #{inspect extractor}")
    [component] = Serverboards.Plugin.Registry.filter_component(id: extractor)

    case Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["extractor"], [config, table, quals, columns], config.user) do
      {:ok, result} ->
        {:ok, %{ columns: result["columns"], rows: result["rows"] }}
      other -> other
    end
  end

  @doc ~S"""
  Returns the list of tables on this extractor
  """
  def schema(config) do
    Logger.debug("schema #{inspect config}")
    extractor = config.extractor
    case Serverboards.Plugin.Registry.filter_component(id: extractor) do
      [component] ->
        Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["schema"], [config, nil], config.user)
      _ ->
        {:error, :unknown_extractor}
    end

  end

  @doc ~S"""
  Returns the schema of the given table.
  """
  def schema(config, table) do
    extractor = config.extractor
    case Serverboards.Plugin.Registry.filter_component(id: extractor) do
      [component] ->
        res = Serverboards.Plugin.Runner.start_call_stop(component.extra["command"], component.extra["schema"], [config, table], config.user)
        case res do
          {:ok, %{ "columns" => columns}} ->
            columns = Enum.map(columns, fn
              %{ "name" => name } -> name
              other when is_binary(other) -> other
            end)
            {:ok, %{ columns: columns }}

          other -> other
        end
      _ ->
        {:error, {:unknown_extractor, extractor}}
    end

  end

  def query(query, context) do
    context = Enum.map(context, fn {k,v} ->
      nv = {Serverboards.Query, v}
      {k, nv}
    end) |> Map.new

    # If not starts with SELECT, it is a simple text to use
    if not String.starts_with?(String.upcase(query), "SELECT") do
      {:ok, %{ columns: "?VALUE?", rows: [[query]]} }
    else
      # Logger.debug("Processed context #{inspect context}")

      try do
        with {:ok, %{ columns: columns, rows: rows}} <- ExoSQL.query(query, context) do
          {:ok, %{ columns: columns, rows: rows}}
        end
      catch
        any ->
          {:error, any}
      rescue
        e in MatchError ->
          Logger.error(inspect e)
          {:error, :invalid_sql}
        e in FunctionClauseError ->
          Logger.error(inspect e)
          {:error, :invalid_expression}
        exception ->
          Logger.error("#{inspect exception}: #{inspect System.stacktrace, pretty: true}")
          {:error, "Meditation code: `#{inspect exception}`. Ask for help at the forums."}
      end
    end
  end
end


feat(guardian_tokens): fix migration

feat(guardian_tokens): fix migration

defmodule Lyn.Repo.Migrations.CreateGuardianToken do
  use Ecto.Migration

  def change do
    create table(:guardian_tokens, primary_key: false) do
      add :jti, :string, primary_key: true
      add :aud, :string
      add :iss, :string
      add :sub, :string
      add :exp, :bigint
      add :jwt, :text
      add :claims, :map
      add :typ, :string

      timestamps
    end

    create index(:guardian_tokens, [:typ])
  end
end

defmodule Lyn.Repo.Migrations.CreateGuardianToken do
  use Ecto.Migration

  def change do
    create table(:guardian_tokens) do
      add :jti, :string, primary_key: true
      add :aud, :string
      add :iss, :string
      add :sub, :string
      add :exp, :bigint
      add :jwt, :text
      add :claims, :map
      add :typ, :string

      timestamps
    end

    create index(:guardian_tokens, [:typ])
  end
end