lib/a2s/client.ex

defmodule A2S.Client do
  @moduledoc """
  An easy to use client that performs all the handshaking and busy-work necessary to make complete A2S queries.

  This client handles requests concurrently and should be suitable for most general uses.

  Note: must supply a :name
  """

  use Supervisor

  ## API

  def start_link(opts) do
    name = a2s_name!(opts)
    port = Keyword.get(opts, :port, 20850)
    parser_timeout = Keyword.get(opts, :parser_timeout, 120 * 1000)

    config = %{
      port: port,
      parser_timeout: parser_timeout
    }

    Supervisor.start_link(__MODULE__, config, name: supervisor_name(name))
  end

  def child_spec(opts) do
    %{
      id: a2s_name!(opts),
      start: {__MODULE__, :start_link, [opts]}
    }
  end

  @doc """
  Query a game server running at `address` for the data specified by `query`.
  """
  @spec query(query :: :info | :players | :rules, {:inet.ip_address, :inet.port_number}) ::
  {:info, A2S.Info.t}
  | {:players | A2S.Players.t}
  | {:rules | A2s.Rules.t}
  | {:error, any}
  def query(query, address, timeout \\ 5000) do
    :gen_statem.call(find_or_start(address), query, timeout)
  end

  ## CALLBACKS

  @impl true
  def init(config) do
    children = [
      {Registry, [keys: :unique, name: :a2s_registry]},
      # these are ignored for the time being
      {A2S.Supervisor, [name: A2S.Supervisor, parser_timeout: config.parser_timeout]},
      {A2S.UDP, [port: config.port]}
    ]

    Supervisor.init(children, strategy: :one_for_all)
  end

  ## FUNCTIONS

  defp a2s_name!(opts) do
    Keyword.get(opts, :name) || raise(ArgumentError, "must supply a name")
  end

  defp supervisor_name(name), do: :"#{name}.Supervisor"

  @spec find_or_start({:inet.ip_address, :inet.port_number}) :: pid
  defp find_or_start(address) do
    case Registry.lookup(:a2s_registry, address) do
      [{pid, _value}] -> pid
      [] ->
        case A2S.Supervisor.start_child(address) do
          {:ok, pid} -> pid
          {:ok, pid, _info} -> pid
          {:error, {:already_started, pid}} -> pid
        end
    end
  end
end