diff options
Diffstat (limited to 'src/ejabberd_hooks.erl')
-rw-r--r-- | src/ejabberd_hooks.erl | 141 |
1 files changed, 134 insertions, 7 deletions
diff --git a/src/ejabberd_hooks.erl b/src/ejabberd_hooks.erl index 7fc2f07f0..bb71f14e1 100644 --- a/src/ejabberd_hooks.erl +++ b/src/ejabberd_hooks.erl @@ -5,7 +5,7 @@ %%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2008 Process-one +%%% ejabberd, Copyright (C) 2002-2009 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -16,7 +16,7 @@ %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. -%%% +%%% %%% You should have received a copy of the GNU General Public License %%% along with this program; if not, write to the Free Software %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA @@ -31,12 +31,18 @@ %% External exports -export([start_link/0, + add/3, add/4, + add_dist/5, + delete/3, delete/4, + delete_dist/5, run/2, run_fold/3, add/5, + add_dist/6, delete/5, + delete_dist/6, run/3, run_fold/4]). @@ -50,6 +56,9 @@ -include("ejabberd.hrl"). +%% Timeout of 5 seconds in calls to distributed hooks +-define(TIMEOUT_DISTRIBUTED_HOOK, 5000). + -record(state, {}). %%%---------------------------------------------------------------------- @@ -58,18 +67,55 @@ start_link() -> gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []). +%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok +%% @doc See add/4. +add(Hook, Function, Seq) when is_function(Function) -> + add(Hook, global, undefined, Function, Seq). + +add(Hook, Host, Function, Seq) when is_function(Function) -> + add(Hook, Host, undefined, Function, Seq); + +%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok +%% @doc Add a module and function to this hook. +%% The integer sequence is used to sort the calls: low number is called before high number. add(Hook, Module, Function, Seq) -> add(Hook, global, Module, Function, Seq). add(Hook, Host, Module, Function, Seq) -> gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}). +add_dist(Hook, Node, Module, Function, Seq) -> + gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}). + +add_dist(Hook, Host, Node, Module, Function, Seq) -> + gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}). + +%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok +%% @doc See del/4. +delete(Hook, Function, Seq) when is_function(Function) -> + delete(Hook, global, undefined, Function, Seq). + +delete(Hook, Host, Function, Seq) when is_function(Function) -> + delete(Hook, Host, undefined, Function, Seq); + +%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok +%% @doc Delete a module and function from this hook. +%% It is important to indicate exactly the same information than when the call was added. delete(Hook, Module, Function, Seq) -> delete(Hook, global, Module, Function, Seq). delete(Hook, Host, Module, Function, Seq) -> gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}). +delete_dist(Hook, Node, Module, Function, Seq) -> + delete_dist(Hook, global, Node, Module, Function, Seq). + +delete_dist(Hook, Host, Node, Module, Function, Seq) -> + gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}). + +%% @spec (Hook::atom(), Args) -> ok +%% @doc Run the calls of this hook in order, don't care about function results. +%% If a call returns stop, no more calls are performed. run(Hook, Args) -> run(Hook, global, Args). @@ -81,6 +127,12 @@ run(Hook, Host, Args) -> ok end. +%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal +%% @doc Run the calls of this hook in order. +%% The arguments passed to the function are: [Val | Args]. +%% The result of a call is used as Val for the next call. +%% If a call returns 'stop', no more calls are performed and 'stopped' is returned. +%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned. run_fold(Hook, Val, Args) -> run_fold(Hook, global, Val, Args). @@ -134,6 +186,24 @@ handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) -> ok end, {reply, Reply, State}; +handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) -> + Reply = case ets:lookup(hooks, {Hook, Host}) of + [{_, Ls}] -> + El = {Seq, Node, Module, Function}, + case lists:member(El, Ls) of + true -> + ok; + false -> + NewLs = lists:merge(Ls, [El]), + ets:insert(hooks, {{Hook, Host}, NewLs}), + ok + end; + [] -> + NewLs = [{Seq, Node, Module, Function}], + ets:insert(hooks, {{Hook, Host}, NewLs}), + ok + end, + {reply, Reply, State}; handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) -> Reply = case ets:lookup(hooks, {Hook, Host}) of [{_, Ls}] -> @@ -144,6 +214,16 @@ handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) -> ok end, {reply, Reply, State}; +handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) -> + Reply = case ets:lookup(hooks, {Hook, Host}) of + [{_, Ls}] -> + NewLs = lists:delete({Seq, Node, Module, Function}, Ls), + ets:insert(hooks, {{Hook, Host}, NewLs}), + ok; + [] -> + ok + end, + {reply, Reply, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. @@ -184,8 +264,32 @@ code_change(_OldVsn, State, _Extra) -> run1([], _Hook, _Args) -> ok; +run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) -> + case rpc:call(Node, Module, Function, Args, ?TIMEOUT_DISTRIBUTED_HOOK) of + timeout -> + ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p", + [Node, {Hook, Args}]), + run1(Ls, Hook, Args); + {badrpc, Reason} -> + ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p", + [Node, Reason, {Hook, Args}]), + run1(Ls, Hook, Args); + stop -> + ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" + "Stop.", [self(), node(), Node]), % debug code + ok; + Res -> + ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" + "The response is:~n~s", [self(), node(), Node, Res]), % debug code + run1(Ls, Hook, Args) + end; run1([{_Seq, Module, Function} | Ls], Hook, Args) -> - case catch apply(Module, Function, Args) of + Res = if is_function(Function) -> + catch apply(Function, Args); + true -> + catch apply(Module, Function, Args) + end, + case Res of {'EXIT', Reason} -> ?ERROR_MSG("~p~nrunning hook: ~p", [Reason, {Hook, Args}]), @@ -199,8 +303,34 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) -> run_fold1([], _Hook, Val, _Args) -> Val; +run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) -> + case rpc:call(Node, Module, Function, [Val | Args], ?TIMEOUT_DISTRIBUTED_HOOK) of + {badrpc, Reason} -> + ?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p", + [Node, Reason, {Hook, Args}]), + run_fold1(Ls, Hook, Val, Args); + timeout -> + ?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p", + [Node, {Hook, Args}]), + run_fold1(Ls, Hook, Val, Args); + stop -> + stopped; + {stop, NewVal} -> + ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" + "Stop, and the NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code + NewVal; + NewVal -> + ?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n" + "The NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code + run_fold1(Ls, Hook, NewVal, Args) + end; run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) -> - case catch apply(Module, Function, [Val | Args]) of + Res = if is_function(Function) -> + catch apply(Function, [Val | Args]); + true -> + catch apply(Module, Function, [Val | Args]) + end, + case Res of {'EXIT', Reason} -> ?ERROR_MSG("~p~nrunning hook: ~p", [Reason, {Hook, Args}]), @@ -212,6 +342,3 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) -> NewVal -> run_fold1(Ls, Hook, NewVal, Args) end. - - - |