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
|