summaryrefslogtreecommitdiff
path: root/lib/irc/store.ex
blob: 5aaef6c39909396f412749ade21b5238f537eb48 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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