defmodule ExMicrosoftBot.RefreshableAgent do @callback get_refreshed_state(any, any) :: any @callback time_to_refresh_after_in_seconds(any) :: integer @doc false defmacro __using__(_) do quote location: :keep do require Logger @behaviour ExMicrosoftBot.RefreshableAgent use GenServer # Init def init(args) do state = args |> get_refreshed_state_and_schedule_refresh(nil) |> Map.merge(%{original_args: args}, fn _k1, _v1, v2 -> v2 end) {:ok, state} end def start_link(args \\ []) do GenServer.start_link(__MODULE__, args, name: __MODULE__) end # GenServer API def handle_call(:get_state, _from, %{state: mod_state} = state) do Logger.debug("refreshable_agent: handle_call/3 -> :get_state -> #{inspect(state)}") {:reply, mod_state, state} end # Cancel the timer, send message to self to refresh and reply with :ok def handle_call(:force_refresh, _from, %{timer_ref: timer_ref} = state) do Logger.debug("refreshable_agent: handle_call/3 -> :force_refresh") Process.cancel_timer(timer_ref) send(self(), :refresh) {:reply, :ok, state} end def handle_info(:refresh, %{state: mod_state, original_args: args} = state) do new_state = args |> get_refreshed_state_and_schedule_refresh(mod_state) |> Map.merge(state, fn _k, v1, _v2 -> v1 end) {:noreply, new_state} end # Private @spec get_state() :: any defp get_state() do GenServer.call(__MODULE__, :get_state) end @spec get_refreshed_state_and_schedule_refresh(any, any) :: Map.t() defp get_refreshed_state_and_schedule_refresh(args, old_state) do Logger.debug( "RefreshableAgent.get_refreshed_state_and_schedule_refresh/2 -> #{inspect(old_state)}" ) updated_mod_state = get_refreshed_state(args, old_state) Logger.debug( "RefreshableAgent.get_refreshed_state_and_schedule_refresh/2 -> #{ inspect(updated_mod_state) }" ) timer_ref = updated_mod_state |> time_to_refresh_after_in_seconds() # Converting to ms |> Kernel.*(1000) |> schedule_next_refresh %{state: updated_mod_state, timer_ref: timer_ref} end @spec force_refresh_state() :: :ok defp force_refresh_state() do GenServer.call(__MODULE__, :force_refresh) end defp schedule_next_refresh(refresh_time_in_seconds) do Logger.debug("RefreshableAgent.schedule_next_refresh/1 -> #{refresh_time_in_seconds}") Process.send_after(__MODULE__, :refresh, refresh_time_in_seconds) end end end end