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
|
defmodule IRC.Membership do
@moduledoc """
Memberships (users in channels)
"""
# Key: {account, net, channel}
# Format: {key, last_seen}
defp dets() do
to_charlist(LSG.data_path <> "/memberships.dets")
end
def start_link() do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def init(_) do
dets = :dets.open_file(dets(), [])
{:ok, dets}
end
def of_account(%IRC.Account{id: id}) do
spec = [{{{:"$1", :"$2", :"$3"}, :_}, [{:==, :"$1", {:const, id}}], [{{:"$2", :"$3"}}]}]
:dets.select(dets(), spec)
end
def merge_account(old_id, new_id) do
#iex(37)> :ets.fun2ms(fn({{old_id, _, _}, _}=obj) when old_id == "42" -> obj end)
spec = [{{{:"$1", :_, :_}, :_}, [{:==, :"$1", {:const, old_id}}], [:"$_"]}]
Util.ets_mutate_select_each(:dets, dets(), spec, fn(table, obj = {{_old, net, chan}, ts}) ->
:dets.delete_object(table, obj)
:dets.insert(table, {{new_id, net, chan}, ts})
end)
end
def touch(%IRC.Account{id: id}, network, channel) do
:dets.insert(dets(), {{id, network, channel}, NaiveDateTime.utc_now()})
end
def touch(account_id, network, channel) do
if account = IRC.Account.get(account_id) do
touch(account, network, channel)
end
end
def notify_channels(account, minutes \\ 30, last_active \\ true) do
not_before = NaiveDateTime.add(NaiveDateTime.utc_now(), (minutes*-60), :second)
spec = [{{{:"$1", :_, :_}, :_}, [{:==, :"$1", {:const, account.id}}], [:"$_"]}]
memberships = :dets.select(dets(), spec)
|> Enum.sort_by(fn({_, ts}) -> ts end, {:desc, NaiveDateTime})
active_memberships = Enum.filter(memberships, fn({_, ts}) -> NaiveDateTime.compare(ts, not_before) == :gt end)
cond do
active_memberships == [] && last_active ->
case memberships do
[{{_, net, chan}, _}|_] -> [{net, chan}]
_ -> []
end
active_memberships == [] ->
[]
true ->
Enum.map(active_memberships, fn({{_, net, chan}, _}) -> {net,chan} end)
end
end
def members_or_friends(account, _network, nil) do
friends(account)
end
def members_or_friends(_, network, channel) do
members(network, channel)
end
def expanded_members_or_friends(account, network, channel) do
expand(network, members_or_friends(account, network, channel))
end
def expanded_members(network, channel) do
expand(network, members(network, channel))
end
def members(network, channel) do
#iex(19)> :ets.fun2ms(fn({{id, net, chan}, ts}) when net == network and chan == channel and ts > min_seen -> id end)
limit = 0 # NaiveDateTime.add(NaiveDateTime.utc_now, 30*((24*-60)*60), :second)
spec = [
{{{:"$1", :"$2", :"$3"}, :"$4"},
[
{:andalso,
{:andalso, {:==, :"$2", {:const, network}}, {:==, :"$3", {:const, channel}}},
{:>, :"$4", {:const, limit}}}
], [:"$1"]}
]
:dets.select(dets(), spec)
end
def friends(account = %IRC.Account{id: id}) do
for({net, chan} <- of_account(account), do: members(net, chan))
|> List.flatten()
|> Enum.uniq()
end
def handle_info(_, dets) do
{:noreply, dets}
end
def handle_cast(_, dets) do
{:noreply, dets}
end
def handle_call(_, _, dets) do
{:noreply, dets}
end
def terminate(_, dets) do
:dets.sync(dets)
:dets.close(dets)
end
defp expand(network, list) do
for id <- list do
if account = IRC.Account.get(id) do
user = IRC.UserTrack.find_by_account(network, account)
nick = if(user, do: user.nick, else: account.name)
{account, user, nick}
end
end
|> Enum.filter(fn(x) -> x end)
end
end
|