src/http_cache_store_native.erl

%% @private
-module(http_cache_store_native).

-include("http_cache_store_native.hrl").

-behaviour(http_cache_store).

-export([list_candidates/2, get_response/2, put/6, notify_response_used/2,
         invalidate_url/2, invalidate_by_alternate_key/2, delete_object/2, object_key/2, lru/2]).

list_candidates(RequestKey, _Opts) ->
    Spec =
        [{{{RequestKey, '$1'}, '$2', '_', {'$3', '$4', '_'}, '$5', '_'},
          [],
          [['$1', '$2', '$3', '$4', '$5']]}],
    Now = unix_now(),
    [{{RequestKey, SecondKeyPart}, Status, RespHeaders, VaryHeaders, RespMetadata}
     || [SecondKeyPart, VaryHeaders, Status, RespHeaders, RespMetadata]
            <- ets:select(?OBJECT_TABLE, Spec),
        Now < map_get(grace, RespMetadata)].

get_response(ObjectKey, _Opts) ->
    case ets:lookup(?OBJECT_TABLE, ObjectKey) of
        [{_ObjectKey,
          _VaryHeaders,
          _UrlDigest,
          {Status, RespHeaders, RespBody},
          RespMetadata,
          _SeqNumber}] ->
            {Status, RespHeaders, RespBody, RespMetadata};
        [] ->
            undefined
    end.

put(RequestKey, UrlDigest, VaryHeaders, Response, #{grace := _} = RespMetadata, _Opts) ->
    case http_cache_store_native_worker_sup:start_worker({cache_object,
                                                          {RequestKey,
                                                           UrlDigest,
                                                           VaryHeaders,
                                                           Response,
                                                           RespMetadata}})
    of
        ok ->
            ObjectKey = object_key(RequestKey, VaryHeaders),
            Expires = map_get(grace, RespMetadata),
            http_cache_store_native_cluster_mon:broadcast_object_available(ObjectKey, Expires),
            ok;
        {error, _} = Error ->
            Error
    end.

invalidate_url(UrlDigest, _Opts) ->
    http_cache_store_native_cluster_mon:broadcast_invalidate_url(UrlDigest),
    case http_cache_store_native_worker_sup:start_worker({invalidate_url, UrlDigest}) of
        ok ->
            {ok, undefined};
        {error, _} = Error ->
            Error
    end.

invalidate_by_alternate_key(AltKeys, _Opts) ->
    http_cache_store_native_cluster_mon:broadcast_invalidate_by_alternate_key(AltKeys),
    case http_cache_store_native_worker_sup:start_worker({invalidate_by_alternate_key,
                                                          AltKeys})
    of
        ok ->
            {ok, undefined};
        {error, _} = Error ->
            Error
    end.

notify_response_used(ObjectKey, _Opts) ->
    SeqNumber = erlang:unique_integer(),
    case ets:update_element(?OBJECT_TABLE, ObjectKey, {6, SeqNumber}) of
        true ->
            lru(ObjectKey, SeqNumber),
            ok;
        false ->
            ok
    end.

delete_object(ObjectKey, Reason) ->
    telemetry:execute([http_cache_store_native, object_deleted], #{}, #{reason => Reason}),
    ets:delete(?OBJECT_TABLE, ObjectKey),
    ok.

object_key(RequestKey, VaryHeaders) ->
    {RequestKey, crypto:hash(sha256, erlang:term_to_binary(VaryHeaders))}.

lru(ObjectKey, SeqNumber) ->
    ets:insert(?LRU_TABLE, {{unix_now(), ObjectKey, SeqNumber}}).

unix_now() ->
    os:system_time(second).