%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. %%% %%% Entry storage. %%% %%% BUGS: Retrieving entries don't update list of tables. The effects %%% are that 1) dets:open_file is called for each retrieval and 2) %%% tables only read from won't get closed by close/1. -module(es). -export_type([entry_store/0]). -export([open/0, close/1, store/3, retrieve/2]). -import(lists, [filtermap/2, map/2, foreach/2, nth/2, flatten/1]). -record(entry_store, {tables :: list()}). % [dets:tab_name()] -type entry_store() :: #entry_store{}. %%%%%%%%%%%%%%%%%%%% %% Public interface. -spec open() -> entry_store(). open() -> Files = filtermap(fun(F) -> dets:is_dets_file(F) end, filelib:wildcard("es*.dat")), Tables = map(fun(F) -> list_to_atom(filename:basename(F, ".dat")) end, Files), foreach(fun(T) -> T = open_file(T) end, Tables), #entry_store{tables = Tables}. -spec close(entry_store()) -> ok. close(#entry_store{tables = Tables}) -> foreach(fun(Tab) -> dets:close(Tab) end, Tables). -spec store(entry_store(), non_neg_integer(), binary()) -> entry_store(). store(Store, Index, Entry) -> {Tables, Tab} = get_table(Store#entry_store.tables, Index), true = dets:insert_new(Tab, {Index, Entry}), Store#entry_store{tables = Tables}. -spec retrieve(entry_store(), non_neg_integer()) -> binary() | notfound. retrieve(#entry_store{tables = Tables}, Index) -> {_, Tab} = get_table(Tables, Index), case dets:lookup(Tab, Index) of [E] -> element(2, E); [] -> notfound; Error -> exit(Error) end. %%%%%%%%%%%%%%%%%%%% %% Internal functions. %%-define(TABLE_SIZE, 25000000). % < 2e9 / 32 + 8 -define(TABLE_SIZE, 256). % For testing. -define(AUTO_SAVE_INTERVAL, 30000). % Milliseconds. -spec get_table(list(), non_neg_integer()) -> {list(), dets:tab_name()}. get_table(Tables, Index) -> TableIndex = trunc(Index / ?TABLE_SIZE) + 1, if TableIndex =< length(Tables) -> {Tables, nth(TableIndex, Tables)}; true -> Tab = open_file(list_to_atom(flatten( io_lib:format("es~p", [TableIndex])))), {Tables ++ [Tab], Tab} % Order is important, efficiency not. end. -spec open_file(atom()) -> dets:tab_name(). open_file(Name) -> {ok, Tab} = dets:open_file(Name, [{type, set}, {file, atom_to_list(Name)++".dat"}, {auto_save, ?AUTO_SAVE_INTERVAL}, {min_no_slots, ?TABLE_SIZE}, {max_no_slots, ?TABLE_SIZE}]), Tab.