diff --git a/Makefile b/Makefile index d9cc3d4..efd842b 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ERLC_WARNINGS := -W1 MNESIA_DIR := db -RUN_INIT := -run ezic_db init +RUN_INIT := -boot start_sasl ##-run ezic_db init DEBUG := -DNODEBUG -Ddebug TEST := TZSET := northamerica @@ -31,7 +31,8 @@ clean : run : - erl -pa ebin -mnesia dir $(MNESIA_DIR) $(RUN_INIT) +## erl -pa ebin -mnesia dir $(MNESIA_DIR) $(RUN_INIT) + erl -pa ebin $(RUN_INIT) devstart : RUN_INIT += -s ezic dev -s erlang halt @@ -48,4 +49,5 @@ diff : debug : DEBUG += +debug_info +##debug : RUN_INIT += -s ezic dev debug : all run \ No newline at end of file diff --git a/src/ezic.app.src b/src/ezic.app.src new file mode 100644 index 0000000..e3443ff --- /dev/null +++ b/src/ezic.app.src @@ -0,0 +1,10 @@ +{application, ezic, + [ + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {ezic_app, []}}, + {env, [{db_dir, "db"}]} + ]}. \ No newline at end of file diff --git a/src/ezic.erl b/src/ezic.erl index 3995f90..b2390f6 100644 --- a/src/ezic.erl +++ b/src/ezic.erl @@ -78,12 +78,14 @@ load(Folder) -> dev() -> - ezic:load(filename:join("priv","tzdata")), - ezic_flatten:flatten(), - +% ezic:load(filename:join("priv","tzdata")), +% ezic_flatten:flatten(), % Zones= ezic_db:zones("WET"), % ezic_flatten:flatten_all_zones(Zones), + application:start(ezic), + ezic_db_ets:flatzone({{2010,11,17}, #tztime{time={23,42,0}}}, "America/Los_Angeles"), + ok. zf() -> diff --git a/src/ezic_app.erl b/src/ezic_app.erl new file mode 100644 index 0000000..3eb803b --- /dev/null +++ b/src/ezic_app.erl @@ -0,0 +1,47 @@ +%%%------------------------------------------------------------------- +%%% File : ezic_app.erl +%%% Author : aj +%%% Description : +%%% +%%% Created : 15 Nov 2010 by aj +%%%------------------------------------------------------------------- +-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 +%%==================================================================== diff --git a/src/ezic_db.erl b/src/ezic_db.erl index 73d2613..f319ca2 100644 --- a/src/ezic_db.erl +++ b/src/ezic_db.erl @@ -4,8 +4,19 @@ -include_lib("eunit/include/eunit.hrl"). --export([zones/1, rules/1, flatzones/1, flatzone/2]). --export([wipe/0, wipe/1, init/0, insert_all/1, get_all/1]). +-export([ + zones/1 + , rules/1 + , flatzone/2 + ]). + +-export([ + wipe/0 + , wipe/1 + , init/0 + , insert_all/1 + , get_all/1 + ]). -define(create(Record), @@ -50,17 +61,14 @@ get_all(Tab) when is_atom(Tab) -> {atomic, Ret}= mnesia:transaction(F), Ret. - -flatzones(TzName) -> - F = fun() -> - Q = qlc:q([Fz || Fz=#flatzone{tzname=N}<- mnesia:table(flatzone), N=:=TzName]), - 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) @@ -82,22 +90,6 @@ flatzone(Date, TzName) -> end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% WRITE - insertion/edit methods -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - - -insert_all(Records) -> - mnesia:transaction( - fun() -> - lists:foreach( - fun(R)-> mnesia:write(R) end, - Records) - end). - - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % ADMIN - initialization and administration methods @@ -143,5 +135,15 @@ create_tabs(E) -> ?debugVal(E). +insert_all(Records) -> + mnesia:transaction( + fun() -> + lists:foreach( + fun(R)-> mnesia:write(R) end, + Records) + end). + + + diff --git a/src/ezic_db_ets.erl b/src/ezic_db_ets.erl new file mode 100644 index 0000000..af9d85c --- /dev/null +++ b/src/ezic_db_ets.erl @@ -0,0 +1,101 @@ +-module(ezic_db_ets). +-include("include/ezic.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-behaviour(gen_server). + + +-export([ + zones/1 + , rules/1 + , flatzone/2 + , all/1 + ]). + + +-export([ + start_link/0 + , init/1 + , code_change/3 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + , terminate/2 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% PUBLIC API +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +zones(TzName) -> + gen_server:call(?MODULE, {zones, TzName}). + + +rules(TzName) -> + gen_server:call(?MODULE, {rules, TzName}). + + + +flatzone(Date, TzName) -> + gen_server:call(?MODULE, {flatzone, Date, TzName}). + + + +all(Tab) -> + gen_server:call(?MODULE, {all, Tab}). + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% GEN_SERVER +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init(_) -> + ezic_db_ets_admin:init(). + +%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% DB Lookup +%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +handle_call({zones, Name}, _, Ets) -> + Matches= ets:select(Ets, [{#zone{name=Name, _='_'}, [], ['$_']}]), + {reply, Matches, Ets}; +handle_call({rules, Name}, _, Ets) -> + Matches= ets:select(Ets, [{#rule{name=Name, _='_'}, [], ['$_']}]), + {reply, Matches, Ets}; +handle_call({all, Table}, _, Ets) -> + Matches= ets:lookup(Ets, Table), + {reply, Matches, Ets}; +handle_call({flatzone, Date, Name}, _, Ets) -> + Matches= ets:select(Ets, ezic_flatten:ms(Date, Name)), + {reply, Matches, Ets}; +handle_call(_, _, Ets) -> + {noreply, Ets}. + + +%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%% Other gen_server +%%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +code_change(_, State, _) -> + {ok, State}. + + +handle_cast(_, State) -> + {noreply, State}. + +handle_info(_, State) -> + {noreply, State}. + +terminate(_, State) -> + ok. diff --git a/src/ezic_db_ets_admin.erl b/src/ezic_db_ets_admin.erl new file mode 100644 index 0000000..219f800 --- /dev/null +++ b/src/ezic_db_ets_admin.erl @@ -0,0 +1,110 @@ +-module(ezic_db_ets_admin). +-include("include/ezic.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-define(TABS, [rule, zone, leap, flatzone]). +-define(DB_FILENAME, "db.e2f"). +-define(DETS_OPTS, []). +-define(ETS_OPTS, [named_table]). + + + + + +-export([ + init/0 + , wipe/0 + , insert_all/1 + , wipe/1 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% ADMIN API +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +% Entire DB +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +%% if dets tables exist, +%% load stored tables +%% else +%% create all ets tables as named tables +%% load data +init() -> + {ok, DbDir}= application:get_env(db_dir), + Filename= filename:join(DbDir, ?DB_FILENAME), + Tabfile= case db_sane(Filename) of + true -> load_tabfile(Filename); + false -> create_tables(Filename) + end, + {ok, Tabfile}. + + +% erases all data +wipe() -> + not_done. + + +% inserts all records into the appropriate db +insert_all(Record) -> + not_done. + + + +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +% Single Table +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +% erases date for one table +wipe(Tab) -> + not_done. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% PRIVATE +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%% -> 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) -> + Tab= ets:new(ezic_ets_db, [duplicate_bag]), + ets:insert(Tab, ezic_loader:load()), + + % save to disk + ets:tab2file(Tab, Filename), + + Tab. + + + +%% loads or creates the dets table. +load_tabfile(Filename) -> + {ok, Name}= ets:file2tab(Filename), + Name. + + + +%% takes a populated dets table and converts it to an ets table. +ets_from_dets(Dets) -> + Ets= ets:new(tzdb, ?ETS_OPTS), + dets:to_ets(Dets, Ets). + + + diff --git a/src/ezic_flatten.erl b/src/ezic_flatten.erl index a71050e..4ef4cc0 100644 --- a/src/ezic_flatten.erl +++ b/src/ezic_flatten.erl @@ -16,6 +16,7 @@ -export([ flatten/0 , contains_date/2 + , ms/2 ]). @@ -35,6 +36,41 @@ contains_date(FlatZone, Date) -> contains_date2(FlatZone, NDate). +%% create matchspec for the given date and name +%% date is expected to have a tztime with accurate flag +ms(Date, Name) -> + + M= #flatzone{tzname=Name + , wall_from='$1', wall_to='$2' + , std_from='$3', std_to='$4' + , utc_from='$5', utc_to='$6' + , _='_'}, + R= ['$_'], + + {D, #tztime{time=T, flag=F}}=Date, + + SDate= {D,T}, + DComp= {{{D},{T}}}, + + G= ms_guards(F, DComp), + + MS= [{M,G,R}], + + + + %% @todo move to eunit + %% TestFZ= #flatzone{tzname=Name + %% , wall_from=SDate, wall_to=SDate + %% , std_from=SDate, std_to=SDate + %% , utc_from=SDate, utc_to=SDate + %% }, + %% {ok, TestResult}= ets:test_ms(TestFZ, MS), + %% io:format("TestFZ: ~p~n", [TestFZ]), + %% io:format("MS: ~p~n", [MS]), + %% io:format("TestResult: ~p~n", [TestResult]), + + MS. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % PRIVATE @@ -293,3 +329,21 @@ maxyear_reached(Atom) when Atom=:=maximum; Atom=:=current -> true; % for some N, taking n > N => maximum > ?MAXYEAR maxyear_reached(Val) -> erlang:error(bad_year, Val). + + + + + +ms_guards(X, D) when X=:=u;X=:=g;X=:=z -> + ms_guards2(D, '$6', '$5'); +ms_guards(s, D) -> + ms_guards2(D, '$3', '$4'); +ms_guards(X, D) when X=:=w;X=:=undefined -> + ms_guards2(D, '$1', '$2'). + + +ms_guards2(D, From, To) -> + [ + {'=<', From, D} + , {'=<', D, To} + ]. diff --git a/src/ezic_loader.erl b/src/ezic_loader.erl index c3ceadd..f3412a3 100644 --- a/src/ezic_loader.erl +++ b/src/ezic_loader.erl @@ -1,27 +1,38 @@ -module(ezic_loader). -include("include/ezic.hrl"). --export([load/1]). +-export([load/0, load/1]). +-define(TZDIR, filename:join("priv", "tzdata")). + +load() -> + load(?TZDIR). + +%% returns all records, parsed from the tzdata files. load(File) -> {ok, Records} = case filelib:is_dir(File) of true -> {ok, parse_dir(File)}; - false -> case filelib:is_regular(File) of - true -> {ok, parse_file(File)}; - false -> erlang:error(badFile, File) - end + false -> erlang:error(not_done) + + %% case filelib:is_regular(File) of + %% true -> {ok, parse_file(File)}; + %% false -> erlang:error(badFile, File) + %% end end, - {ok, Zones, Rules, Leaps, Links} = ezic_record:separate(Records), - ezic_db:wipe(), - ezic_db:init(), + Records. - ezic_db:insert_all(Zones), - ezic_db:insert_all(Rules), - ezic_db:insert_all(Leaps), - ezic_db:insert_all(Links). + %% {ok, Zones, Rules, Leaps, Links} = ezic_record:separate(Records), + + %% ezic_db:wipe(), + %% ezic_db:init(), + + %% ezic_db:insert_all(Zones), + %% ezic_db:insert_all(Rules), + %% ezic_db:insert_all(Leaps), + %% ezic_db:insert_all(Links). diff --git a/src/ezic_sup.erl b/src/ezic_sup.erl new file mode 100644 index 0000000..fd9711f --- /dev/null +++ b/src/ezic_sup.erl @@ -0,0 +1,49 @@ +%%%------------------------------------------------------------------- +%%% File : ezic_sup.erl +%%% Author : aj +%%% Description : +%%% +%%% Created : 15 Nov 2010 by aj +%%%------------------------------------------------------------------- +-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(_) -> + supervisor:start_link(?MODULE, []). + +%%==================================================================== +%% 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(_) -> + EzicDb = {ezic_db_ets,{ezic_db_ets, start_link, []}, + permanent,2000,worker,[ezic_db_ets]}, + {ok,{{one_for_all,3,30}, [EzicDb]}}. + +%%==================================================================== +%% Internal functions +%%====================================================================