117 lines
3.9 KiB
Elixir
117 lines
3.9 KiB
Elixir
defmodule AprsmeWeb.MapLive.Subscriptions do
|
|
@moduledoc """
|
|
Manages Phoenix.PubSub subscriptions and connection lifecycle for the map LiveView.
|
|
Extracted from Index to reduce module size.
|
|
"""
|
|
|
|
import Phoenix.Component, only: [assign: 3]
|
|
import Phoenix.LiveView, only: [connected?: 1, push_event: 3, put_flash: 3]
|
|
|
|
alias Phoenix.LiveView.Socket
|
|
|
|
@doc """
|
|
Set up spatial and PubSub subscriptions for a newly mounted socket.
|
|
Only creates subscriptions when the socket is connected (LiveSocket).
|
|
"""
|
|
@spec setup(Socket.t(), map()) :: Socket.t()
|
|
def setup(socket, initial_bounds) do
|
|
do_setup(socket, initial_bounds, connected?(socket))
|
|
end
|
|
|
|
@doc """
|
|
Clean up all subscriptions and timers when the LiveView terminates.
|
|
"""
|
|
@spec teardown(Socket.t()) :: :ok
|
|
def teardown(socket) do
|
|
_teardown_connection_monitor(socket)
|
|
_teardown_spatial(socket)
|
|
_ = _teardown_all_timers(socket)
|
|
_teardown_batch_tasks(socket)
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Update the spatial viewport bounds for an existing subscription.
|
|
"""
|
|
@spec update_viewport(Socket.t(), map()) :: :ok | nil
|
|
def update_viewport(socket, map_bounds) do
|
|
if socket.assigns[:spatial_client_id] do
|
|
Aprsme.SpatialPubSub.update_viewport(socket.assigns.spatial_client_id, map_bounds)
|
|
end
|
|
end
|
|
|
|
# --- Private helpers ---
|
|
|
|
defp do_setup(socket, _initial_bounds, false), do: socket
|
|
|
|
defp do_setup(socket, initial_bounds, true) do
|
|
# Check if we should accept new connections
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) and
|
|
not Aprsme.ConnectionMonitor.accepting_connections?() do
|
|
socket
|
|
|> put_flash(:info, "This server is currently at capacity. Please try again in a moment.")
|
|
|> push_event("redirect_to_least_loaded", %{})
|
|
|> assign(:connection_draining, true)
|
|
else
|
|
# Generate a unique client ID for this LiveView instance
|
|
client_id = "liveview_#{:erlang.phash2(self())}"
|
|
|
|
# Register with connection monitor
|
|
_ =
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) do
|
|
_ = Aprsme.ConnectionMonitor.register_connection()
|
|
# Subscribe to drain events for this node
|
|
Phoenix.PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")
|
|
end
|
|
|
|
# Register the actual initial viewport.
|
|
{:ok, spatial_topic} = Aprsme.SpatialPubSub.register_viewport(client_id, initial_bounds)
|
|
|
|
# Subscribe to the spatial topic for this client
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, spatial_topic)
|
|
|
|
# Still subscribe to bad packets (they don't have location)
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "bad_packets")
|
|
|
|
# Subscribe to deployment events
|
|
:ok = Phoenix.PubSub.subscribe(Aprsme.PubSub, "deployment_events")
|
|
|
|
_ = Process.send_after(self(), :cleanup_old_packets, 60_000)
|
|
|
|
socket
|
|
|> assign(:spatial_client_id, client_id)
|
|
|> assign(:connection_draining, false)
|
|
end
|
|
end
|
|
|
|
defp _teardown_connection_monitor(socket) do
|
|
_ =
|
|
if Application.get_env(:aprsme, :cluster_enabled, false) and
|
|
not socket.assigns[:connection_draining] do
|
|
Aprsme.ConnectionMonitor.unregister_connection()
|
|
end
|
|
end
|
|
|
|
defp _teardown_spatial(socket) do
|
|
_ =
|
|
if socket.assigns[:spatial_client_id] do
|
|
Aprsme.SpatialPubSub.unregister_client(socket.assigns.spatial_client_id)
|
|
end
|
|
end
|
|
|
|
defp _teardown_all_timers(socket) do
|
|
_ = cancel_if_exists(socket.assigns[:buffer_timer])
|
|
_ = cancel_if_exists(socket.assigns[:bounds_update_timer])
|
|
_ = cancel_if_exists(socket.assigns[:hover_end_timer])
|
|
end
|
|
|
|
defp cancel_if_exists(nil), do: :ok
|
|
defp cancel_if_exists(timer_ref), do: Process.cancel_timer(timer_ref)
|
|
|
|
defp _teardown_batch_tasks(socket) do
|
|
_ =
|
|
if socket.assigns[:pending_batch_tasks] do
|
|
Enum.each(socket.assigns.pending_batch_tasks, &Process.cancel_timer/1)
|
|
end
|
|
end
|
|
end
|