live_beats/lib/live_beats/user_tracker.ex
2022-01-28 10:18:50 -06:00

110 lines
2.6 KiB
Elixir

defmodule LiveBeats.UserTracker do
@moduledoc """
Send active users updates using a polling interval.
"""
use GenServer
@pubsub LiveBeats.PubSub
@poll_interval :timer.seconds(30)
def subscribe() do
Phoenix.PubSub.subscribe(@pubsub, topic())
end
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def list_active_users() do
GenServer.call(__MODULE__, :list_users)
end
def presence_joined(presence) do
GenServer.call(__MODULE__, {:presence_joined, presence})
end
def presence_left(presence) do
GenServer.call(__MODULE__, {:presence_left, presence})
end
@impl true
def init(_opts) do
{:ok,
schedule_updates(%{
active_users: %{},
user_leaves: [],
user_joins: []
})}
end
@impl true
def handle_call(:list_users, _from, state) do
{:reply, list_users(state), state}
end
@impl true
def handle_call({:presence_joined, presence}, _from, state) do
{:reply, :ok, handle_join(state, presence)}
end
@impl true
def handle_call({:presence_left, presence}, _from, state) do
{:reply, :ok, handle_leave(state, presence)}
end
@impl true
def handle_info(:send_updates, state) do
leaves = state.user_leaves -- state.user_joins
joins = state.user_joins -- state.user_leaves
broadcast_updates(leaves, joins)
# cleaning joins and leaves for each interval
new_state = %{state | user_leaves: [], user_joins: []}
{:noreply, schedule_updates(new_state)}
end
defp schedule_updates(state) do
Process.send_after(self(), :send_updates, @poll_interval)
state
end
defp handle_join(state, %{user: user}) do
if Map.has_key?(state.active_users, user.id) do
state
else
updated_active_users = Map.put_new(state.active_users, user.id, user)
updated_user_joins = [user | state.user_joins]
%{state | active_users: updated_active_users, user_joins: updated_user_joins}
end
end
defp handle_leave(state, %{user: user, metas: metas}) do
if Map.has_key?(state.active_users, user.id) and metas == [] do
updated_active_users = Map.delete(state.active_users, user.id)
updated_user_leaves = [user | state.user_leaves]
%{state | active_users: updated_active_users, user_leaves: updated_user_leaves}
else
state
end
end
defp topic() do
"active_users"
end
defp broadcast_updates(leaves, joins) do
Phoenix.PubSub.local_broadcast(
@pubsub,
topic(),
{LiveBeats.UserTracker, %{user_leaves: leaves, user_joins: joins}}
)
end
defp list_users(state) do
Enum.map(state.active_users, fn {_key, value} -> value end)
end
end