% vim: ts=4 sw=4 et
% Copyright (c) 2013-2021 Jesse Gumm
% See LICENSE for licensing information.
-module(qdate_srv).
-behaviour(gen_server).
-export([
set_timezone/1,
set_timezone/2,
get_timezone/0,
get_timezone/1,
clear_timezone/0,
clear_timezone/1,
register_parser/1,
register_parser/2,
get_parsers/0,
deregister_parsers/0,
deregister_parser/1,
register_format/2,
get_format/1,
deregister_format/1,
get_formats/0
]).
%% API
-export([start_link/0]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
%% Simple wrappers for unique keys
-define(BASETAG, qdate_var).
-define(KEY(Name), {?BASETAG, Name}).
-define(TZTAG, qdate_tz).
-define(TZKEY(Name), {?TZTAG, Name}).
-define(PARSERTAG, qdate_parser).
-define(PARSERKEY(Name), {?PARSERTAG, Name}).
-define(FORMATTAG, qdate_format).
-define(FORMATKEY(Name), {?FORMATTAG, Name}).
-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
init([]) ->
error_logger:info_msg("Creating qdate ETS Table: ~p",[?TABLE]),
?TABLE = ets:new(?TABLE, [public, {read_concurrency, true}, named_table]),
{ok, #state{}}.
handle_call({set, Key, Val}, _From, State) ->
ets:insert(?TABLE, {Key, Val}),
{reply, ok, State};
handle_call({unset, Key}, _From, State) ->
ets:delete(?TABLE, Key),
{reply, ok, State};
handle_call(_, _From, State) ->
{reply, invalid_request, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% PUBLIC API FUNCTIONS
set_timezone(TZ) ->
put_pd(?TZTAG, TZ).
set_timezone(Key, TZ) ->
set_env(?TZKEY(Key), TZ).
get_timezone() ->
get_pd(?TZTAG).
get_timezone(Key) ->
get_env(?TZKEY(Key)).
clear_timezone() ->
unset_pd(?TZTAG).
clear_timezone(Key) ->
unset_env(?TZKEY(Key)).
register_parser(Parser) when is_function(Parser,1) ->
register_parser(erlang:make_ref(),Parser).
register_parser(Key,Parser) when is_function(Parser,1) ->
set_env(?PARSERKEY(Key), Parser).
deregister_parser(Key) ->
unset_env(?PARSERKEY(Key)).
deregister_parsers() ->
[deregister_parser(Key) || {Key, _} <- get_parsers()].
get_parsers() ->
get_all_env(?PARSERTAG).
register_format(Key, Format) ->
set_env(?FORMATKEY(Key), Format).
get_format(Key) ->
get_env(?FORMATKEY(Key)).
deregister_format(Key) ->
unset_env(?FORMATKEY(Key)).
get_formats() ->
get_all_env(?FORMATTAG).
%% PRIVATE TOOLS
%% App Vars
set_env(Key, Val) ->
gen_server:call(?SERVER, {set, ?KEY(Key), Val}).
get_env(Key) ->
get_env(Key, undefined).
get_env(Key, Default) ->
case ets:lookup(?TABLE, ?KEY(Key)) of
[{__Key, Val}] -> Val;
[] -> Default
end.
unset_env(Key) ->
gen_server:call(?SERVER, {unset, ?KEY(Key)}).
get_all_env(FilterTag) ->
try ets:tab2list(?TABLE) of
All ->
[{Key, V} || {{?BASETAG, {Tag, Key}}, V} <- All, Tag==FilterTag]
catch
error:badarg:S ->
Msg = maybe_start_msg(),
logger:error(Msg),
error({qdate_get_all_env_failed, #{
original_error => {error, badarg, S}
}})
end.
maybe_start_msg() ->
"Attempting to read qdate environment failed (qdate_srv:get_all_env/1).\n"
"qdate may not have been started properly.\n"
"Please ensure qdate is started properly by either:\n"
"* Putting qdate in the 'applications' key in your .app.src or .app file, or\n"
"* Starting it manually with application:ensure_all_started(qdate) or qdate:start().".
%% ProcDic Vars
get_pd(Key) ->
erlang:get(?KEY(Key)).
put_pd(Key, Val) ->
erlang:put(?KEY(Key), Val),
ok.
unset_pd(Key) ->
put_pd(Key, undefined).