Generate zone db in static modules

This commit is contained in:
Juan Wajnerman
2013-10-19 14:13:19 -03:00
parent f62709811f
commit ed55266b25
11 changed files with 71 additions and 611 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ tzdata/
\#*
db/
out
zones

View File

@@ -1,48 +0,0 @@
-module(erldev).
%% Contains development-related functions.
%-compile([export_all]).
-export([make_app/1]).
%% "Compiles" a *.app.src file into a .app file, and places it in
%% the appropriate ebin folder
make_app([AppDirAtom]) ->
%%@todo: module name conflicts?
AppDir = atom_to_list(AppDirAtom),
Modules = get_modules(AppDir),
AppSrc = try get_app_src(AppDir)
catch error:{badmatch, _} ->
% error_logger:error_msg("Couldn't get AppSrcFile for ~p~n", [AppDir]),
erlang:halt()
end,
{application, Name, Opts} = AppSrc,
NewOpts = [ {modules, Modules} | Opts ],
AppFile = filename:join([AppDir, "ebin", atom_to_list(Name) ++ ".app"]),
ok = file:write_file(AppFile, io_lib:format("~p.~n", [{application, Name, NewOpts}])),
error_logger:info_msg("Wrote .app contents to: ~p~n", [AppFile]),
ok.
%% Returns a list of module names of all *.erl files in a given Application
%% folder (assuming OTP directory structure)
get_modules(AppDir) ->
lists:map(
fun(X)-> filename:basename(X, ".erl") end,
filelib:wildcard(
filename:join([AppDir, "src", "*.erl"])
)
).
%% Converts the contsnts of the *.app.src file into erlang terms
get_app_src(AppDir) ->
[AppSrcFile] = filelib:wildcard(filename:join([AppDir, "src", "*.app.src"])),
{ok, [AppSrc]} = file:consult(AppSrcFile),
%error_logger:info_msg("AppSrc: ~p~n", [AppSrc]),
AppSrc.

View File

@@ -6,6 +6,5 @@
kernel,
stdlib
]},
{mod, {ezic_app, ezic_db_ets}},
{env, [{db_dir, "db"}, {tzdata_dir, "priv/tzdata"}]}
{env, [{tzdata_dir, "priv/tzdata"}]}
]}.

View File

