-module(advanced_event). -behaviour(gen_server). -export([start_link/0, start_link/1, notify/2, sync_notify/2, add_handler/3, delete_handler/3 %, call/3, call/4 ]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(handler, {module :: atom(), id, state}). -callback init(InitArgs :: term()) -> {ok, State :: term()}. -callback handle_event(Event :: term(), State :: term()) -> {ok, NewState :: term()} | {handled, NewState :: term()} | handled_and_remove | remove_handler. -callback handle_call(Request :: term(), State :: term()) -> {ok, Reply :: term(), NewState :: term()} | {remove_handler, Reply :: term()}. -callback handle_info(Info :: term(), State :: term()) -> {ok, NewState :: term()} | remove_handler. -callback terminate(Args :: (term() | {stop, Reason :: term()} | stop | remove_handler | {error, {'EXIT', Reason :: term()}} | {error, term()}), State :: term()) -> term(). -callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), Extra :: term()) -> {ok, NewState :: term()}. -type handler() :: atom() | {atom(), term()}. -type emgr_name() :: {'local', atom()} | {'global', atom()}. -type emgr_ref() :: atom() | {atom(), atom()} | {'global', atom()} | pid(). -type start_ret() :: {'ok', pid()} | {'error', term()}. -define(NO_CALLBACK, 'no callback module'). %-spec start_link() -> start_ret(). start_link() -> gen_server:start_link(?MODULE, [], []). %-spec start_link(emgr_name()) -> start_ret(). start_link(Name) -> gen_server:start_link(Name, ?MODULE, [], []). init(_Args) -> process_flag(trap_exit, true), {ok, []}. -spec add_handler(emgr_ref(), handler(), term()) -> term(). add_handler(Manager, Handler, Args) -> gen_server:call(Manager, {add_handler, Handler, Args}). -spec delete_handler(emgr_ref(), handler(), term()) -> term(). delete_handler(Manager, Handler, Args) -> gen_server:call(Manager, {delete_handler, Handler, Args}). -spec notify(emgr_ref(), term()) -> 'ok'. notify(Manager, Event) -> gen_server:cast(Manager, {notify, Event}). -spec sync_notify(emgr_ref(), term()) -> 'ok'. sync_notify(Manager, Event) -> gen_server:call(Manager, {sync_notify, Event}). %-spec call(emgr_ref(), handler(), term()) -> term(). %call(M, Handler, Query) -> call1(M, Handler, Query). %-spec call(emgr_ref(), handler(), term(), timeout()) -> term(). %call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout). -spec handle_call(term(), {pid(), term()}, list()) -> {reply, term(), list()}. handle_call({add_handler, Handler, Args}, _From, Handlers) -> {Module, Id} = case Handler of {_Mod, _Id} -> Handler; Mod -> {Mod, undefined} end, case catch Module:init(Args) of {ok, State} -> {reply,ok,[#handler{module=Module,id=Id,state=State}|Handlers]}; Other -> {reply,Other,Handlers} end; handle_call({delete_handler, Handler, Args}, _From, Handlers) -> {Module, Id} = case Handler of {_Mod, _Id} -> Handler; Mod -> {Mod, undefined} end, case remove_from_list(Module, Id, Handlers) of error -> {reply,{error,module_not_found},Handlers}; {H,Hs} -> case catch (H#handler.module):terminate(Args, (H#handler.state)) of Result -> {reply,Result,Hs} end end; handle_call({sync_notify,Event}, _From, Handlers) -> Handlers1 = call_handlers(Event, Handlers), {reply,ok,Handlers1}. handle_cast({notify,Event}, Handlers) -> Handlers1 = call_handlers(Event, Handlers), {noreply,Handlers1}. handle_info(_Msg, Handlers) -> {noreply,Handlers}. remove_from_list(Module, Id, [Handler|Handlers]) -> case Handler of #handler{module=Module,id=Id} -> {Handler,Handlers}; _ -> case remove_from_list(Module, id, Handlers) of {H,Hs} -> {H,[Handler|Hs]}; error -> error end end; remove_from_list(_, _, []) -> error. call_handlers(Event, [Handler|Handlers]) -> case catch (Handler#handler.module):handle_event(Event, Handler#handler.state) of {ok, State} -> Rest = call_handlers(Event, Handlers), [Handler#handler{state=State}|Rest]; {handled, State} -> [Handler#handler{state=State}|Handlers]; remove_handler -> (Handler#handler.module):terminate(remove_handler, Handler#handler.state), call_handlers(Event, Handlers); handled_and_remove -> (Handler#handler.module):terminate(remove_handler, Handler#handler.state), [] end; call_handlers(_, []) -> []. terminate(Reason, [Handler|Handlers]) -> (Handler#handler.module):terminate({stop, Reason}, (Handler#handler.state)), terminate(Reason, Handlers); terminate(_Reason, []) -> ok. code_change(_OldVersion, Config, _Extra) -> {ok, Config}.