microprints-phoenix/lib/microprint_cache.ex

104 lines
2.7 KiB
Elixir

defmodule Microprints.MicroprintCache do
@moduledoc """
GenServer that caches microprint visualizations in ETS.
Subscribes to a PubSub topic and invalidates cache entries when files
change via LiveReload (or any other file watcher).
## Configuration
The PubSub module and topic can be configured via `Application` environment:
config :microprints,
pubsub: MyApp.PubSub,
pubsub_topic: "dev_tools_files"
Defaults to `Phoenix.PubSub` and `"dev_tools_files"`.
"""
use GenServer
alias Microprints.Microprint
@table_name :microprint_cache
@live_reload_topic "dev_tools_files"
# Client API
@doc """
Starts the MicroprintCache GenServer.
## Options
* `:pubsub` - The PubSub module to use (defaults to `Phoenix.PubSub`)
* `:pubsub_topic` - The PubSub topic to subscribe to (defaults to `"dev_tools_files"`)
"""
def start_link(opts \\ []) do
pubsub = opts[:pubsub] || Application.get_env(:microprints, :pubsub, Phoenix.PubSub)
pubsub_topic = opts[:pubsub_topic] || Application.get_env(:microprints, :pubsub_topic, @live_reload_topic)
GenServer.start_link(__MODULE__, %{pubsub: pubsub, pubsub_topic: pubsub_topic}, name: __MODULE__)
end
@doc """
Returns the microprint for the given file path.
Returns cached version if available, otherwise generates and caches it.
"""
@spec get_microprint(String.t()) :: {:ok, Microprint.microprint()} | {:error, term()}
def get_microprint(path) do
absolute_path = Path.expand(path)
case :ets.lookup(@table_name, absolute_path) do
[{^absolute_path, microprint}] ->
{:ok, microprint}
[] ->
case Microprint.generate(absolute_path) do
{:ok, microprint} ->
:ets.insert(@table_name, {absolute_path, microprint})
{:ok, microprint}
error ->
error
end
end
end
@doc """
Invalidates the cache entry for the given path.
"""
@spec invalidate(String.t()) :: true
def invalidate(path) do
absolute_path = Path.expand(path)
:ets.delete(@table_name, absolute_path)
end
@doc """
Clears all cached microprints.
"""
@spec clear_all() :: true
def clear_all do
:ets.delete_all_objects(@table_name)
end
# Server callbacks
@impl true
def init(%{pubsub: pubsub, pubsub_topic: pubsub_topic}) do
table = :ets.new(@table_name, [:named_table, :public, :set, read_concurrency: true])
Phoenix.PubSub.subscribe(pubsub, pubsub_topic)
{:ok, %{table: table, pubsub_topic: pubsub_topic}}
end
@impl true
def handle_info({:phoenix_live_reload, _topic, path}, state) do
invalidate(path)
{:noreply, state}
end
def handle_info(_msg, state) do
{:noreply, state}
end
end