defmodule Image.Plug.VariantStore.ETS do @moduledoc """ In-memory ETS-backed implementation of `Image.Plug.VariantStore`. A singleton owner process holds a `:protected` ETS table. Reads go straight to ETS; writes go through the owner process to serialise inserts and to populate the `:inserted_at` / `:updated_at` timestamps. ### Configuration Started by the `:image_plug` application with a default table name of `:image_plug_variants`. Variants are seeded from the application environment: # config/config.exs config :image_plug, variants: [ {"thumbnail", "width=200,height=200,fit=cover,format=webp"}, {"hero", "width=1600,format=auto,quality=82"} ] Each entry value can be: * a Cloudflare-style options string (parsed by `Image.Plug.Provider.Cloudflare.Options`), * a `{provider, options_string, variant_options}` triple, * a pre-built `Image.Plug.Pipeline`, or * a complete `Image.Plug.Variant` struct. The implicit `"public"` variant is always seeded and represents Cloudflare's default "no transforms" behaviour. It can be overridden by adding an explicit `"public"` entry to the seeds. ### Per-call options Every callback accepts a `:table` keyword to address a non-default table — useful for tests and for hosts that run multiple isolated stores. """ @behaviour Image.Plug.VariantStore alias Image.Plug.{Variant, VariantStore} @impl VariantStore def get(name, options \\ []) when is_binary(name) do table = Keyword.get(options, :table, default_table()) case :ets.lookup(table, name) do [{^name, %Variant{} = variant}] -> {:ok, variant} [] -> {:error, :not_found} end end @impl VariantStore def put(%Variant{} = variant, options \\ []) do server = Keyword.get(options, :server, default_server()) GenServer.call(server, {:put, variant}) end @impl VariantStore def delete(name, options \\ []) when is_binary(name) do server = Keyword.get(options, :server, default_server()) GenServer.call(server, {:delete, name}) end @impl VariantStore def list(options \\ []) do table = Keyword.get(options, :table, default_table()) variants = table |> :ets.tab2list() |> Enum.map(fn {_name, variant} -> variant end) |> Enum.sort_by(& &1.name) {:ok, variants} end @doc false def default_table, do: Image.Plug.VariantStore.ETS.Server.default_table() @doc false def default_server, do: Image.Plug.VariantStore.ETS.Server end