summaryrefslogblamecommitdiff
path: root/lib/irc/membership.ex
blob: 74ed1b4d3fd3d47e104693df829596422ff60e46 (plain) (tree)
































































































































                                                                                                                        
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