defmodule Nola.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(%Nola.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(%Nola.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 = Nola.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 = %Nola.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 = Nola.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