diff options
Diffstat (limited to 'learn-you-some-erlang/reminder/src/evserv.erl')
-rw-r--r-- | learn-you-some-erlang/reminder/src/evserv.erl | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/learn-you-some-erlang/reminder/src/evserv.erl b/learn-you-some-erlang/reminder/src/evserv.erl new file mode 100644 index 0000000..1087376 --- /dev/null +++ b/learn-you-some-erlang/reminder/src/evserv.erl @@ -0,0 +1,168 @@ +%% Event server +-module(evserv). +-compile(export_all). + +-record(state, {events, %% list of #event{} records + clients}). %% list of Pids + +-record(event, {name="", + description="", + pid, + timeout={{1970,1,1},{0,0,0}}}). + +%%% User Interface + +start() -> + register(?MODULE, Pid=spawn(?MODULE, init, [])), + Pid. + +start_link() -> + register(?MODULE, Pid=spawn_link(?MODULE, init, [])), + Pid. + +terminate() -> + ?MODULE ! shutdown. + +init() -> + %% Loading events from a static file could be done here. + %% You would need to pass an argument to init (maybe change the functions + %% start/0 and start_link/0 to start/1 and start_link/1) telling where the + %% resource to find the events is. Then load it from here. + %% Another option is to just pass the event straight to the server + %% through this function. + loop(#state{events=orddict:new(), + clients=orddict:new()}). + +subscribe(Pid) -> + Ref = erlang:monitor(process, whereis(?MODULE)), + ?MODULE ! {self(), Ref, {subscribe, Pid}}, + receive + {Ref, ok} -> + {ok, Ref}; + {'DOWN', Ref, process, _Pid, Reason} -> + {error, Reason} + after 5000 -> + {error, timeout} + end. + +add_event(Name, Description, TimeOut) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}}, + receive + {Ref, Msg} -> Msg + after 5000 -> + {error, timeout} + end. + +add_event2(Name, Description, TimeOut) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}}, + receive + {Ref, {error, Reason}} -> erlang:error(Reason); + {Ref, Msg} -> Msg + after 5000 -> + {error, timeout} + end. + +cancel(Name) -> + Ref = make_ref(), + ?MODULE ! {self(), Ref, {cancel, Name}}, + receive + {Ref, ok} -> ok + after 5000 -> + {error, timeout} + end. + +listen(Delay) -> + receive + M = {done, _Name, _Description} -> + [M | listen(0)] + after Delay*1000 -> + [] + end. + +%%% The Server itself + +loop(S=#state{}) -> + receive + {Pid, MsgRef, {subscribe, Client}} -> + Ref = erlang:monitor(process, Client), + NewClients = orddict:store(Ref, Client, S#state.clients), + Pid ! {MsgRef, ok}, + loop(S#state{clients=NewClients}); + {Pid, MsgRef, {add, Name, Description, TimeOut}} -> + case valid_datetime(TimeOut) of + true -> + EventPid = event:start_link(Name, TimeOut), + NewEvents = orddict:store(Name, + #event{name=Name, + description=Description, + pid=EventPid, + timeout=TimeOut}, + S#state.events), + Pid ! {MsgRef, ok}, + loop(S#state{events=NewEvents}); + false -> + Pid ! {MsgRef, {error, bad_timeout}}, + loop(S) + end; + {Pid, MsgRef, {cancel, Name}} -> + Events = case orddict:find(Name, S#state.events) of + {ok, E} -> + event:cancel(E#event.pid), + orddict:erase(Name, S#state.events); + error -> + S#state.events + end, + Pid ! {MsgRef, ok}, + loop(S#state{events=Events}); + {done, Name} -> + case orddict:find(Name, S#state.events) of + {ok, E} -> + send_to_clients({done, E#event.name, E#event.description}, + S#state.clients), + NewEvents = orddict:erase(Name, S#state.events), + loop(S#state{events=NewEvents}); + error -> + %% This may happen if we cancel an event and + %% it fires at the same time + loop(S) + end; + shutdown -> + exit(shutdown); + {'DOWN', Ref, process, _Pid, _Reason} -> + loop(S#state{clients=orddict:erase(Ref, S#state.clients)}); + code_change -> + ?MODULE:loop(S); + {Pid, debug} -> %% used as a hack to let me do some unit testing + Pid ! S, + loop(S); + Unknown -> + io:format("Unknown message: ~p~n",[Unknown]), + loop(S) + end. + + +%%% Internal Functions +send_to_clients(Msg, ClientDict) -> + orddict:map(fun(_Ref, Pid) -> Pid ! Msg end, ClientDict). + +valid_datetime({Date,Time}) -> + try + calendar:valid_date(Date) andalso valid_time(Time) + catch + error:function_clause -> %% not in {{Y,M,D},{H,Min,S}} format + false + end; +valid_datetime(_) -> + false. + +%% calendar has valid_date, but nothing for days. +%% This function is based on its interface. +%% Ugly, but ugh. +valid_time({H,M,S}) -> valid_time(H,M,S). + +valid_time(H,M,S) when H >= 0, H < 24, + M >= 0, M < 60, + S >= 0, S < 60 -> true; +valid_time(_,_,_) -> false. |