lib/nostrum/cache/presence_cache/ets.ex

defmodule Nostrum.Cache.PresenceCache.ETS do
  @moduledoc """
  ETS-based cache for user presences.

  If you need to get the table reference for the table used by this module,
  please use the `table/0` function.
  """
  @moduledoc since: "0.5.0"

  alias Nostrum.Cache.PresenceCache

  @behaviour PresenceCache

  @table_name :nostrum_presences

  alias Nostrum.Struct.Guild
  use Supervisor

  @doc "Start the supervisor."
  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @doc "Retrieve the ETS table reference used for the cache."
  @doc since: "0.8.0"
  @spec table :: :ets.table()
  def table, do: @table_name

  @doc "Set up the cache's ETS table."
  @impl Supervisor
  def init(_init_arg) do
    :ets.new(@table_name, [:set, :public, :named_table])
    Supervisor.init([], strategy: :one_for_one)
  end

  @impl PresenceCache
  @doc "Add the given presence data to the cache."
  @spec create(map) :: :ok
  def create(presence) do
    :ets.insert(@table_name, {{presence.guild_id, presence.user.id}, presence})
    :ok
  end

  @impl PresenceCache
  @doc "Update the given presence data in the cache."
  @spec update(map()) :: {Guild.id(), presence | nil, presence} | :noop
        when presence: PresenceCache.presence()
  def update(new) do
    case :ets.lookup(@table_name, {new.guild_id, new.user.id}) do
      [{{guild_id, _}, old}] ->
        merged = Map.merge(old, new)
        create(merged)

        if old.activities == merged.activities and old.status == merged.status,
          do: :noop,
          else: {guild_id, old, merged}

      [] ->
        create(new)
        {new.guild_id, nil, new}
    end
  end

  @impl PresenceCache
  @doc "Bulk create multiple presences in the cache."
  @spec bulk_create(Guild.id(), [map]) :: :ok
  def bulk_create(_, []), do: :ok

  def bulk_create(guild_id, presences) when is_list(presences) do
    Enum.each(presences, fn p ->
      :ets.insert(@table_name, {{guild_id, p.user.id}, p})
    end)
  end

  @impl PresenceCache
  @doc "Retrieve a query handle for QLC queries."
  @doc since: "0.8.0"
  @spec query_handle :: :qlc.query_handle()
  def query_handle, do: :ets.table(@table_name)
end