Files
cobblemon-ui/lib/cobblemon_ui/sprite_cache.ex
Alex Mickelson 1b476ad816
All checks were successful
Build and Deploy / Build & Push Image (push) Successful in 32s
updates
2026-03-25 21:32:10 -06:00

107 lines
3.2 KiB
Elixir

defmodule CobblemonUi.SpriteCache do
@moduledoc """
Downloads and caches Pokémon sprite images locally.
Sprites are stored under `$CACHE_DIR/sprites/<species>.png`.
Use `ensure_sprite/1` to lazily download a sprite on first access,
or `sprite_path/1` to get the expected local filesystem path.
"""
use GenServer
require Logger
@source_url "https://img.rankedboost.com/wp-content/plugins/k-Pokemon/assets/sprites-official"
# ---------------------------------------------------------------------------
# Client API
# ---------------------------------------------------------------------------
def start_link(_opts) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc """
Ensures the sprite for the given species is downloaded locally.
Returns `:ok` if the file exists (or was just fetched), or `{:error, reason}`.
"""
@spec ensure_sprite(String.t()) :: :ok | {:error, term()}
def ensure_sprite(species) when is_binary(species) do
key = String.downcase(species)
path = sprite_path(key)
if File.exists?(path) do
:ok
else
GenServer.call(__MODULE__, {:download, key}, 30_000)
end
end
@doc """
Returns the local URL path for a species sprite (for use in templates).
Always returns the path even if the file hasn't been downloaded yet.
"""
@spec sprite_url(String.t()) :: String.t()
def sprite_url(species) do
"/sprites/#{String.downcase(species)}.png"
end
@doc "Returns the filesystem path where a sprite would be stored."
@spec sprite_path(String.t()) :: String.t()
def sprite_path(species) do
Path.join(sprites_dir(), "#{String.downcase(species)}.png")
end
@doc "Returns the base sprites directory."
@spec sprites_dir() :: String.t()
def sprites_dir do
dir = System.get_env("CACHE_DIR", ".")
Path.join(dir, "sprites")
end
# ---------------------------------------------------------------------------
# Server callbacks
# ---------------------------------------------------------------------------
@impl true
def init(_opts) do
File.mkdir_p!(sprites_dir())
{:ok, MapSet.new()}
end
@impl true
def handle_call({:download, species}, _from, in_progress) do
path = sprite_path(species)
if File.exists?(path) do
{:reply, :ok, in_progress}
else
result = download_sprite(species, path)
{:reply, result, in_progress}
end
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp download_sprite(species, dest) do
url = "#{@source_url}/#{species}.png"
case Req.get(url, decode_body: false) do
{:ok, %Req.Response{status: 200, body: body}} when is_binary(body) ->
File.mkdir_p!(Path.dirname(dest))
File.write!(dest, body)
Logger.debug("[SpriteCache] Downloaded sprite for #{species}")
:ok
{:ok, %Req.Response{status: status}} ->
Logger.warning("[SpriteCache] Failed to download #{species}: HTTP #{status}")
{:error, {:http_error, status}}
{:error, reason} ->
Logger.warning("[SpriteCache] Failed to download #{species}: #{inspect(reason)}")
{:error, reason}
end
end
end