All checks were successful
Build and Deploy / Build & Push Image (push) Successful in 32s
107 lines
3.2 KiB
Elixir
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
|