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
|
defmodule Nola.Plugins.Link.Store do
alias DialyxirVendored.Warnings.Apply
use GenServer
require Logger
require Record
import Ex2ms
@type url() :: String.t()
Record.defrecord(:link, link: nil, result: nil, at: nil)
@type link :: record(:link, link: url(), result: any(), at: nil)
Record.defrecord(:link_seen, key: nil, at: nil)
@doc "A `link_seen` record represents a link that has been seen at a specific time in a given context."
@type link_seen :: record(:link_seen, key: {url(), String.t()}, at: nil)
def setup do
:ets.new(:links, [:set, :public, :named_table, keypos: 2])
:ets.new(:links_witness, [:set, :public, :named_table, keypos: 2])
end
@spec insert_link(url(), any()) :: true
def insert_link(url, result) do
:ets.insert(
:links,
link(link: url, result: result, at: DateTime.utc_now() |> DateTime.to_unix())
)
end
@spec get_link(url()) :: any() | nil
def get_link(url) do
case :ets.lookup(:links, url) do
[link(result: result)] -> result
[] -> nil
end
end
@spec witness_link(url(), String.t()) :: boolean()
def inhibit_link?(url, key) do
case :ets.lookup(:links_witness, {url, key}) do
[_] -> true
[] -> false
end
end
@spec witness_link(url(), String.t()) :: :ok | :inhibit
def witness_link(url, key) do
if inhibit_link?(url, key) do
:inhibit
else
:ets.insert(
:links_witness,
link_seen(key: {url, key}, at: DateTime.utc_now() |> DateTime.to_unix())
)
:ok
end
end
def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
@doc false
@impl true
def init(_) do
setup()
env = Keyword.fetch!(Application.get_env(:nola, Nola.Plugins.Link, []), :store)
:erlang.send_after(env[:interval], self(), :expire)
{:ok, nil}
end
@doc false
@impl true
def handle_info(:expire, state) do
env = Keyword.fetch!(Application.get_env(:nola, Nola.Plugins.Link, []), :store)
:erlang.send_after(env[:interval], self(), :expire)
ttl = env[:ttl] / 1000
inhibit = env[:inhibit] / 1000
now = DateTime.utc_now() |> DateTime.to_unix()
links_evicted =
:ets.select_delete(:links, [
{{:_, :_, :_, :"$1"}, [{:<, :"$1", now - ttl}], [true]}
])
witness_evicted =
:ets.select_delete(:links_witness, [
{{:_, :_, :"$1"}, [{:<, :"$1", now - inhibit}], [true]}
])
Logger.debug("evicted #{links_evicted} links and #{witness_evicted} witnesses")
{:noreply, state}
end
end
|