lib/storex/registries/ets.ex

defmodule Storex.Registry.ETS do
  @moduledoc """
  Registry for sessions and stores in ETS.
  """

  @behaviour Storex.Registry

  @doc false
  use GenServer

  @registry :storex_registry

  @doc false
  def start_link do
    GenServer.start_link(__MODULE__, nil, name: @registry)
  end

  @doc false
  def init(nil) do
    :ets.new(@registry, [:bag, :protected, :named_table])

    {:ok, %{}}
  end

  @doc false
  def register_session(session, pid) do
    GenServer.call(@registry, {:register_session, session, pid})
  end

  @doc false
  def unregister_session(session) do
    GenServer.call(@registry, {:unregister_session, session})
  end

  @doc false
  def session_pid(session) do
    GenServer.call(@registry, {:session_pid, session})
  end

  @doc false
  def register_store(session, store, pid) do
    GenServer.call(@registry, {:register_store, session, store, pid})
  end

  @doc false
  def unregister_store(session, store) do
    GenServer.call(@registry, {:unregister_store, session, store})
  end

  @doc false
  def get_store_pid(session, store) do
    GenServer.call(@registry, {:get_store_pid, session, store})
  end

  @doc false
  def session_stores(session) do
    GenServer.call(@registry, {:session_stores, session})
  end

  @doc false
  def handle_call({:register_session, session, pid}, _from, state) do
    :ets.insert(@registry, {session, nil, pid})
    {:reply, {:ok, pid}, state}
  end

  @doc false
  def handle_call({:unregister_session, session}, _from, state) do
    result = :ets.delete(@registry, session)
    {:reply, result, state}
  end

  @doc false
  def handle_call({:session_pid, session}, _from, state) do
    :ets.match(@registry, {session, nil, :"$1"})
    |> case do
      [] -> {:reply, :undefined, state}
      [[pid] | _tail] -> {:reply, pid, state}
    end
  end

  @doc false
  def handle_call({:register_store, session, store, pid}, _from, state) do
    :ets.insert(@registry, {session, store, pid})
    Process.monitor(pid)
    {:reply, {:ok, pid}, state}
  end

  @doc false
  def handle_call({:unregister_store, session, store}, _from, state) do
    result = :ets.match_delete(@registry, {session, store, :_})
    {:reply, result, state}
  end

  @doc false
  def handle_call({:get_store_pid, session, store}, _from, state) do
    :ets.match(@registry, {session, store, :"$1"})
    |> case do
      [] -> {:reply, :undefined, state}
      [[pid] | _tail] -> {:reply, pid, state}
    end
  end

  @doc false
  def handle_call({:session_stores, session}, _from, state) do
    stores = :ets.lookup(@registry, session)

    {:reply, stores, state}
  end

  @doc false
  def handle_info({:DOWN, _ref, :process, pid, _reason}, _state) do
    :ets.match_delete(@registry, {:_, :_, pid})

    {:noreply, :ok}
  end
end