aprs.me/lib/aprsme_web/live/shared/packet_handler.ex

141 lines
4.1 KiB
Elixir

defmodule AprsmeWeb.Live.SharedPacketHandler do
@moduledoc """
Shared functionality for handling packet updates in LiveView modules.
Provides common patterns for packet filtering, enrichment, and processing.
"""
alias Aprsme.Callsign
alias Aprsme.DeviceCache
alias Aprsme.EncodingUtils
@doc """
Handles incoming packet updates with filtering and processing.
## Options
* `:filter_fn` - Function to determine if packet should be processed (required)
* `:process_fn` - Function to process matching packets and update socket (required)
* `:enrich_packet` - Whether to enrich packet with device info (default: true)
"""
def handle_packet_update(packet, socket, opts) do
filter_fn = Keyword.fetch!(opts, :filter_fn)
process_fn = Keyword.fetch!(opts, :process_fn)
enrich_packet? = Keyword.get(opts, :enrich_packet, true)
if filter_fn.(packet, socket) do
enriched_packet =
if enrich_packet? do
packet
|> sanitize_packet()
|> enrich_with_device_info()
else
packet
end
process_fn.(enriched_packet, socket)
else
{:noreply, socket}
end
end
@doc """
Checks if packet sender matches the given callsign.
"""
def packet_matches_callsign?(packet, callsign) do
packet_sender = Map.get(packet, "sender") || Map.get(packet, :sender, "")
Callsign.matches?(packet_sender, callsign)
end
@doc """
Checks if packet contains weather data.
"""
def has_weather_data?(packet) do
Enum.any?(EncodingUtils.weather_fields(), fn field ->
value = Map.get(packet, field) || Map.get(packet, to_string(field))
not is_nil(value)
end)
end
@doc """
Sanitizes packet strings to handle encoding issues.
"""
def sanitize_packet(packet) do
EncodingUtils.sanitize_packet(packet)
end
@doc """
Enriches packet with device information.
"""
def enrich_with_device_info(packet) do
device_identifier = get_device_identifier(packet)
device = lookup_device_info(device_identifier)
packet
|> Map.put(:device_model, device && device.model)
|> Map.put(:device_vendor, device && device.vendor)
|> Map.put(:device_contact, device && device.contact)
|> Map.put(:device_class, device && device.class)
end
@doc """
Batch enrich multiple packets with device info for better performance.
"""
def enrich_packets_with_device_info(packets) when is_list(packets) do
# Get unique device identifiers from all packets
device_identifiers =
packets
|> Enum.map(&get_device_identifier/1)
|> Enum.reject(&nil_or_empty?/1)
|> Enum.uniq()
# Batch lookup devices (DeviceCache is already optimized with caching)
device_map =
Map.new(device_identifiers, fn identifier -> {identifier, DeviceCache.lookup_device(identifier)} end)
# Enrich packets using the cached device map
Enum.map(packets, fn packet ->
device_identifier = get_device_identifier(packet)
device = Map.get(device_map, device_identifier)
packet
|> Map.put(:device_model, device && device.model)
|> Map.put(:device_vendor, device && device.vendor)
|> Map.put(:device_contact, device && device.contact)
|> Map.put(:device_class, device && device.class)
end)
end
defp get_device_identifier(packet) do
Map.get(packet, :device_identifier) || Map.get(packet, "device_identifier")
end
defp lookup_device_info(device_identifier) do
case device_identifier do
nil -> nil
"" -> nil
identifier -> DeviceCache.lookup_device(identifier)
end
end
defp nil_or_empty?(nil), do: true
defp nil_or_empty?(""), do: true
defp nil_or_empty?(_), do: false
@doc """
Creates a filter function that checks both callsign and weather data.
"""
def callsign_and_weather_filter(callsign) do
fn packet, _socket ->
packet_matches_callsign?(packet, callsign) and has_weather_data?(packet)
end
end
@doc """
Creates a filter function that only checks callsign.
"""
def callsign_filter(callsign) do
fn packet, _socket ->
packet_matches_callsign?(packet, callsign)
end
end
end