diff --git a/mod_message_log/src/mod_message_log.erl b/mod_message_log/src/mod_message_log.erl index 1cfed17..5e1492c 100644 --- a/mod_message_log/src/mod_message_log.erl +++ b/mod_message_log/src/mod_message_log.erl @@ -9,12 +9,22 @@ -author('holger@zedat.fu-berlin.de'). -behaviour(gen_mod). +-behaviour(gen_server). --export([start/2, - stop/1, - init/1, - perform_upgrade/1]). +%% gen_mod/supervisor callbacks. +-export([start_link/1, + start/2, + stop/1]). +%% gen_server callbacks. +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +%% ejabberd_hooks callbacks. -export([log_packet_send/3, log_packet_receive/4, log_packet_offline/3, @@ -27,7 +37,22 @@ -define(DEFAULT_FILENAME, <<"message.log">>). -define(FILE_MODES, [append, raw]). --record(config, {filename = ?DEFAULT_FILENAME, iodevice}). +-record(state, {filename = ?DEFAULT_FILENAME :: binary(), + iodevice :: io:device()}). + +-type direction() :: incoming | outgoing | offline. +-type state() :: #state{}. + +%% ------------------------------------------------------------------- +%% gen_mod/supervisor callbacks. +%% ------------------------------------------------------------------- + +-spec start_link(gen_mod:opts()) -> {ok, pid()} | ignore | {error, _}. + +start_link(Opts) -> + gen_server:start_link({local, ?PROCNAME}, ?MODULE, Opts, []). + +-spec start(binary(), gen_mod:opts()) -> {ok, _} | {ok, _, _} | {error, _}. start(Host, Opts) -> ejabberd_hooks:add(user_send_packet, Host, ?MODULE, @@ -36,17 +61,17 @@ start(Host, Opts) -> log_packet_receive, 42), ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, log_packet_offline, 42), - case whereis(?PROCNAME) of - undefined -> - ejabberd_hooks:add(reopen_log_hook, ?MODULE, reopen_log, 42), - Filename = gen_mod:get_opt(filename, Opts, fun(V) -> V end, - ?DEFAULT_FILENAME), - register(?PROCNAME, spawn(?MODULE, init, - [#config{filename = Filename}])), - ok; - _ -> - ok - end. + Spec = { + ?PROCNAME, + {?MODULE, start_link, [Opts]}, + permanent, + 3000, + worker, + [?MODULE] + }, + supervisor:start_child(ejabberd_sup, Spec). + +-spec stop(binary()) -> ok. stop(Host) -> ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, @@ -55,42 +80,102 @@ stop(Host) -> log_packet_receive, 42), ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, log_packet_offline, 42), - case whereis(?PROCNAME) of - undefined -> - ok; - _ -> - ejabberd_hooks:delete(reopen_log_hook, ?MODULE, reopen_log, 42), - gen_mod:get_module_proc(Host, ?PROCNAME) ! stop, - ok + case supervisor:terminate_child(ejabberd_sup, ?PROCNAME) of + ok -> + ok = supervisor:delete_child(ejabberd_sup, ?PROCNAME); + {error, not_found} -> + ok % We just run one process per node. end. -init(Config) -> - {ok, IoDevice} = file:open(Config#config.filename, ?FILE_MODES), - loop(Config#config{iodevice = IoDevice}). +%% ------------------------------------------------------------------- +%% gen_server callbacks. +%% ------------------------------------------------------------------- + +-spec init(gen_mod:opts()) -> {ok, state()}. + +init(Opts) -> + process_flag(trap_exit, true), + ejabberd_hooks:add(reopen_log_hook, ?MODULE, reopen_log, 42), + Filename = gen_mod:get_opt(filename, Opts, fun(V) -> V end, + ?DEFAULT_FILENAME), + {ok, IoDevice} = file:open(Filename, ?FILE_MODES), + {ok, #state{filename = Filename, iodevice = IoDevice}}. + +-spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}. + +handle_call(_Request, _From, State) -> + {noreply, State}. + +-spec handle_cast(_, state()) -> {noreply, state()}. + +handle_cast({message, Direction, From, To, Type}, #state{iodevice = IoDevice} = + State) -> + write_log(IoDevice, Direction, From, To, Type), + {noreply, State}; +handle_cast(reopen_log, #state{filename = Filename, iodevice = IoDevice} = + State) -> + ok = file:close(IoDevice), + {ok, NewIoDevice} = file:open(Filename, ?FILE_MODES), + {noreply, State#state{iodevice = NewIoDevice}}; +handle_cast(_Request, State) -> + {noreply, State}. + +-spec handle_info(timeout | _, state()) -> {noreply, state()}. + +handle_info(_Info, State) -> + {noreply, State}. + +-spec terminate(normal | shutdown | {shutdown, _} | _, _) -> any(). + +terminate(_Reason, State) -> + ejabberd_hooks:delete(reopen_log_hook, ?MODULE, reopen_log, 42), + ok = file:close(State#state.iodevice). + +-spec code_change({down, _} | _, state(), _) -> {ok, state()}. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% ------------------------------------------------------------------- +%% ejabberd_hooks callbacks. +%% ------------------------------------------------------------------- + +-spec log_packet_send(jid(), jid(), xmlel()) -> any(). log_packet_send(From, To, Packet) -> log_packet(outgoing, From, To, Packet). +-spec log_packet_receive(jid(), jid(), jid(), xmlel()) -> any(). + log_packet_receive(JID, From, _To, Packet) -> log_packet(incoming, From, JID, Packet). +-spec log_packet_offline(jid(), jid(), xmlel()) -> any(). + log_packet_offline(From, To, Packet) -> log_packet(offline, From, To, Packet). -reopen_log() -> - ?PROCNAME ! reopen. +-spec reopen_log() -> any(). +reopen_log() -> + gen_server:cast(?PROCNAME, reopen_log). + +%% ------------------------------------------------------------------- %% Internal functions. +%% ------------------------------------------------------------------- + +-spec log_packet(direction(), jid(), jid(), xmlel()) -> any(). log_packet(Direction, From, To, #xmlel{name = <<"message">>} = Packet) -> case xml:get_subtag(Packet, <<"body">>) of #xmlel{children = Body} when length(Body) > 0 -> Type = get_message_type(Packet), - ?PROCNAME ! {message, Direction, From, To, Type}; + gen_server:cast(?PROCNAME, {message, Direction, From, To, Type}); _ -> case is_carbon(Packet) of {true, OrigDirection} -> - ?PROCNAME ! {message, OrigDirection, From, To, carbon}; + gen_server:cast(?PROCNAME, {message, OrigDirection, From, To, + carbon}); false -> ok end @@ -98,6 +183,8 @@ log_packet(Direction, From, To, #xmlel{name = <<"message">>} = Packet) -> log_packet(_Direction, _From, _To, _Packet) -> ok. +-spec get_message_type(xmlel()) -> binary(). + get_message_type(#xmlel{attrs = Attrs}) -> case xml:get_attr_s(<<"type">>, Attrs) of <<"">> -> @@ -106,6 +193,8 @@ get_message_type(#xmlel{attrs = Attrs}) -> Type end. +-spec is_carbon(xmlel()) -> {true, direction()} | false. + is_carbon(Packet) -> {Direction, SubTag} = case {xml:get_subtag(Packet, <<"sent">>), xml:get_subtag(Packet, <<"received">>)} of @@ -128,20 +217,7 @@ is_carbon(Packet) -> false end. -loop(Config) -> - receive - {message, Direction, From, To, Type} -> - write_log(Config#config.iodevice, Direction, From, To, Type), - loop(Config); - reopen -> - file:close(Config#config.iodevice), - {ok, IoDevice} = file:open(Config#config.filename, ?FILE_MODES), - loop(Config#config{iodevice = IoDevice}); - upgrade -> - mod_message_log:perform_upgrade(Config); - stop -> - exit(normal) - end. +-spec write_log(io:device(), direction(), jid(), jid(), binary()) -> ok. write_log(IoDevice, Direction, From, To, Type) -> Date = format_date(calendar:local_time()), @@ -151,9 +227,8 @@ write_log(IoDevice, Direction, From, To, Type) -> jlib:jid_to_string(To)]), ok = file:write(IoDevice, [Record]). +-spec format_date(calendar:datetime()) -> io_lib:chars(). + format_date({{Year, Month, Day}, {Hour, Minute, Second}}) -> Format = "~B-~2..0B-~2..0B ~2..0B:~2..0B:~2..0B", io_lib:format(Format, [Year, Month, Day, Hour, Minute, Second]). - -perform_upgrade(Config) -> - loop(Config).