Files
wanderer/lib/wanderer_app/zkb/zkills_provider/cache.ex
guarzo 55465688c8 Add hover tooltips for local counter and kills bookmark (#130)
* feat: add local pilots and kills display on hover
2025-02-04 21:19:13 +04:00

165 lines
4.9 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 its 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