lib/baby/application.ex

defmodule Baby.Application do
  @moduledoc """
  By design the `Baby` server does not start automatically.

  To use the `:baby` configuration, it may be started via:

  ```
  Baby.Application.start(:normal) 
  ```

  One may also craft a custom configuration to be supplied at runtime:

  ```
  config = [spool_dir: "~/.special_bamboo"]
  Baby.Application.start(:normal, config)
  ```
  """

  use Application

  @impl true
  def start(type, args \\ [])
  def start(type, []), do: start(type, Application.get_all_env(:baby))

  def start(_type, args) do
    spool_path = Keyword.get(args, :spool_dir)
    baobab_spool = Path.expand(spool_path)
    :ok = Application.put_env(:baobab, :spool_dir, baobab_spool)
    # It is presumed that all available clumps are established here
    # A later `Baby.connect` will not create missing clumps
    clumps = Keyword.get(args, :clumps, [])

    for clump <- clumps do
      case Keyword.get(clump, :id) do
        nil -> :ok
        clump_id -> File.mkdir_p(Path.join([baobab_spool, clump_id]))
      end
    end

    children =
      clumps_setup(clumps)
      |> Enum.reduce([], fn {port, identity, clump_id, cryouts}, a ->
        # The configured identity must exist
        :ranch.start_listener(
          String.to_atom("baby_" <> clump_id),
          :ranch_tcp,
          [port: port],
          Baby.Connection,
          identity: identity,
          clump_id: clump_id
        )

        [
          Supervisor.child_spec(
            {Baby.Monitor, %{cryouts: cryouts, identity: identity, clump_id: clump_id}},
            id: String.to_atom(clump_id)
          )
          | a
        ]
      end)

    opts = [strategy: :one_for_one, name: Baby.Supervisor]
    Supervisor.start_link(children, opts)
  end

  @doc """
  A usable child specification for starting under a supervision tree
  """
  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start, [:normal, opts]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  defp clumps_setup(clumps, acc \\ [])
  defp clumps_setup([], acc), do: acc

  defp clumps_setup([clump | rest], acc) do
    whoami = Keyword.get(clump, :controlling_identity, "default")

    case Baobab.Identity.key(whoami, :public) do
      :error -> Baobab.Identity.create(whoami, Keyword.get(clump, :controlling_secret))
      _ -> :ok
    end

    clump_id = Keyword.get(clump, :id)
    port = Keyword.get(clump, :port, 8483)
    cryouts = Keyword.get(clump, :cryouts, [])
    clumps_setup(rest, [{port, whoami, clump_id, cryouts} | acc])
  end
end