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