%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(asn1_db).
-export([dbstart/1,dbnew/1,dbsave/2,dbload/1,dbput/3,dbget/2,dbget_all/1]).
-export([dbget_all_mod/1,dbclear/0,dberase_module/1,dbstop/0]).
-record(state, {parent, monitor, includes, table}).
%% Interface
dbstart(Includes) ->
Parent = self(),
case get(?MODULE) of
undefined ->
put(?MODULE, spawn_link(fun() -> init(Parent, Includes) end)),
true;
_Pid ->
req({new_includes, Includes})
end.
dbnew(Module) -> req({new, Module}).
dbsave(OutFile, Module) -> req({save, OutFile, Module}).
dbload(Module) -> req({load, Module}).
dbput(Module, K, V) -> req({set, Module, K, V}).
dbget(Module, K) -> req({get, Module, K}).
dbget_all(K) -> req({get_all, K}).
dbget_all_mod(Mod) -> req({all_mod, Mod}).
dbclear() -> req(clear).
dberase_module({module,M}) -> req({delete_mod, M}).
dbstop() -> Resp = req(stop), erase(?MODULE), Resp.
%% Internal functions
req(Request) ->
get(?MODULE) ! {self(), Request},
receive {?MODULE, Reply} -> Reply after 5000 -> exit(db_timeout) end.
reply(From, Response) ->
From ! {?MODULE, Response}.
init(Parent, Includes) ->
MRef = erlang:monitor(process, Parent),
loop(#state{parent = Parent, monitor = MRef, includes = Includes,
table = ets:new(?MODULE, [])}).
loop(#state{parent = Parent, monitor = MRef, table = Table,
includes = Includes} = State) ->
receive
{From, {set, Mod, K2, V}} ->
[{_, Modtab}] = ets:lookup(Table, Mod),
ets:insert(Modtab, {K2, V}),
reply(From, ok),
loop(State);
{From, {get, Mod, K2}} ->
Result = case ets:lookup(Table, Mod) of
[] -> opentab(Table, Mod, Includes);
[{_, Modtab}] -> {ok, Modtab}
end,
case Result of
{ok, Newtab} -> reply(From, lookup(Newtab, K2));
_Error -> reply(From, undefined)
end,
loop(State);
{From, {all_mod, Mod}} ->
[{_, Modtab}] = ets:lookup(Table, Mod),
reply(From, ets:tab2list(Modtab)),
loop(State);
{From, {delete_mod, Mod}} ->
[{_, Modtab}] = ets:lookup(Table, Mod),
ets:delete(Modtab),
ets:delete(Table, Mod),
reply(From, ok),
loop(State);
{From, {save, OutFile, Mod}} ->
[{_,Mtab}] = ets:lookup(Table, Mod),
reply(From, ets:tab2file(Mtab, OutFile)),
loop(State);
{From, {load, Mod}} ->
Result = case ets:lookup(Table, Mod) of
[] -> opentab(Table, Mod, Includes);
[{_, Modtab}] -> {ok, Modtab}
end,
reply(From, Result),
loop(State);
{From, {new, Mod}} ->
case ets:lookup(Table, Mod) of
[{_, Modtab}] -> ets:delete(Modtab);
_ -> true
end,
ModTableId = ets:new(list_to_atom(lists:concat(["asn1_",Mod])), []),
ets:insert(Table, {Mod, ModTableId}),
reply(From, ok),
loop(State);
{From, clear} ->
[ets:delete(Mt) || {_, Mt} <- ets:tab2list(Table)],
ets:delete(Table),
reply(From, cleared),
loop(State#state{table = ets:new(asn1, [set])});
{From, {new_includes, NewIncludes}} ->
reply(From, true),
loop(State#state{includes = NewIncludes});
{From, stop} ->
reply(From, stopped); %% Nothing to store
{'DOWN', MRef, process, Parent, Reason} ->
exit(Reason)
end.
opentab(Tab, Mod, []) ->
opentab(Tab, Mod, ["."]);
opentab(Tab, Mod, Includes) ->
Base = lists:concat([Mod, ".asn1db"]),
opentab2(Tab, Base, Mod, Includes, ok).
opentab2(_Tab, _Base, _Mod, [], Error) ->
Error;
opentab2(Tab, Base, Mod, [Ih|It], _Error) ->
File = filename:join(Ih, Base),
case ets:file2tab(File) of
{ok, Modtab} ->
ets:insert(Tab, {Mod, Modtab}),
{ok, Modtab};
NewErr ->
opentab2(Tab, Base, Mod, It, NewErr)
end.
lookup(Tab, K) ->
case ets:lookup(Tab, K) of
[] -> undefined;
[{K,V}] -> V
end.