- Move require Logger to module top level in SpatialPubSub - Replace weak is_list assertions with stronger checks - Remove redundant 'is a list' test in PromEx tests - Replace apply/3 with direct function call in packet_replay test - Fix unmatched_return in weather_cache.ex and callsign_view.ex
90 lines
2.1 KiB
Elixir
90 lines
2.1 KiB
Elixir
defmodule Aprsme.WeatherCache do
|
|
@moduledoc """
|
|
Persistent ETS-based cache for weather callsign lookups.
|
|
|
|
Eliminates N+1 queries for `has_weather_packets?` checks during map
|
|
marker rendering. Entries expire after `@ttl_ms` to stay reasonably
|
|
current without per-callsign DB queries.
|
|
"""
|
|
@cache_name :weather_cache
|
|
@ttl_ms to_timeout(minute: 5)
|
|
|
|
@doc """
|
|
Create the ETS table if it doesn't exist yet.
|
|
The table is typically created in application.ex alongside other ETS tables.
|
|
"""
|
|
def setup do
|
|
_table =
|
|
if :ets.whereis(@cache_name) == :undefined do
|
|
:ets.new(@cache_name, [
|
|
:named_table,
|
|
:public,
|
|
:set,
|
|
read_concurrency: true,
|
|
write_concurrency: true
|
|
])
|
|
end
|
|
|
|
:ok
|
|
rescue
|
|
ArgumentError -> :ok
|
|
end
|
|
|
|
@doc """
|
|
Batch-populate the cache for the given list of callsigns.
|
|
"""
|
|
def cache_weather_callsigns(callsigns) when is_list(callsigns) do
|
|
weather_set = Aprsme.Packets.weather_callsigns(callsigns)
|
|
expires_at = System.monotonic_time(:millisecond) + @ttl_ms
|
|
|
|
Enum.each(callsigns, fn cs ->
|
|
key = String.upcase(String.trim(cs))
|
|
has_weather = MapSet.member?(weather_set, key)
|
|
:ets.insert(@cache_name, {key, has_weather, expires_at})
|
|
end)
|
|
|
|
:ok
|
|
rescue
|
|
_ -> :ok
|
|
end
|
|
|
|
@doc """
|
|
Check if a callsign has weather packets.
|
|
Returns `:unknown` if not cached, `true`/`false` if cached.
|
|
"""
|
|
def weather_callsign?(callsign) when is_binary(callsign) do
|
|
key = String.upcase(String.trim(callsign))
|
|
|
|
case :ets.lookup(@cache_name, key) do
|
|
[{^key, value, expires_at}] ->
|
|
if System.monotonic_time(:millisecond) < expires_at do
|
|
value
|
|
else
|
|
:ets.delete(@cache_name, key)
|
|
:unknown
|
|
end
|
|
|
|
_ ->
|
|
:unknown
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Evict expired entries.
|
|
"""
|
|
def evict_expired do
|
|
now = System.monotonic_time(:millisecond)
|
|
|
|
@cache_name
|
|
|> :ets.tab2list()
|
|
|> Enum.each(fn
|
|
{key, _value, expires_at} when expires_at < now ->
|
|
:ets.delete(@cache_name, key)
|
|
|
|
_ ->
|
|
:ok
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
end
|