%%% Copyright (c) 2014, NORDUnet A/S. %%% See LICENSE for licensing information. -module(db). -behaviour(gen_server). %% API. -export([start_link/0, stop/0]). -export([create_size_table/0]). -export([add/2, add_entryhash/2, add_index_nosync/2, set_treesize/1, size/0]). -export([get_by_index/1, get_by_indices/3, get_by_leaf_hash/1]). -export([get_by_entry_hash/1, entry_for_leafhash/1, leafhash_for_index/1]). -export([leafhash_for_indices/2, indexsize/0]). -export([indexforhash_sync/2, index_sync/0]). %% gen_server callbacks. -export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2, code_change/3]). -import(stacktrace, [call/2]). -include_lib("stdlib/include/qlc.hrl"). -include("db.hrl"). -define(DB_SIZE_TABLE, db_size). size() -> [{_, Size}] = ets:lookup(?DB_SIZE_TABLE, db_size), Size. indexsize() -> index:indexsize(index_path()). init(_Args) -> {ok, []}. create_size_table() -> case ets:info(?DB_SIZE_TABLE) of undefined -> ok; _ -> ets:delete(?DB_SIZE_TABLE) end, ets:new(?DB_SIZE_TABLE, [set, public, named_table]). start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). stop() -> call(?MODULE, stop). %%%%%%%%%%%%%%%%%%%% %% Public API. -spec add(binary(), binary()) -> ok. add(LeafHash, Data) -> lager:debug("add leafhash ~s", [mochihex:to_hex(LeafHash)]), ok = perm:ensurefile(entry_root_path(), LeafHash, Data), lager:debug("leafhash ~s added", [mochihex:to_hex(LeafHash)]), ok. -spec add_entryhash(binary(), binary()) -> ok. add_entryhash(LeafHash, EntryHash) -> ok = perm:ensurefile_nosync(entryhash_root_path(), EntryHash, LeafHash), ok. -spec add_index_nosync(binary(), non_neg_integer()) -> ok. add_index_nosync(LeafHash, Index) -> call(?MODULE, {add_index_nosync, {LeafHash, Index}}). -spec set_treesize(non_neg_integer()) -> ok. set_treesize(Size) -> true = ets:insert(?DB_SIZE_TABLE, {db_size, Size}), ok. -spec get_by_indices(integer(), integer(), {sorted, true|false}) -> [{non_neg_integer(), binary(), binary()}]. get_by_indices(Start, End, {sorted, _Sorted}) -> get_by_indices_helper(Start, End). -spec get_by_index(binary()) -> notfound | {non_neg_integer(), binary(), binary()}. get_by_index(Index) -> LeafHash = leafhash_for_index(Index), Entry = entry_for_leafhash(LeafHash), {Index, LeafHash, Entry}. -spec get_by_leaf_hash(binary()) -> notfound | {non_neg_integer(), binary(), binary()}. get_by_leaf_hash(LeafHash) -> case entry_for_leafhash(LeafHash) of noentry -> notfound; Entry -> case index_for_leafhash(LeafHash) of noentry -> notfound; Index -> {Index, LeafHash, Entry} end end. -spec get_by_entry_hash(binary()) -> notfound | {notfetched, binary(), binary()}. get_by_entry_hash(EntryHash) -> case leafhash_for_entryhash(EntryHash) of noentry -> notfound; LeafHash -> case entry_for_leafhash(LeafHash) of noentry -> notfound; Entry -> %% Don't fetch index, isn't used and might not exist {notfetched, LeafHash, Entry} end end. %%%%%%%%%%%%%%%%%%%% %% gen_server callbacks. handle_cast(_Request, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. terminate(_Reason, _State) -> io:format("~p terminating~n", [?MODULE]), ok. %%%%%%%%%%%%%%%%%%%% %% The meat. % Table for Leaf hash -> Entry entry_root_path() -> {ok, Value} = application:get_env(plop, entry_root_path), Value. % Table for Leaf hash -> Entry indexforhash_root_path() -> {ok, Value} = application:get_env(plop, indexforhash_root_path), Value. % Table for Index -> Leaf hash index_path() -> {ok, Value} = application:get_env(plop, index_path), Value. % Table for Entry hash -> Leaf hash entryhash_root_path() -> {ok, Value} = application:get_env(plop, entryhash_root_path), Value. entry_for_leafhash(LeafHash) -> perm:readfile(entry_root_path(), LeafHash). index_for_leafhash(LeafHash) -> case perm:readfile(indexforhash_root_path(), LeafHash) of noentry -> noentry; Index -> binary_to_integer(Index) end. leafhash_for_index(Index) -> index:get(index_path(), Index). leafhash_for_indices(Start, End) -> index:getrange(index_path(), Start, End). leafhash_for_entryhash(EntryHash) -> perm:readfile(entryhash_root_path(), EntryHash). get_by_indices_helper(Start, _End) when Start < 0 -> []; get_by_indices_helper(Start, End) -> EndBound = min(End, indexsize() - 1), case Start =< EndBound of true -> lists:map(fun ({LeafHash, Index}) -> {Index, LeafHash, notfetched} end, lists:zip(leafhash_for_indices(Start, EndBound), lists:seq(Start, EndBound))); false -> [] end. handle_call(stop, _From, State) -> {stop, normal, stopped, State}; handle_call({add_index_nosync, {LeafHash, Index}}, _From, State) -> ok = perm:ensurefile_nosync(indexforhash_root_path(), LeafHash, integer_to_binary(Index)), ok = index:add_nosync(index_path(), Index, LeafHash), {reply, ok, State}. indexforhash_sync(LeafHash, Index) -> ok = perm:ensurefile(indexforhash_root_path(), LeafHash, integer_to_binary(Index)), ok. index_sync() -> Basepath = index_path(), ok = util:fsync([Basepath, filename:dirname(Basepath)]), ok.