summaryrefslogtreecommitdiff
path: root/lib/irc/membership.ex
blob: b727dfd614b7d9e9e8c7c19d1b9bb9fe935444d0 (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
defmodule IRC.Membership do
  @moduledoc """
  Memberships (users in channels)
  """

  # Key: {account, net, channel}
  # Format: {key, last_seen}

  defp dets() do
    to_charlist(Nola.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