dont parse device identifiers on each insert
This commit is contained in:
parent
38f240b3eb
commit
b1ee1f92f9
6 changed files with 153 additions and 49 deletions
|
|
@ -1319,25 +1319,9 @@ let MapAPRSMap = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// For historical packets that are not the most recent for their callsign,
|
// For historical packets that are not the most recent for their callsign,
|
||||||
// still show the proper APRS symbol but with reduced opacity
|
// show a simple dot instead of the full APRS symbol
|
||||||
if (data.historical && !data.is_most_recent_for_callsign) {
|
if (data.historical && !data.is_most_recent_for_callsign) {
|
||||||
// Use server-generated symbol HTML if available
|
// Always show a red dot for historical positions
|
||||||
if (data.symbol_html) {
|
|
||||||
// Add opacity to the symbol HTML for historical markers
|
|
||||||
const historicalHtml = data.symbol_html.replace(
|
|
||||||
/style="([^"]*)"/,
|
|
||||||
'style="$1 opacity: 0.7;"'
|
|
||||||
);
|
|
||||||
|
|
||||||
return L.divIcon({
|
|
||||||
html: historicalHtml,
|
|
||||||
className: "historical-aprs-marker",
|
|
||||||
iconSize: [120, 32],
|
|
||||||
iconAnchor: [16, 16],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: red dot for historical positions without symbol data
|
|
||||||
const iconHtml = `<div style="
|
const iconHtml = `<div style="
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
|
|
@ -1346,7 +1330,7 @@ let MapAPRSMap = {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
box-shadow: 0 0 2px rgba(0,0,0,0.3);
|
box-shadow: 0 0 2px rgba(0,0,0,0.3);
|
||||||
" title="Historical position for ${data.callsign} (position changed)"></div>`;
|
" title="Historical position for ${data.callsign}"></div>`;
|
||||||
|
|
||||||
return L.divIcon({
|
return L.divIcon({
|
||||||
html: iconHtml,
|
html: iconHtml,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ defmodule Aprsme.Application do
|
||||||
%{id: :symbol_cache, start: {Cachex, :start_link, [:symbol_cache, [limit: 1_000]]}},
|
%{id: :symbol_cache, start: {Cachex, :start_link, [:symbol_cache, [limit: 1_000]]}},
|
||||||
# Start circuit breaker
|
# Start circuit breaker
|
||||||
Aprsme.CircuitBreaker,
|
Aprsme.CircuitBreaker,
|
||||||
|
# Start device cache manager
|
||||||
|
Aprsme.DeviceCache,
|
||||||
|
|
||||||
# Start the Endpoint (http/https)
|
# Start the Endpoint (http/https)
|
||||||
AprsmeWeb.Endpoint,
|
AprsmeWeb.Endpoint,
|
||||||
|
|
|
||||||
130
lib/aprsme/device_cache.ex
Normal file
130
lib/aprsme/device_cache.ex
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
defmodule Aprsme.DeviceCache do
|
||||||
|
@moduledoc """
|
||||||
|
Caches device information and provides efficient lookup by identifier with wildcard matching.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
alias Aprsme.Devices
|
||||||
|
alias Aprsme.Repo
|
||||||
|
|
||||||
|
@cache_name :device_cache
|
||||||
|
@refresh_interval to_timeout(day: 1)
|
||||||
|
|
||||||
|
# Client API
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Starts the DeviceCache GenServer.
|
||||||
|
"""
|
||||||
|
def start_link(_opts) do
|
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Looks up a device by identifier using wildcard matching.
|
||||||
|
Returns the device struct if found, or nil.
|
||||||
|
"""
|
||||||
|
def lookup_device(nil), do: nil
|
||||||
|
|
||||||
|
def lookup_device(identifier) when is_binary(identifier) do
|
||||||
|
case Cachex.get(@cache_name, :all_devices) do
|
||||||
|
{:ok, nil} ->
|
||||||
|
# Cache miss - load devices
|
||||||
|
GenServer.call(__MODULE__, :refresh_cache)
|
||||||
|
lookup_device(identifier)
|
||||||
|
|
||||||
|
{:ok, devices} when is_list(devices) ->
|
||||||
|
find_matching_device(devices, identifier)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Refreshes the device cache from the database.
|
||||||
|
"""
|
||||||
|
def refresh_cache do
|
||||||
|
GenServer.call(__MODULE__, :refresh_cache)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Server callbacks
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_) do
|
||||||
|
# Load devices on startup
|
||||||
|
load_devices_into_cache()
|
||||||
|
|
||||||
|
# Schedule periodic refresh
|
||||||
|
Process.send_after(self(), :refresh_cache, @refresh_interval)
|
||||||
|
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call(:refresh_cache, _from, state) do
|
||||||
|
result = load_devices_into_cache()
|
||||||
|
{:reply, result, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:refresh_cache, state) do
|
||||||
|
load_devices_into_cache()
|
||||||
|
|
||||||
|
# Schedule next refresh
|
||||||
|
Process.send_after(self(), :refresh_cache, @refresh_interval)
|
||||||
|
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Private functions
|
||||||
|
|
||||||
|
defp load_devices_into_cache do
|
||||||
|
devices = Repo.all(Devices)
|
||||||
|
|
||||||
|
# Store all devices in cache
|
||||||
|
case Cachex.put(@cache_name, :all_devices, devices) do
|
||||||
|
{:ok, true} -> :ok
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp find_matching_device(devices, identifier) do
|
||||||
|
Enum.find(devices, fn device ->
|
||||||
|
pattern = device.identifier
|
||||||
|
|
||||||
|
cond do
|
||||||
|
String.contains?(pattern, "?") ->
|
||||||
|
try do
|
||||||
|
regex = wildcard_pattern_to_regex(pattern)
|
||||||
|
Regex.match?(regex, identifier)
|
||||||
|
rescue
|
||||||
|
_e in Regex.CompileError ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
String.contains?(pattern, "*") ->
|
||||||
|
# Compare literally if pattern contains * but not ?
|
||||||
|
pattern == identifier
|
||||||
|
|
||||||
|
true ->
|
||||||
|
pattern == identifier
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Converts a pattern with ? wildcards to a regex
|
||||||
|
defp wildcard_pattern_to_regex(pattern) when is_binary(pattern) do
|
||||||
|
# Replace ? with a placeholder, escape all regex metacharacters except the placeholder,
|
||||||
|
# then replace placeholder with .
|
||||||
|
pattern
|
||||||
|
|> String.replace("?", "__WILDCARD__")
|
||||||
|
# Escape all regex metacharacters
|
||||||
|
|> String.replace(~r/([\\.\+\*\?\[\^\]\$\(\)\{\}=!<>\|:\-])/, "\\\\\\1")
|
||||||
|
|> String.replace("__WILDCARD__", ".")
|
||||||
|
|> then(fn s ->
|
||||||
|
regex = "^" <> s <> "$"
|
||||||
|
~r/#{regex}/
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -209,9 +209,7 @@ defmodule Aprsme.PacketConsumer do
|
||||||
|> normalize_ssid()
|
|> normalize_ssid()
|
||||||
|> then(fn attrs ->
|
|> then(fn attrs ->
|
||||||
device_identifier = Aprsme.DeviceParser.extract_device_identifier(packet_data)
|
device_identifier = Aprsme.DeviceParser.extract_device_identifier(packet_data)
|
||||||
matched_device = Aprsme.DeviceIdentification.lookup_device_by_identifier(device_identifier)
|
Map.put(attrs, :device_identifier, device_identifier)
|
||||||
canonical_identifier = if matched_device, do: matched_device.identifier, else: device_identifier
|
|
||||||
Map.put(attrs, :device_identifier, canonical_identifier)
|
|
||||||
end)
|
end)
|
||||||
|> sanitize_packet_strings()
|
|> sanitize_packet_strings()
|
||||||
|> Map.put(:inserted_at, current_time)
|
|> Map.put(:inserted_at, current_time)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ defmodule AprsmeWeb.Api.V1.CallsignJSON do
|
||||||
Renders callsign and packet data for API v1.
|
Renders callsign and packet data for API v1.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
alias Aprsme.DeviceCache
|
||||||
alias Aprsme.Packet
|
alias Aprsme.Packet
|
||||||
|
|
||||||
def render("show.json", %{packet: packet}) do
|
def render("show.json", %{packet: packet}) do
|
||||||
|
|
@ -97,10 +98,23 @@ defmodule AprsmeWeb.Api.V1.CallsignJSON do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp equipment_json(%Packet{} = packet) do
|
defp equipment_json(%Packet{} = packet) do
|
||||||
|
# Look up device info based on device_identifier
|
||||||
|
device =
|
||||||
|
case packet.device_identifier do
|
||||||
|
nil -> nil
|
||||||
|
"" -> nil
|
||||||
|
identifier -> DeviceCache.lookup_device(identifier)
|
||||||
|
end
|
||||||
|
|
||||||
equipment_data =
|
equipment_data =
|
||||||
%{}
|
%{}
|
||||||
|
|> maybe_add(:device_identifier, packet.device_identifier)
|
||||||
|> maybe_add(:manufacturer, packet.manufacturer)
|
|> maybe_add(:manufacturer, packet.manufacturer)
|
||||||
|> maybe_add(:equipment_type, packet.equipment_type)
|
|> maybe_add(:equipment_type, packet.equipment_type)
|
||||||
|
|> maybe_add(:device_model, device && device.model)
|
||||||
|
|> maybe_add(:device_vendor, device && device.vendor)
|
||||||
|
|> maybe_add(:device_contact, device && device.contact)
|
||||||
|
|> maybe_add(:device_class, device && device.class)
|
||||||
|
|
||||||
case equipment_data do
|
case equipment_data do
|
||||||
empty when empty == %{} -> nil
|
empty when empty == %{} -> nil
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Aprsme.Callsign
|
alias Aprsme.Callsign
|
||||||
alias Aprsme.DeviceIdentification
|
alias Aprsme.DeviceCache
|
||||||
alias Aprsme.DeviceParser
|
|
||||||
alias Aprsme.EncodingUtils
|
alias Aprsme.EncodingUtils
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -67,13 +66,13 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
|
||||||
Enriches packet with device information.
|
Enriches packet with device information.
|
||||||
"""
|
"""
|
||||||
def enrich_with_device_info(packet) do
|
def enrich_with_device_info(packet) do
|
||||||
device_identifier = extract_device_identifier(packet)
|
device_identifier = Map.get(packet, :device_identifier) || Map.get(packet, "device_identifier")
|
||||||
|
|
||||||
device =
|
device =
|
||||||
case device_identifier do
|
case device_identifier do
|
||||||
nil -> nil
|
nil -> nil
|
||||||
"" -> nil
|
"" -> nil
|
||||||
identifier -> DeviceIdentification.lookup_device_by_identifier(identifier)
|
identifier -> DeviceCache.lookup_device(identifier)
|
||||||
end
|
end
|
||||||
|
|
||||||
packet
|
packet
|
||||||
|
|
@ -100,27 +99,4 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
|
||||||
packet_matches_callsign?(packet, callsign)
|
packet_matches_callsign?(packet, callsign)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Private functions
|
|
||||||
|
|
||||||
defp extract_device_identifier(packet) do
|
|
||||||
comment = Map.get(packet, :comment) || Map.get(packet, "comment") || ""
|
|
||||||
|
|
||||||
normalized_comment =
|
|
||||||
case comment do
|
|
||||||
comment when is_binary(comment) ->
|
|
||||||
comment
|
|
||||||
|> String.trim()
|
|
||||||
|> String.replace(~r/[^\x20-\x7E]+/, "")
|
|
||||||
|> String.trim()
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
""
|
|
||||||
end
|
|
||||||
|
|
||||||
case DeviceParser.extract_device_identifier(%{comment: normalized_comment}) do
|
|
||||||
nil -> nil
|
|
||||||
identifier -> String.trim(identifier)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue