lib/dvr.ex

defmodule DVR do
  @moduledoc """
  Documentation for Dvr.
  """

  @type replay_id() :: integer()
  @type payload() :: any()
  @type topics() :: list(String.t())

  require Logger

  @spec record(payload(), topics()) :: {:ok, replay_id()} | {:error, any()}
  def record(payload, topics), do: record(payload, topics, calculate_id())

  def record(payload, topics, id) do
    write = fn -> :mnesia.write({:dvr, id, payload, topics}) end

    case :mnesia.transaction(write) do
      {:atomic, _} ->
        {:ok, id}

      err ->
        {:error, err}
    end
  end

  @spec replay(replay_id()) :: {:ok, list(tuple)} | {:error, any()}
  def replay(id) do
    select = fn ->
      :mnesia.select(:dvr, [
        {
          {:dvr, :"$1", :"$2", :"$3"},
          [{:>, :"$1", id}],
          [:"$$"]
        }
      ])
    end

    case :mnesia.transaction(select) do
      {:atomic, result} ->
        Stream.map(result, fn [_, payload, topics] -> {payload, topics} end)

      err ->
        {:error, err}
    end
  end

  @spec search(replay_id()) :: {:ok, replay_id()} | {:not_found, replay_id()}
  def search(id) do
    read = fn -> :mnesia.read({:dvr, id}) end

    case :mnesia.transaction(read) do
      {:atomic, []} ->
        case :mnesia.transaction(fn -> :mnesia.first(:dvr) end) do
          {:atomic, earliest_id} ->
            {:not_found, earliest_id}

          err ->
            {:error, err}
        end

      {:atomic, _} ->
        {:ok, id}

      err ->
        {:error, err}
    end
  end

  @spec calculate_id() :: replay_id()
  def calculate_id, do: DateTime.utc_now() |> DateTime.to_unix(:microsecond)
end