summaryrefslogtreecommitdiff
path: root/src/es.erl
blob: 9e8be8da0295e917512a1027c51641419c3d652b (plain)
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.