defmodule WeChat.Registry do @moduledoc false use Decorator.Define, cache: 0 use GenServer alias WeChat.Utils def start_link([]) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end @impl true def init(_) do ets = :ets.new(__MODULE__, [:public, :named_table, read_concurrency: true]) {:ok, ets} end def cache(body, context) do quote do import WeChat.Registry, only: [read_from_local: 2, write_to_local: 2] case read_from_local(unquote(context.name), unquote(context.args)) do {key, nil} -> value = unquote(body) write_to_local(key, value) value value -> # read from local registry {:ok, value} end end end def read_from_local(:refresh_access_token, [appid, _access_token, _hub_url]) do # always clean registry when refresh key = key_access_token([appid]) delete(key) {key, nil} end def read_from_local(:refresh_access_token, [appid, authorizer_appid, _access_token, _hub_url]) do # always clean registry when refresh key = key_access_token([appid, authorizer_appid]) delete(key) {key, nil} end def read_from_local(:fetch_access_token, [appid, authorizer_appid, _hub_url]) do [appid, authorizer_appid] |> key_access_token() |> lookup() |> use_cache_if_not_expired() end def read_from_local(:fetch_access_token, [appid, _hub_url]) do [appid] |> key_access_token() |> lookup() |> use_cache_if_not_expired() end def read_from_local(:fetch_component_access_token, [appid, _hub_url]) do [appid] |> key_component_access_token() |> lookup() |> use_cache_if_not_expired() end def read_from_local(:refresh_component_access_token, [appid, _access_token, _hub_url]) do key = key_component_access_token([appid]) delete(key) {key, nil} end def read_from_local(:fetch_ticket, [appid, type, _hub_url]) do [appid, type] |> key_ticket() |> lookup() |> use_cache_if_not_expired() end def read_from_local(:fetch_ticket, [appid, authorizer_appid, type, _hub_url]) do [appid, authorizer_appid, type] |> key_ticket() |> lookup() |> use_cache_if_not_expired() end def write_to_local(key, {:ok, value}) do :ets.insert(__MODULE__, {key, value}) end def write_to_local(_key, _) do # ignore error case :ok end defp key_access_token(items) do Enum.join(["access_token" | items], ".") end defp key_component_access_token(items) do Enum.join(["component_access_token" | items], ".") end defp key_ticket(items) do Enum.join(["ticket" | items], ".") end defp expired?(value) do Utils.now_unix() - value.timestamp >= value.expires_in end defp delete(key) do :ets.delete(__MODULE__, key) end defp lookup(key) do case :ets.lookup(__MODULE__, key) do [] -> {key, nil} [{^key, value}] -> {key, value} end end defp use_cache_if_not_expired({key, nil}) do {key, nil} end defp use_cache_if_not_expired({key, value}) do if expired?(value) do {key, nil} else value end end end