@@ -1,47 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : ezic_app.erl
%%% Author : aj <aj@fattie>
%%% Description :
%%%
%%% Created : 15 Nov 2010 by aj <aj@fattie>
%%%-------------------------------------------------------------------
-module(ezic_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
%%====================================================================
%% Application callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start(Type, StartArgs) -> {ok, Pid} |
%% {ok, Pid, State} |
%% {error, Reason}
%% Description: This function is called whenever an application
%% is started using application:start/1,2, and should start the processes
%% of the application. If the application is structured according to the
%% OTP design principles as a supervision tree, this means starting the
%% top supervisor of the tree.
%%--------------------------------------------------------------------
start(_Type, StartArgs) ->
case ezic_sup:start_link(StartArgs) of
{ok, Pid} ->
{ok, Pid};
Error ->
Error
end.
%%--------------------------------------------------------------------
%% Function: stop(State) -> void()
%% Description: This function is called whenever an application
%% has stopped. It is intended to be the opposite of Module:start/2 and
%% should do any necessary cleaning up. The return value is ignored.
%%--------------------------------------------------------------------
stop(_State) ->
ok.
%%====================================================================
%% Internal functions
%%====================================================================

View File

@@ -1,158 +1,24 @@
-module(ezic_db).
-include("include/ezic.hrl").
-include_lib("eunit/include/eunit.hrl").
-export([flatzone/2]).
-behaviour(gen_server).
flatzone(Date, ZoneName) ->
ModuleName = ezic_zone_map:find(ZoneName),
FlatZones = ModuleName:flatzones(),
{D, #tztime{time = T, flag = F}} = Date,
DComp = {D, T},
case lists:filter(filter(DComp, F), FlatZones) of
[] -> {error, no_zone};
[FlatZone] -> FlatZone;
Matches = [_, _] -> {error, {ambiguous_zone, Matches}};
Matches -> {error, {should_not_happen, {Matches, Date, ZoneName}}}
end.
-export([
zones/1
, rules/1
, flatzones/1
, flatzone/2
%, insert/2
, get_all/1
, insert_all/1
, wipe/1
, flatten/0
, get_implementation/0
]).
-export([
start_link/1
, init/1
, code_change/3
, handle_call/3
, handle_cast/2
, handle_info/2
, terminate/2
]).
-record(state, {mod}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% PUBLIC API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
zones(TzName) ->
gen_server:call(?MODULE, {zones, TzName}).
rules(TzName) ->
gen_server:call(?MODULE, {rules, TzName}).
flatzones(TzName) ->
gen_server:call(?MODULE, {flatzones, TzName}).
flatzone(Date, TzName) ->
gen_server:call(?MODULE, {flatzone, Date, TzName}).
get_all(Tab) ->
gen_server:call(?MODULE, {all, Tab}).
insert_all(Records) ->
gen_server:call(?MODULE, {insert_all, Records}).
%wipe() ->
% gen_server:call(?MODULE, {wipe}).
wipe(Tab) ->
gen_server:call(?MODULE, {wipe, Tab}).
flatten() ->
gen_server:call(?MODULE, {flatten}).
get_implementation() ->
gen_server:call(?MODULE, {implementation}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% GEN_SERVER
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link(StartArgs) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, StartArgs, []).
%% TODO: create a db behavior
init(DbModule) when DbModule =:= ezic_db_mnesia;
DbModule=:= ezic_db_ets ->
DbModule:init(),
State = #state{mod = DbModule},
{ok, State}.
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%% DB Lookup
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
handle_call({zones, Name}, _, State) ->
Mod= State#state.mod,
Matches= Mod:zones(Name),
{reply, Matches, State};
handle_call({rules, Name}, _, State) ->
Mod= State#state.mod,
Matches= Mod:rules(Name),
{reply, Matches, State};
handle_call({all, Tab}, _, State) ->
Mod= State#state.mod,
Matches= Mod:get_all(Tab),
{reply, Matches, State};
handle_call({flatzones, Name}, _, State) ->
Mod= State#state.mod,
Result= Mod:flatzones(Name),
{reply, Result, State};
handle_call({flatzone, Date, Name}, _, State) ->
Mod= State#state.mod,
Result= Mod:flatzone(Date, Name),
{reply, Result, State};
handle_call({insert_all, Records}, _, State) ->
Mod= State#state.mod,
Mod:insert_all(Records),
{noreply, State};
handle_call({wipe, Tab}, _, State) ->
Mod= State#state.mod,
Result= Mod:wipe(Tab),
{reply, Result, State};
handle_call({flatten}, _, State) ->
Mod= State#state.mod,
Zones= Mod:get_all(zone),
Rules= Mod:get_all(rule),
FlatZone= ezic_flatten:flatten(Zones, Rules),
Result= Mod:insert_all(FlatZone),
{reply, Result, State};
handle_call({implementation}, _, State) ->
Mod= State#state.mod,
Impl= Mod:implementation(),
{reply, Impl, State};
handle_call(_, _, State) ->
{noreply, State}.
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%% Other gen_server
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
code_change(_, State, _) ->
{ok, State}.
handle_cast(_, State) ->
{noreply, State}.
handle_info(_, State) ->
{noreply, State}.
terminate(_, _State) ->
ok.
filter(DComp, u) ->
fun(#flatzone{utc_from = From, utc_to = To}) ->
(From =< DComp) and ((DComp =< To) or (To == current))
end;
filter(DComp, w) ->
fun(#flatzone{wall_from = From, wall_to = To}) ->
(From =< DComp) and ((DComp =< To) or (To == current))
end.

View File

@@ -1,149 +0,0 @@
-module(ezic_db_ets).
-include("include/ezic.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(DB_FILENAME, "db.e2f").
-export([
init/0
, zones/1
, rules/1
, flatzones/1
, flatzone/2
%, insert/2
, get_all/1
, insert_all/1
, wipe/1
, implementation/0
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% PUBLIC API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
implementation() -> ?MODULE.
zones(TzName) ->
ets:select(zone, [{#zone{name=TzName, _='_'}, [], ['$_']}]).
rules(TzName) ->
ets:select(rule, [{#rule{name=TzName, _='_'}, [], ['$_']}]).
flatzones(TzName) ->
ets:select(flatzone, [{#flatzone{tzname=TzName, _='_'}, [], ['$_']}]).
flatzone(Date, TzName) ->
FlatZones= ets:select(flatzone, ezic_flatten:ms(Date, TzName)),
case length(FlatZones) of
1 ->
hd(FlatZones);
2 ->
{error, {ambiguous_zone, FlatZones}};
0 ->
{error, no_zone};
_ ->
{error, {should_not_happen, {FlatZones, Date, TzName}}}
end.
get_all(Tab) ->
try ets:lookup(Tab, Tab) of X -> X
catch error:X -> {error, X}
end.
insert_all(Records) ->
Zones= [ZI || ZI <- Records, ZI = #zone{}],
ets:insert(zone, Zones),
Rules= [RI || RI <- Records, RI = #rule{}],
ets:insert(rule, Rules),
FlatZones= [FZ || FZ <- Records, FZ = #flatzone{}],
ets:insert(flatzone, FlatZones).
%wipe() ->
% gen_server:call(?MODULE, {wipe}).
wipe(Tab) ->
ets:delete(Tab, Tab).
init() ->
{ok, DbDir}= application:get_env(db_dir),
Filename= filename:join(DbDir, ?DB_FILENAME),
case db_sane(Filename) of
true -> load_tabfile(Filename);
false -> create_tables(Filename)
end,
{ok, []}.
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%% Private functions for initializing ets
%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%% -> true if table exists
db_sane(Filename) ->
%% @todo ensure data is present for each record type.
case ets:tabfile_info(Filename) of
{ok, _} -> true;
{error, _} -> false
end.
%% creates the dets table and populates it.
create_tables(Filename) ->
{ok, Zones, Rules, _, _} = ezic_record:separate(ezic_loader:load()),
ets:new(zone, [duplicate_bag, named_table]),
true = ets:insert(zone, Zones),
ets:new(rule, [duplicate_bag, named_table]),
true = ets:insert(rule, Rules),
ets:new(flatzone, [duplicate_bag, named_table]),
%% error_logger:info_msg("~p~n", [Rules]),
FlatZones = ezic_flatten:flatten(Zones, Rules),
true = ets:insert(flatzone, FlatZones),
% combine into one ets
Ets= ets:new(ezic_db_ets, [duplicate_bag]),
ets:insert(Ets, ets:lookup(zone, zone)),
ets:insert(Ets, ets:lookup(rule, rule)),
ets:insert(Ets, ets:lookup(flatzone, flatzone)),
% save to disk
ets:tab2file(Ets, Filename),
ets:delete(Ets),
ok.
%% loads or creates the dets table.
load_tabfile(Filename) ->
{ok, Ets}= ets:file2tab(Filename),
Zones= ets:lookup(Ets, zone),
ets:new(zone, [duplicate_bag, named_table]),
ets:insert(zone, Zones),
Rules= ets:lookup(Ets, rule),
ets:new(rule, [duplicate_bag, named_table]),
ets:insert(rule, Rules),
FlatZones= ets:lookup(Ets, flatzone),
ets:new(flatzone, [duplicate_bag, named_table]),
ets:insert(flatzone, FlatZones),
ets:delete(Ets),
ok.

View File

@@ -1,159 +0,0 @@
-module(ezic_db_mnesia).
-include("include/ezic.hrl").
-include_lib("stdlib/include/qlc.hrl").
-include_lib("eunit/include/eunit.hrl").
-export([
init/0
, zones/1
, rules/1
, flatzones/1
, flatzone/2
%, insert/2
, get_all/1
, insert_all/1
, wipe/1
, implementation/0
]).
-define(create(Record),
{atomic, ok} = mnesia:create_table(Record,
[{type, bag}
, {disc_copies, [node()]}
, {attributes, record_info(fields, Record)}
])).
implementation() -> ?MODULE.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% READ - db reading methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% retrieve all zones by name
zones(Name) ->
F = fun()->
Q = qlc:q([Z || Z=#zone{name=N} <- mnesia:table(zone), N=:=Name]),
qlc:e(Q)
end,
{atomic, Zones}= mnesia:transaction(F),
Zones.
% retrieve all rules by name
rules(Name) ->
F = fun()->
Q = qlc:q([R || R=#rule{name=N} <- mnesia:table(rule), N=:=Name]),
qlc:e(Q)
end,
{atomic, Rules}= mnesia:transaction(F),
Rules.
% retrieve all flatzones by tz name
flatzones(Name) ->
F = fun()->
Q = qlc:q([Z || Z=#flatzone{tzname=N} <- mnesia:table(flatzone), N=:=Name]),
qlc:e(Q)
end,
{atomic, FlatZones}= mnesia:transaction(F),
FlatZones.
% get all records from table
get_all(Tab) when is_atom(Tab) ->
F = fun() ->
Q = qlc:q([R || R<- mnesia:table(Tab)]),
qlc:e(Q)
end,
{atomic, Ret}= mnesia:transaction(F),
Ret.
%% get a flatzone for a specific date
%% Date :: {date(), #tztime{}}
%% returns #flatzone{}
%% or throws either error:
%% * {ambiguous_zone, [Z1,Z2,...]}
%% * {no_zone}
flatzone(Date, TzName) ->
%% @todo validate Date
F = fun()->
Q = qlc:q(
[Fz || Fz=#flatzone{tzname=N} <- mnesia:table(flatzone)
, N=:=TzName
, ezic_flatten:contains_date(Fz, Date)]),
qlc:e(Q)
end,
{atomic, FlatZones}= mnesia:transaction(F),
case length(FlatZones) of
1 ->
hd(FlatZones);
2 ->
erlang:error(ambiguous_zone, FlatZones);
0 ->
erlang:error(no_zone);
_ ->
erlang:error(should_not_happen, {FlatZones, Date, TzName})
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ADMIN - initialization and administration methods
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% initialize the db
init() ->
create_tabs(mnesia:create_schema([node()])),
mnesia:wait_for_tables([rule, zone, link, leap, flatzone], 3000).
% WARNING: deletes all db files
%wipe() ->
% mnesia:stop(),
% mnesia:delete_schema([node()]).
% WARNING: deletes all entries in table Tab
wipe(Tab) ->
mnesia:transaction(
fun()->
mnesia:delete(Tab, '_', write)
end).
create_tabs(ok) ->
mnesia:start(),
?create(rule),
?create(zone),
?create(link),
?create(leap),
?create(flatzone),
{ok, Zones, Rules, _, _} = ezic_record:separate(ezic_loader:load()),
insert_all(Zones),
insert_all(Rules),
FlatZones = ezic_flatten:flatten(Zones, Rules),
error_logger:info_msg("~p~n", [FlatZones]),
insert_all(FlatZones),
ok;
create_tabs({error, {_, {already_exists,_}}}) ->
mnesia:start(),
ok;
create_tabs(E) ->
?debugVal(E).
insert_all(Records) ->
mnesia:transaction(
fun() ->
lists:foreach(
fun(R)-> mnesia:write(R) end,
Records)
end).

View File

@@ -9,7 +9,7 @@
%% @todo move MAXYEAR to config file
-define(MAXYEAR, 2500). % last year to process flatzones for.
-define(MAXYEAR, 2050). % last year to process flatzones for.
-export([

46
src/ezic_generator.erl Normal file
View File

@@ -0,0 +1,46 @@
-module(ezic_generator).
-export([generate/0]).
-include("include/ezic.hrl").
generate() ->
{ok, Zones, Rules, _, _} = ezic_record:separate(ezic_loader:load()),
FlatZones = ezic_flatten:flatten(Zones, Rules),
filelib:ensure_dir("zones/"),
{ok, MapFile} = file:open("zones/ezic_zone_map.erl", [write]),
file:write(MapFile, "-module(ezic_zone_map).\n"),
file:write(MapFile, "-export([find/1]).\n"),
generate_files(FlatZones, MapFile, []),
ok.
generate_files([], MapFile, Files) ->
lists:foreach(fun({_, File}) ->
file:write(File, "]."),
file:close(File)
end, Files),
file:write(MapFile, "find(Z) -> throw({zone_not_found, Z}).\n"),
file:close(MapFile);
generate_files([FlatZone = #flatzone{tzname = Name} | Rest], MapFile, Files) ->
{File, NewFiles} = case orddict:find(Name, Files) of
error ->
IO = create_file(Name, MapFile),
{IO, orddict:store(Name, IO, Files)};
{ok, IO} ->
file:write(IO, ",\n"),
{IO, Files}
end,
file:write(File, io_lib:format("~1000p", [FlatZone])),
generate_files(Rest, MapFile, NewFiles).
create_file(ZoneName, MapFile) ->
io:format("Creating file for zone ~p~n", [ZoneName]),
ModuleName = "ezic_zone_" ++ re:replace(string:to_lower(ZoneName), "/", "_", [global, {return, list}]),
FileName = "zones" ++ "/" ++ ModuleName ++ ".erl",
{ok, File} = file:open(FileName, [write]),
file:write(File, io_lib:format("-module(~p).\n", [list_to_atom(ModuleName)])),
file:write(File, "-export([flatzones/0]).\n"),
file:write(File, "flatzones() ->\n["),
file:write(MapFile, io_lib:format("find(~p) -> ~p;\n", [ZoneName, list_to_atom(ModuleName)])),
File.

View File

@@ -43,7 +43,7 @@ parse_dir(Dir) ->
% returns a list of tzdata records from file
parse_file(File) ->
%% error_logger:info_msg("Parsing File: ~p~n", [File]),
error_logger:info_msg("Parsing File: ~p~n", [File]),
{ok, FD} = file:open(File, [read]),
parse_lines(file:read_line(FD), FD, []).

View File

@@ -1,49 +0,0 @@
%%%-------------------------------------------------------------------
%%% File : ezic_sup.erl
%%% Author : aj <aj@fattie>
%%% Description :
%%%
%%% Created : 15 Nov 2010 by aj <aj@fattie>
%%%-------------------------------------------------------------------
-module(ezic_sup).
-behaviour(supervisor).
%% API
-export([start_link/1]).
%% Supervisor callbacks
-export([init/1]).
-define(SERVER, ?MODULE).
%%====================================================================
%% API functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the supervisor
%%--------------------------------------------------------------------
start_link(StartArgs) ->
supervisor:start_link(?MODULE, StartArgs).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Func: init(Args) -> {ok, {SupFlags, [ChildSpec]}} |
%% ignore |
%% {error, Reason}
%% Description: Whenever a supervisor is started using
%% supervisor:start_link/[2,3], this function is called by the new process
%% to find out about restart strategy, maximum restart frequency and child
%% specifications.
%%--------------------------------------------------------------------
init(StartArgs) ->
EzicDb = {ezic_db,{ezic_db, start_link, [StartArgs]},
permanent,2000,worker,[ezic_db]},
{ok,{{one_for_all,3,30}, [EzicDb]}}.
%%====================================================================
%% Internal functions
%%====================================================================