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