defmodule Fermo.Live.SocketRegistry do
use GenServer
@name :fermo_registry
def init(_opts) do
{:ok, %{}}
end
def start_link(_opts) do
GenServer.start_link(__MODULE__, nil, name: @name)
end
def subscribe(path, pid) when is_pid(pid) do
path = if String.ends_with?(path, "/"), do: path, else: path <> "/"
GenServer.call(@name, {:subscribe, path, pid})
end
def unsubscribe(pid) do
GenServer.call(@name, {:unsubscribe, pid})
end
def subscribed() do
GenServer.call(@name, {:subscribed})
end
def subscribed(nil) do
GenServer.call(@name, {:subscribed})
end
def subscribed(paths) when is_list(paths) do
Enum.map(paths, &(subscribed(&1)))
|> List.flatten()
end
def subscribed(path) when is_binary(path) do
GenServer.call(@name, {:subscribed, path})
end
def subscribed(%Regex{} = path) do
GenServer.call(@name, {:subscribed, path})
end
def reload(path \\ nil) do
subscribed(path)
|> Enum.each(fn pid ->
send(pid, {:reload})
end)
{:ok}
end
def handle_call({:subscribe, path, pid}, _from, registry) do
subscribed = [pid | registry[path] || []]
Process.monitor(pid)
registry = Map.put(registry, path, subscribed)
{:reply, :ok, registry}
end
def handle_call({:unsubscribe, pid}, _from, registry) do
{:reply, :unsubscribed, unsubscribe_pid(registry, pid)}
end
def handle_call({:subscribed, %Regex{} = match}, _from, registry) do
matching =
registry
|> Enum.filter(fn {path, _pids} -> String.match?(path, match) end)
|> List.flatten()
{:reply, matching, registry}
end
def handle_call({:subscribed, path}, _from, registry) when is_binary(path) do
{:reply, Map.get(registry, path, []), registry}
end
def handle_call({:subscribed}, _from, registry) do
all_pids = Enum.map(registry, fn {_path, pids} -> pids end)
|> List.flatten()
{:reply, all_pids, registry}
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, registry) do
{:noreply, unsubscribe_pid(registry, pid)}
end
def handle_info(_info, registry), do: {:noreply, registry}
defp unsubscribe_pid(registry, pid) do
Enum.reduce(registry, %{}, fn {path, pids}, acc ->
other = Enum.filter(pids, &(&1 != pid))
Map.put(acc, path, other)
end)
end
end