defmodule IRC.UserTrack do @moduledoc """ User Track DB & Utilities """ @ets IRC.UserTrack.Storage # {uuid, nick, nicks, privilege_map} # Privilege map: # %{"#channel" => [:operator, :voice] defmodule Storage do def delete(id) do op(fn(ets) -> :ets.delete(ets, id) end) end def insert(tuple) do op(fn(ets) -> :ets.insert(ets, tuple) end) end def op(fun) do GenServer.call(__MODULE__, {:op, fun}) end def start_link do GenServer.start_link(__MODULE__, [], [name: __MODULE__]) end def init([]) do ets = :ets.new(__MODULE__, [:set, :named_table, :protected, {:read_concurrency, true}]) {:ok, ets} end def handle_call({:op, fun}, _from, ets) do returned = try do {:ok, fun.(ets)} rescue rescued -> {:error, rescued} catch rescued -> {:error, rescued} end {:reply, returned, ets} end def terminate(_reason, ets) do :ok end end defmodule Id, do: use EntropyString defmodule User do defstruct [:id, :nick, :nicks, :username, :host, :realname, :privileges] def to_tuple(u = %__MODULE__{}) do {u.id || IRC.UserTrack.Id.large_id, u.nick, u.nicks || [], u.username, u.host, u.realname, u.privileges} end def from_tuple({id, nick, nicks, username, host, realname, privs}) do %__MODULE__{id: id, nick: nick, nicks: nicks, username: username, realname: realname, privileges: privs} end end def find_by_nick(nick) do case :ets.match(@ets, {:'$1', nick, :_, :_, :_, :_, :_}) do [[id]] -> lookup(id) _ -> nil end end def to_list, do: :ets.tab2list(@ets) def lookup(id) do case :ets.lookup(@ets, id) do [] -> nil [tuple] -> User.from_tuple(tuple) end end def operator?(channel, nick) do if user = find_by_nick(nick) do privs = Map.get(user.privileges, channel, []) Enum.member?(privs, :admin) || Enum.member?(privs, :operator) else false end end def channel(channel) do Enum.filter(to_list(), fn({_, nick, _, _, _, _, channels}) -> Map.get(channels, channel) end) end def joined(c, s), do: joined(c,s,[]) def joined(channel, sender=%{nick: nick, user: uname, host: host}, privileges) do privileges = if IRC.admin?(sender) do privileges ++ [:admin] else privileges end user = if user = find_by_nick(nick) do %User{user | username: uname, host: host, privileges: Map.put(user.privileges || %{}, channel, privileges)} else %User{nick: nick, username: uname, host: host, privileges: %{channel => privileges}} end Storage.op(fn(ets) -> :ets.insert(ets, User.to_tuple(user)) end) end def joined(channel, nick, privileges) do user = if user = find_by_nick(nick) do %User{user | privileges: Map.put(user.privileges, channel, privileges)} else %User{nick: nick, privileges: %{channel => privileges}} end Storage.op(fn(ets) -> :ets.insert(ets, User.to_tuple(user)) end) end def renamed(old_nick, new_nick) do if user = find_by_nick(old_nick) do user = %User{user | nick: new_nick, nicks: [old_nick|user.nicks]} Storage.insert(User.to_tuple(user)) end end def change_privileges(channel, nick, {add, remove}) do if user = find_by_nick(nick) do privs = Map.get(user.privileges, channel) privs = Enum.reduce(add, privs, fn(priv, acc) -> [priv|acc] end) privs = Enum.reduce(remove, privs, fn(priv, acc) -> List.delete(acc, priv) end) user = %User{user | privileges: Map.put(user.privileges, channel, privs)} Storage.insert(User.to_tuple(user)) end end def parted(channel, nick) do if user = find_by_nick(nick) do privs = Map.delete(user.privileges, channel) if Enum.count(privs) > 0 do user = %User{user | privileges: privs} Storage.insert(User.to_tuple(user)) else Storage.delete(user.id) end end end def quitted(sender) do if user = find_by_nick(sender.nick) do Storage.delete(user.id) end end end