1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
%%% 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.
|