defmodule Irc.Store do @moduledoc """ Mnesia-based store for `Irc.Client`. Tables: * User (transient, in memory): tracks known connected users. * Channel (transient, in memory): tracks known channels. ## Setup Needs to be ran only once at setup: ```elixir # First step: create schemas. Memento.stop() Memento.Schema.create([node()]) Memento.start() # Second step: create tables Irc.Store.setup() ``` """ @doc false # Childrens for Irc.Application supervisor tree def childs do [{Irc.Store.OwnerSweeper, []}] end @doc "Creates tables in Mnesia. Should only be run once." def setup do Memento.Table.create!(Irc.Store.User) end defmodule OwnerSweeper do @moduledoc """ Ensures the Store only keeps alive data by monitoring owners and deleting entries when their owner dies. """ use GenServer require Logger @doc "Register calling process to the `OwnerSweeper` process." def register() do GenServer.call(__MODULE__, {:monitor, self()}) end @doc "Unregister calling process from the sweeper and delete all its entries." def unregister() do GenServer.call(__MODULE__, {:unregister, self()}) end @doc false def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end @impl true @doc false def init(_) do {:ok, Map.new} end @impl true @doc false def handle_call({:register, pid}, _, monitors) do if Map.get(monitors, pid) do {:reply, :ok, monitors} else Logger.debug("Irc.Store.OwnerSweeper: added to sweep list #{inspect(pid)}") monitor = Process.monitor(pid) {:reply, :ok, Map.put(monitors, pid, monitor)} end end @impl true @doc false def handle_call({:unregister, pid}, _, monitors) do if ref = Map.get(monitors, pid) do Process.demonitor(ref, [:flush]) clear_entries(pid) {:reply, :ok, Map.drop(monitors, pid)} else {:reply, :not_registered, monitors} end end @impl true @doc false def handle_info({:DOWN, monitor, :process, pid, _reason}, monitors) do Logger.debug("Irc.Store.OwnerSweeper: removing entries of #{inspect(pid)}") if monitor == Map.get(monitors, pid) do # TODO: Delete all `User` entries where owner == pid clear_entries(pid) {:noreply, Map.drop(monitors, pid)} else {:noreply, monitors} end end defp clear_entries(pid) do # TODO: Find some way to use built in mnesia select_delete for table <- [Irc.Store.User] do Memento.transaction fn -> for entry <- Memento.Query.select(table, {:==, :owner, pid}) do Memento.delete(table, entry.id) end end end end end defmodule User do #attributes = Irc.User.__attrs__ #|> Enum.map(fn # ({k, _}) -> k # k -> k #end) #IO.inspect(attributes) #use Memento.Table, # attributes: attributes, # index: [:owner, :host, :nick, :account] end end