mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-07 08:15:35 +00:00
165 lines
4.9 KiB
Elixir
165 lines
4.9 KiB
Elixir
defmodule WandererApp.Zkb.KillsProvider.KillsCache do
|
||
@moduledoc """
|
||
Provides helper functions for putting/fetching kill data
|
||
"""
|
||
|
||
require Logger
|
||
alias WandererApp.Cache
|
||
|
||
@killmail_ttl :timer.hours(24)
|
||
@system_kills_ttl :timer.hours(1)
|
||
|
||
# Base (average) expiry of 15 minutes for "recently fetched" systems
|
||
@base_full_fetch_expiry_ms 900_000
|
||
@jitter_percent 0.1
|
||
|
||
def killmail_ttl, do: @killmail_ttl
|
||
def system_kills_ttl, do: @system_kills_ttl
|
||
|
||
@doc """
|
||
Store the killmail data, keyed by killmail_id, with a 24h TTL.
|
||
"""
|
||
def put_killmail(killmail_id, kill_data) do
|
||
Logger.debug(fn -> "[KillsCache] Storing killmail => killmail_id=#{killmail_id}" end)
|
||
Cache.put(killmail_key(killmail_id), kill_data, ttl: @killmail_ttl)
|
||
end
|
||
|
||
@doc """
|
||
Fetch kills for `system_id` from the local cache only.
|
||
Returns a list of killmail maps (could be empty).
|
||
"""
|
||
def fetch_cached_kills(system_id) do
|
||
killmail_ids = get_system_killmail_ids(system_id)
|
||
Logger.debug(fn -> "[KillsCache] fetch_cached_kills => system_id=#{system_id}, count=#{length(killmail_ids)}" end)
|
||
|
||
killmail_ids
|
||
|> Enum.map(&get_killmail/1)
|
||
|> Enum.reject(&is_nil/1)
|
||
end
|
||
|
||
@doc """
|
||
Fetch cached kills for multiple solar system IDs.
|
||
Returns a map of `%{ solar_system_id => list_of_kills }`.
|
||
"""
|
||
def fetch_cached_kills_for_systems(system_ids) when is_list(system_ids) do
|
||
Enum.reduce(system_ids, %{}, fn sid, acc ->
|
||
kills_list = fetch_cached_kills(sid)
|
||
Map.put(acc, sid, kills_list)
|
||
end)
|
||
end
|
||
|
||
@doc """
|
||
Fetch the killmail data (if any) from the cache, by killmail_id.
|
||
"""
|
||
def get_killmail(killmail_id) do
|
||
Cache.get(killmail_key(killmail_id))
|
||
end
|
||
|
||
@doc """
|
||
Adds `killmail_id` to the list of killmail IDs for the system
|
||
if it’s not already present. The TTL is 24 hours.
|
||
"""
|
||
def add_killmail_id_to_system_list(solar_system_id, killmail_id) do
|
||
Cache.update(
|
||
system_kills_list_key(solar_system_id),
|
||
[],
|
||
fn existing_list ->
|
||
existing_list = existing_list || []
|
||
if killmail_id in existing_list do
|
||
existing_list
|
||
else
|
||
existing_list ++ [killmail_id]
|
||
end
|
||
end,
|
||
ttl: @killmail_ttl
|
||
)
|
||
end
|
||
|
||
@doc """
|
||
Returns a list of killmail IDs for the given system, or [] if none.
|
||
"""
|
||
def get_system_killmail_ids(solar_system_id) do
|
||
Cache.get(system_kills_list_key(solar_system_id)) || []
|
||
end
|
||
|
||
@doc """
|
||
Increments the kill count for a system by `amount`. The TTL is 1 hour.
|
||
"""
|
||
def incr_system_kill_count(solar_system_id, amount \\ 1) do
|
||
Cache.incr(
|
||
system_kills_key(solar_system_id),
|
||
amount,
|
||
default: 0,
|
||
ttl: @system_kills_ttl
|
||
)
|
||
end
|
||
|
||
@doc """
|
||
Returns the integer count of kills for this system in the last hour, or 0.
|
||
"""
|
||
def get_system_kill_count(solar_system_id) do
|
||
Cache.get(system_kills_key(solar_system_id)) || 0
|
||
end
|
||
|
||
@doc """
|
||
Check if the system is still in its "recently fetched" window.
|
||
We store an `expires_at` timestamp (in ms). If `now < expires_at`,
|
||
this system is still considered "recently fetched".
|
||
"""
|
||
def recently_fetched?(system_id) do
|
||
case Cache.lookup(fetched_timestamp_key(system_id)) do
|
||
{:ok, expires_at_ms} when is_integer(expires_at_ms) ->
|
||
now_ms = current_time_ms()
|
||
now_ms < expires_at_ms
|
||
|
||
_ ->
|
||
false
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Puts a jittered `expires_at` in the cache for `system_id`,
|
||
marking it as fully fetched for ~15 minutes (+/- 10%).
|
||
"""
|
||
def put_full_fetched_timestamp(system_id) do
|
||
now_ms = current_time_ms()
|
||
max_jitter = round(@base_full_fetch_expiry_ms * @jitter_percent)
|
||
# random offset in range [-max_jitter..+max_jitter]
|
||
offset = :rand.uniform(2 * max_jitter + 1) - (max_jitter + 1)
|
||
final_expiry_ms = max(@base_full_fetch_expiry_ms + offset, 60_000)
|
||
expires_at_ms = now_ms + final_expiry_ms
|
||
|
||
Logger.debug(fn -> "[KillsCache] Marking system=#{system_id} recently_fetched? until #{expires_at_ms} (ms)" end)
|
||
Cache.put(fetched_timestamp_key(system_id), expires_at_ms)
|
||
end
|
||
|
||
@doc """
|
||
Returns how many ms remain until this system's "recently fetched" window ends.
|
||
If it's already expired (or doesn't exist), returns -1.
|
||
"""
|
||
def fetch_age_ms(system_id) do
|
||
now_ms = current_time_ms()
|
||
|
||
case Cache.lookup(fetched_timestamp_key(system_id)) do
|
||
{:ok, expires_at_ms} when is_integer(expires_at_ms) ->
|
||
if now_ms < expires_at_ms do
|
||
expires_at_ms - now_ms
|
||
else
|
||
-1
|
||
end
|
||
|
||
_ ->
|
||
-1
|
||
end
|
||
end
|
||
|
||
defp killmail_key(killmail_id), do: "zkb_killmail_#{killmail_id}"
|
||
defp system_kills_key(solar_system_id), do: "zkb_kills_#{solar_system_id}"
|
||
defp system_kills_list_key(solar_system_id), do: "zkb_kills_list_#{solar_system_id}"
|
||
defp fetched_timestamp_key(system_id), do: "zkb_system_fetched_at_#{system_id}"
|
||
|
||
defp current_time_ms() do
|
||
DateTime.utc_now() |> DateTime.to_unix(:millisecond)
|
||
end
|
||
end
|