ekfa/apps/efka/src/iot/efka_iot_cache.erl
2026-05-11 23:21:19 +08:00

104 lines
2.8 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2026, <COMPANY>
%%% @doc
%%% DETS backed outbound packet cache for efka_iot_client.
%%% @end
%%%-------------------------------------------------------------------
-module(efka_iot_cache).
-author("anlicheng").
-define(CACHE_TAB, cache).
-define(MAX_CACHE_ITEMS, 5000000).
-define(MAX_CACHE_FILE_SIZE, 1073741824).
-export([open/0, close/0, insert/1, fetch_next/0, delete/1]).
-spec open() -> ok | {error, term()}.
open() ->
{ok, DetsDir} = application:get_env(efka, dets_dir),
File = DetsDir ++ "cache.dets",
case dets:open_file(?CACHE_TAB, [{file, File}, {type, bag}, {keypos, 1}]) of
{ok, ?CACHE_TAB} ->
ok;
{error, Reason} ->
{error, Reason}
end.
-spec close() -> ok.
close() ->
case dets:close(?CACHE_TAB) of
ok ->
ok;
{error, not_owner} ->
ok
end.
-spec insert(binary()) -> {ok, non_neg_integer()} | {error, term()}.
insert(Data) when is_binary(Data) ->
case dets:insert(?CACHE_TAB, {generate_cache_id(), Data}) of
ok ->
trim_limits(0);
{error, Reason} ->
{error, Reason}
end.
-spec fetch_next() -> error | {ok, {integer(), binary()}}.
fetch_next() ->
case dets:first(?CACHE_TAB) of
'$end_of_table' ->
error;
Key ->
case dets:lookup(?CACHE_TAB, Key) of
[Entry | _] ->
{ok, Entry};
[] ->
fetch_next()
end
end.
-spec delete(integer()) -> ok | {error, term()}.
delete(Id) when is_integer(Id) ->
dets:delete(?CACHE_TAB, Id).
-spec generate_cache_id() -> integer().
generate_cache_id() ->
erlang:unique_integer([monotonic, positive]).
-spec over_limit() -> boolean().
over_limit() ->
item_count() > ?MAX_CACHE_ITEMS orelse file_size() > ?MAX_CACHE_FILE_SIZE.
-spec item_count() -> non_neg_integer().
item_count() ->
dets:info(?CACHE_TAB, size).
-spec file_size() -> non_neg_integer().
file_size() ->
dets:info(?CACHE_TAB, file_size).
-spec trim_limits(non_neg_integer()) -> {ok, non_neg_integer()} | {error, term()}.
trim_limits(DroppedCount) ->
case over_limit() of
true ->
case delete_oldest_entry() of
ok ->
trim_limits(DroppedCount + 1);
error ->
{ok, DroppedCount};
{error, Reason} ->
{error, Reason}
end;
false ->
{ok, DroppedCount}
end.
-spec delete_oldest_entry() -> ok | error | {error, term()}.
delete_oldest_entry() ->
case dets:first(?CACHE_TAB) of
'$end_of_table' ->
error;
Key ->
dets:delete(?CACHE_TAB, Key)
end.