diff --git a/.gitignore b/.gitignore index 6784d9c..8b3c449 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +*.db *.beam .* diff --git a/chapter_10/UsrTabFile b/chapter_10/UsrTabFile deleted file mode 100644 index de37b4e..0000000 Binary files a/chapter_10/UsrTabFile and /dev/null differ diff --git a/chapter_12/gensrv.erl b/chapter_12/gensrv.erl new file mode 100644 index 0000000..f94d9ae --- /dev/null +++ b/chapter_12/gensrv.erl @@ -0,0 +1,34 @@ +-module(sample_gensrv). +-export([start/0, stop/0]). +-export([init/1, handle_cast/2, handle_call/3, terminate/2]). +-export([say_hello/1]). + +-behavior(gen_server). + +start() -> + Name = "Jack", + gen_server:start({local, ?MODULE}, ?MODULE, Name, []). + +init(Name) -> + io:format("Init OPT ~p~n", [Name]), + {ok, null}. + +say_hello(Name) -> + NewName = gen_server:call(?MODULE, {say_hello, Name}), + io:format("Hello ~p ~n", [NewName]). + +stop() -> + gen_server:cast(?MODULE, stop). + +handle_cast(stop, LoopData) -> + io:format("Stop OPT~n"), + {stop, normal, LoopData}. + +terminate(Reason, LoopData) -> + io:format("Terminate OPT ~p ~p ~n", [Reason, LoopData]). + +handle_call({say_hello, Name}, _From, LoopData) -> + io:format("Hello ~p ~n", [Name]), + {reply, "Tommy", LoopData}. + + diff --git a/chapter_12/usr-1.0/ebin/usr.app b/chapter_12/usr-1.0/ebin/usr.app new file mode 100644 index 0000000..5fadc61 --- /dev/null +++ b/chapter_12/usr-1.0/ebin/usr.app @@ -0,0 +1,9 @@ +{application, usr, [ + {description, "Mobile Services Database"}, + {vsn, "1.0"}, + {modules, [usr, usr_db, usr_sup, usr_app]}, + {registered, [usr, usr_sup]}, + {applications, [kernel, stdlib]}, + {env, [{dets_name, "usrDb.db"}]}, + {mod, {usr_app, []}}]}. + diff --git a/chapter_12/usr-1.0/include/usr.hrl b/chapter_12/usr-1.0/include/usr.hrl new file mode 100644 index 0000000..e86b6df --- /dev/null +++ b/chapter_12/usr-1.0/include/usr.hrl @@ -0,0 +1,16 @@ +%%% File: usr.hrl +%%% Description: Include file for user db + +-type(plan() :: prepay | postpay). +-type(status() :: enabled | disabled). +-type(service() :: atom()). + +-record(usr, + { + msisdn ::integer(), + id ::integer(), + status = enabled ::status(), + plan ::plan(), + services = [] ::[service()] + } +). diff --git a/chapter_12/usr-1.0/src/usr.erl b/chapter_12/usr-1.0/src/usr.erl new file mode 100644 index 0000000..24848d5 --- /dev/null +++ b/chapter_12/usr-1.0/src/usr.erl @@ -0,0 +1,107 @@ +-module(usr). +-export([start_link/0, start_link/1, stop/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-export([add_usr/3, delete_usr/1, set_service/3, set_status/2, delete_disabled/0, lookup_id/1]). +-export([lookup_msisdn/1, service_flag/2]). + +-behavior(gen_server). + +-include("usr.hrl"). +-define(TIMEOUT, 30000). + +start_link() -> + {ok, FileName} = application:get_env(dets_name), + start_link(FileName). + +start_link(FileName) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, FileName, []). + +stop() -> + gen_server:cast(?MODULE, stop). + +init(FileName) -> + usr_db:create_tables(FileName), + usr_db:restore_backup(), + {ok, null}. + +terminate(Reason, LoopData) -> + usr_db:close_tables(). + +add_usr(PhoneNum, CustId, Plan) when Plan == prepay; Plan == postpay -> + gen_server:call(?MODULE, {add_usr, PhoneNum, CustId, Plan}). + +delete_usr(CustId) -> + gen_server:call(?MODULE, {delete_usr, CustId}). + +set_service(CustId, Service, Flag) when Flag == true; Flag == false -> + gen_server:call(?MODULE, {set_service, CustId, Service, Flag}). + +set_status(CustId, Status) when Status == enabled; Status == disabled -> + gen_server:call(?MODULE, {set_status, CustId, Status}). + +delete_disabled() -> + gen_server:call(?MODULE, delete_disabled). + +lookup_id(CustId) -> + usr_db:lookup_id(CustId). + +lookup_msisdn(PhoneNo) -> + usr_db:lookup_msisdn(PhoneNo). + +service_flag(PhoneNo, Service) -> + case usr_db:lookup_msisdn(PhoneNo) of + {ok, #usr{services = Services, status = enabled}} -> + lists:member(Service, Services); + {ok, #usr{status=disabled}} -> + {error, disabled}; + {error, Reason} -> + {error, Reason} + end. + +handle_call({add_usr, PhoneNo, CustId, Plan}, _From, LoopData) -> + Reply = usr_db:add_usr( + #usr{ + msisdn=PhoneNo, + id=CustId, + plan=Plan + } + ), + {reply, Reply, LoopData}; + +handle_call({delete_usr, CustId}, _From, LoopData) -> + Reply = usr_db:delete_usr(CustId), + {reply, Reply, LoopData}; + +handle_call({set_service, CustId, Service, Flag}, _From, LoopData) -> + Reply = case usr_db:lookup_id(CustId) of + {ok, Usr} -> + Services = lists:delete(Service, Usr#usr.services), + NewServices = case Flag of + true -> [Service | Services]; + false -> Services + end, + usr_db:update_usr(Usr#usr{services = NewServices}); + {error, instance} -> + {error, instance} + end, + {reply, Reply, LoopData}; + +handle_call({set_status, CustId, Status}, _From, LoopData) -> + Reply = case usr_db:lookup_id(CustId) of + {ok, Usr} -> + usr_db:update_usr(Usr#usr{status = Status}); + {error, instance} -> + {error, instance} + end, + {reply, Reply, LoopData}; + +handle_call({delete_disabled}, _From, LoopData) -> + Reply = usr_db:delete_disabled(), + {reply, Reply, LoopData}. + +handle_cast(stop, LoopData) -> + {stop, normal, LoopData}. + +handle_info(Msg, LoopData) -> + io:format("Recive Not OPT Message ~p~n", [Msg]), + {noreply, LoopData}. diff --git a/chapter_12/usr-1.0/src/usr_app.erl b/chapter_12/usr-1.0/src/usr_app.erl new file mode 100644 index 0000000..53e51c8 --- /dev/null +++ b/chapter_12/usr-1.0/src/usr_app.erl @@ -0,0 +1,10 @@ +-module(usr_app). +-behavior(application). + +-export([start/2, stop/1]). + +start(_Type, StartArgs) -> + usr_sup:start_link(). + +stop(_State) -> + ok. diff --git a/chapter_12/usr-1.0/src/usr_db.erl b/chapter_12/usr-1.0/src/usr_db.erl new file mode 100644 index 0000000..0547459 --- /dev/null +++ b/chapter_12/usr-1.0/src/usr_db.erl @@ -0,0 +1,89 @@ +%%% File: usr_db.erl +%%% Description: Database API for subscriber DB + +-module(usr_db). +-include("usr.hrl"). + +-export([create_tables/1, close_tables/0]). +-export([add_usr/1, update_usr/1]). +-export([lookup_id/1, lookup_msisdn/1]). +-export([restore_backup/0]). + +-export([delete_disabled/0, delete_usr/1]). + +create_tables(FileName) -> + ets:new(usrRam, [named_table, {keypos, #usr.msisdn}]), + ets:new(usrIndex, [named_table]), + dets:open_file(usrDisk, [{file, FileName}, {keypos, #usr.msisdn}]). + +close_tables() -> + ets:delete(usrRam), + ets:delete(usrIndex), + dets:close(usrDisk). + +add_usr(#usr{msisdn=PhoneNo, id=CustId} = Usr) -> + ets:insert(usrIndex, {CustId, PhoneNo}), + update_usr(Usr). + +update_usr(Usr) -> + ets:insert(usrRam, Usr), + dets:insert(usrDisk, Usr), + ok. + +lookup_id(CustId) -> + case get_index(CustId) of + {ok, PhoneNo} -> lookup_msisdn(PhoneNo); + {error, instance} -> {error, instance} + end. + +lookup_msisdn(PhoneNo) -> + case ets:lookup(usrRam, PhoneNo) of + [Usr] -> {ok, Usr}; + [] -> {error, instance} + end. + +get_index(CustId) -> + case ets:lookup(usrIndex, CustId) of + [{CustId, PhoneNo}] -> {ok, PhoneNo}; + [] -> {error, instance} + end. + +restore_backup() -> + Insert = fun(#usr{msisdn=PhoneNo, id=Id} = Usr) -> + ets:insert(usrRam, Usr), + ets:insert(usrIndex, {Id, PhoneNo}), + continue + end, + dets:traverse(usrDisk, Insert). + +delete_disabled() -> + ets:safe_fixtable(usrRam, true), + catch loop_delete_disabled(ets:first(usrRam)), + ets:safe_fixtable(usrRam, false), + ok. + +delete_usr(CustId) -> + case get_index(CustId) of + {ok, PhoneNo} -> + ets:safe_fixtable(usrRam, true), + ets:delete(usrRam, PhoneNo), + ets:safe_fixtable(usrRam, false), + ets:safe_fixtable(usrIndex, true), + ets:delete(usrIndex, CustId), + ets:safe_fixtable(usrIndex, false), + + dets:delete(usrDisk, PhoneNo); + {error, instance} -> {error, instance} + end. + +loop_delete_disabled('$end_of_table') -> + ok; + +loop_delete_disabled(PhoneNo) -> + case ets:lookup(usrRam, PhoneNo) of + [#usr{status=disabled, id=CustId}] -> + delete_usr(CustId); + _ -> + ok + end, + loop_delete_disabled(ets:next(usrRam, PhoneNo)). diff --git a/chapter_12/usr-1.0/src/usr_sup.erl b/chapter_12/usr-1.0/src/usr_sup.erl new file mode 100644 index 0000000..83868fb --- /dev/null +++ b/chapter_12/usr-1.0/src/usr_sup.erl @@ -0,0 +1,16 @@ +-module(usr_sup). +-behavior(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, null). + +init(_) -> + UsrChild = { + usr, {usr, start_link, []}, + permanent, 2000, worker, [usr, usr_db]}, + + {ok, {{one_for_all, 2, 2}, [UsrChild]}}. + diff --git a/chapter_12/usr.erl b/chapter_12/usr.erl new file mode 100644 index 0000000..24848d5 --- /dev/null +++ b/chapter_12/usr.erl @@ -0,0 +1,107 @@ +-module(usr). +-export([start_link/0, start_link/1, stop/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]). +-export([add_usr/3, delete_usr/1, set_service/3, set_status/2, delete_disabled/0, lookup_id/1]). +-export([lookup_msisdn/1, service_flag/2]). + +-behavior(gen_server). + +-include("usr.hrl"). +-define(TIMEOUT, 30000). + +start_link() -> + {ok, FileName} = application:get_env(dets_name), + start_link(FileName). + +start_link(FileName) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, FileName, []). + +stop() -> + gen_server:cast(?MODULE, stop). + +init(FileName) -> + usr_db:create_tables(FileName), + usr_db:restore_backup(), + {ok, null}. + +terminate(Reason, LoopData) -> + usr_db:close_tables(). + +add_usr(PhoneNum, CustId, Plan) when Plan == prepay; Plan == postpay -> + gen_server:call(?MODULE, {add_usr, PhoneNum, CustId, Plan}). + +delete_usr(CustId) -> + gen_server:call(?MODULE, {delete_usr, CustId}). + +set_service(CustId, Service, Flag) when Flag == true; Flag == false -> + gen_server:call(?MODULE, {set_service, CustId, Service, Flag}). + +set_status(CustId, Status) when Status == enabled; Status == disabled -> + gen_server:call(?MODULE, {set_status, CustId, Status}). + +delete_disabled() -> + gen_server:call(?MODULE, delete_disabled). + +lookup_id(CustId) -> + usr_db:lookup_id(CustId). + +lookup_msisdn(PhoneNo) -> + usr_db:lookup_msisdn(PhoneNo). + +service_flag(PhoneNo, Service) -> + case usr_db:lookup_msisdn(PhoneNo) of + {ok, #usr{services = Services, status = enabled}} -> + lists:member(Service, Services); + {ok, #usr{status=disabled}} -> + {error, disabled}; + {error, Reason} -> + {error, Reason} + end. + +handle_call({add_usr, PhoneNo, CustId, Plan}, _From, LoopData) -> + Reply = usr_db:add_usr( + #usr{ + msisdn=PhoneNo, + id=CustId, + plan=Plan + } + ), + {reply, Reply, LoopData}; + +handle_call({delete_usr, CustId}, _From, LoopData) -> + Reply = usr_db:delete_usr(CustId), + {reply, Reply, LoopData}; + +handle_call({set_service, CustId, Service, Flag}, _From, LoopData) -> + Reply = case usr_db:lookup_id(CustId) of + {ok, Usr} -> + Services = lists:delete(Service, Usr#usr.services), + NewServices = case Flag of + true -> [Service | Services]; + false -> Services + end, + usr_db:update_usr(Usr#usr{services = NewServices}); + {error, instance} -> + {error, instance} + end, + {reply, Reply, LoopData}; + +handle_call({set_status, CustId, Status}, _From, LoopData) -> + Reply = case usr_db:lookup_id(CustId) of + {ok, Usr} -> + usr_db:update_usr(Usr#usr{status = Status}); + {error, instance} -> + {error, instance} + end, + {reply, Reply, LoopData}; + +handle_call({delete_disabled}, _From, LoopData) -> + Reply = usr_db:delete_disabled(), + {reply, Reply, LoopData}. + +handle_cast(stop, LoopData) -> + {stop, normal, LoopData}. + +handle_info(Msg, LoopData) -> + io:format("Recive Not OPT Message ~p~n", [Msg]), + {noreply, LoopData}. diff --git a/chapter_12/usr.hrl b/chapter_12/usr.hrl new file mode 100644 index 0000000..e86b6df --- /dev/null +++ b/chapter_12/usr.hrl @@ -0,0 +1,16 @@ +%%% File: usr.hrl +%%% Description: Include file for user db + +-type(plan() :: prepay | postpay). +-type(status() :: enabled | disabled). +-type(service() :: atom()). + +-record(usr, + { + msisdn ::integer(), + id ::integer(), + status = enabled ::status(), + plan ::plan(), + services = [] ::[service()] + } +). diff --git a/chapter_12/usr_app.erl b/chapter_12/usr_app.erl new file mode 100644 index 0000000..53e51c8 --- /dev/null +++ b/chapter_12/usr_app.erl @@ -0,0 +1,10 @@ +-module(usr_app). +-behavior(application). + +-export([start/2, stop/1]). + +start(_Type, StartArgs) -> + usr_sup:start_link(). + +stop(_State) -> + ok. diff --git a/chapter_12/usr_db.erl b/chapter_12/usr_db.erl new file mode 100644 index 0000000..0547459 --- /dev/null +++ b/chapter_12/usr_db.erl @@ -0,0 +1,89 @@ +%%% File: usr_db.erl +%%% Description: Database API for subscriber DB + +-module(usr_db). +-include("usr.hrl"). + +-export([create_tables/1, close_tables/0]). +-export([add_usr/1, update_usr/1]). +-export([lookup_id/1, lookup_msisdn/1]). +-export([restore_backup/0]). + +-export([delete_disabled/0, delete_usr/1]). + +create_tables(FileName) -> + ets:new(usrRam, [named_table, {keypos, #usr.msisdn}]), + ets:new(usrIndex, [named_table]), + dets:open_file(usrDisk, [{file, FileName}, {keypos, #usr.msisdn}]). + +close_tables() -> + ets:delete(usrRam), + ets:delete(usrIndex), + dets:close(usrDisk). + +add_usr(#usr{msisdn=PhoneNo, id=CustId} = Usr) -> + ets:insert(usrIndex, {CustId, PhoneNo}), + update_usr(Usr). + +update_usr(Usr) -> + ets:insert(usrRam, Usr), + dets:insert(usrDisk, Usr), + ok. + +lookup_id(CustId) -> + case get_index(CustId) of + {ok, PhoneNo} -> lookup_msisdn(PhoneNo); + {error, instance} -> {error, instance} + end. + +lookup_msisdn(PhoneNo) -> + case ets:lookup(usrRam, PhoneNo) of + [Usr] -> {ok, Usr}; + [] -> {error, instance} + end. + +get_index(CustId) -> + case ets:lookup(usrIndex, CustId) of + [{CustId, PhoneNo}] -> {ok, PhoneNo}; + [] -> {error, instance} + end. + +restore_backup() -> + Insert = fun(#usr{msisdn=PhoneNo, id=Id} = Usr) -> + ets:insert(usrRam, Usr), + ets:insert(usrIndex, {Id, PhoneNo}), + continue + end, + dets:traverse(usrDisk, Insert). + +delete_disabled() -> + ets:safe_fixtable(usrRam, true), + catch loop_delete_disabled(ets:first(usrRam)), + ets:safe_fixtable(usrRam, false), + ok. + +delete_usr(CustId) -> + case get_index(CustId) of + {ok, PhoneNo} -> + ets:safe_fixtable(usrRam, true), + ets:delete(usrRam, PhoneNo), + ets:safe_fixtable(usrRam, false), + ets:safe_fixtable(usrIndex, true), + ets:delete(usrIndex, CustId), + ets:safe_fixtable(usrIndex, false), + + dets:delete(usrDisk, PhoneNo); + {error, instance} -> {error, instance} + end. + +loop_delete_disabled('$end_of_table') -> + ok; + +loop_delete_disabled(PhoneNo) -> + case ets:lookup(usrRam, PhoneNo) of + [#usr{status=disabled, id=CustId}] -> + delete_usr(CustId); + _ -> + ok + end, + loop_delete_disabled(ets:next(usrRam, PhoneNo)). diff --git a/chapter_12/usr_sup.erl b/chapter_12/usr_sup.erl new file mode 100644 index 0000000..83868fb --- /dev/null +++ b/chapter_12/usr_sup.erl @@ -0,0 +1,16 @@ +-module(usr_sup). +-behavior(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, null). + +init(_) -> + UsrChild = { + usr, {usr, start_link, []}, + permanent, 2000, worker, [usr, usr_db]}, + + {ok, {{one_for_all, 2, 2}, [UsrChild]}}. +