Erlang - 终止函数问题

时间:2015-10-06 23:11:56

标签: erlang ejabberd mongoose-im

我正在使用Erlang创建一个模块,我有三个选项,分别是添加,编辑和删除。

我可以在日志中看到在init方法中调用了添加函数,但是我找不到与删除消息相关的任何内容。我想这是因为"终止方法"没有被调用,但我不确定我的功能是否正确,或者我是否在正确的位置调用编辑和删除功能。

这是我的代码:

-module(mod_msgschedule).

-behaviour(gen_server).
-behaviour(gen_mod).

-include("ejabberd.hrl").
-include("jlib.hrl").

%% gen_mod handlers
-export([start/2,stop/1]).

%% gen_server handlers
-export([init/1,handle_info/2, handle_call/3, handle_cast/2, terminate/2, code_change/3]).

%% Hook handlers
-export([
    remove_delayed_message/2,
    add_delayed_message/7,
    edit_delayed_message/7,
    search_delayed_messages/2,
    check_packet/1,
    process_sm_iq/3]).

-export([start_link/2]).

-define(INTERVAL, timer:minutes(5)).
-define(PROCNAME, ?MODULE).
-define(NS_DELAYMSG, "delayed-msg").
-record(state,{host :: binary()}).

-record(delayed_msg, {id,from,to,server,scheduledTimestamp,packet,relativeId}).

%%--------------------------------------------------------------------
%% gen_mod callbacks
%%--------------------------------------------------------------------
start(VHost, Opts) ->
    ejabberd_loglevel:set_custom(?MODULE, 5),
    ?DEBUG("Start Module", []),
    Proc = gen_mod:get_module_proc(VHost,?PROCNAME),
    ChildSpec = {Proc, {?MODULE, start_link, [VHost,Opts]},
                 transient, 1000, worker, [?MODULE]},
    supervisor:start_child(ejabberd_sup, ChildSpec).

stop(VHost) ->
    Proc = gen_mod:get_module_proc(VHost,?PROCNAME),
    supervisor:terminate_child(ejabberd_sup,Proc),
    supervisor:delete_child(ejabberd_sup,Proc).


start_link(VHost, Opts) ->
    Proc = gen_mod:get_module_proc(VHost, ?PROCNAME),
    gen_server:start_link({local, Proc}, ?MODULE, [VHost, Opts],[]).

init([VHost, Opts]) ->
    ?DEBUG("Start Timer", []),
    process_flag(trap_exit, true),
    ejabberd_hooks:add(filter_local_packet, VHost, ?MODULE, check_packet, 10),

    timer:send_interval(?INTERVAL, self(), tick),

    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
    gen_iq_handler:add_iq_handler(ejabberd_sm, VHost, ?NS_DELAYMSG,?MODULE, process_sm_iq, IQDisc),
    %%gen_iq_handler:add_iq_handler(ejabberd_sm, VHost, ?NS_VCARD,
    %%                              ?MODULE,process_sm_iq, IQDisc),
    %%gen_iq_handler:add_iq_handler(ejabberd_local, VHost, ?NS_VCARD,
    %%                              ?MODULE,process_local_iq, IQDisc),

    %%DirectoryHost = gen_mod:get_opt_host(VHost, Opts, "vjud.@HOST@"),
    %%Search = gen_mod:get_opt(search, Opts, true),

    %%case Search of
    %%    true ->
    %%        ejabberd_router:register_route(DirectoryHost);
    %%    _ ->
    %%        ok
    %%end,
    {ok,#state{host=VHost}}.

terminate(_Reason, State) ->
    VHost = State#state.host,
    %%case State#state.search of
    %%    true ->
    %%        ejabberd_router:unregister_route(State#state.directory_host);
    %%    _ ->
    %%        ok
    %%end,
    ejabberd_hooks:delete(filter_local_packet, VHost,?MODULE, check_packet, 10),
    gen_iq_handler:remove_iq_handler(ejabberd_local, VHost, ?NS_DELAYMSG).
    %%gen_iq_handler:remove_iq_handler(ejabberd_local, VHost, ?NS_VCARD),
    %%gen_iq_handler:remove_iq_handler(ejabberd_sm, VHost, ?NS_VCARD),
    %%ejabberd_hooks:delete(host_config_update, VHost, ?MODULE, config_change, 50),
    %%ejabberd_hooks:delete(disco_local_features, VHost, ?MODULE, get_local_features, 50).

handle_call(get_state, _From, State) ->
    {reply, {ok, State}, State};
handle_call(stop,_From,State) ->
    {stop, normal, ok, State};
handle_call(_Request, _From,State) ->
    {reply, bad_request, State}.

%% this function is called whenever gen_server receives a 'tick' message
handle_info(tick, State) ->
    State2 = send_pending_delayed_messages(State),
    {noreply, State2};

handle_info(_Info, State) ->
    {noreply, State}.

handle_cast(_Request, State) ->
    {noreply, State}.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% this function is called by handle_info/2 when tick message is received
send_pending_delayed_messages(State) ->
    LServer = jlib:nameprep(State#state.host),
    Now = erlang:now(),
    CurrentTimestamp = now_to_microseconds(Now),
    ?DEBUG("Timer Triggered!! ~p", [CurrentTimestamp]),
    case search_delayed_messages(LServer,CurrentTimestamp) of
        {ok, DelayedMessages} ->
            lists:foreach(fun(DelayedMessage) ->
                  route_scheduled_message(LServer, DelayedMessage),
                  remove_delayed_message(LServer, DelayedMessage)
              end, DelayedMessages);
        {error, Reason} ->
            ?DEBUG("Select command: ~p", [{error, Reason}])
    end,
    State.

route_scheduled_message(Server, #delayed_msg{from=From, to=To, packet=Packet} = DelayedMessage) ->
    NewPacket = resend_scheduled_message_packet(Server,DelayedMessage),
    ejabberd_router:route(From, To, NewPacket).

resend_scheduled_message_packet(Server,
        #delayed_msg{scheduledTimestamp=TimeStamp, packet = Packet}) ->
    add_timestamp(TimeStamp, Server, Packet).

add_timestamp(undefined, _Server, Packet) ->
    Packet;
add_timestamp({_,_,Micro} = TimeStamp, Server, Packet) ->
    {D,{H,M,S}} = calendar:now_to_universal_time(TimeStamp),
    Time = {D,{H,M,S, Micro}},
    TimeStampXML = timestamp_xml(Server, Time),
    xml:append_subtags(Packet, [TimeStampXML]).

timestamp_xml(Server, Time) ->
    FromJID = jlib:make_jid(<<>>, Server, <<>>),
    jlib:timestamp_to_xml(Time, utc, FromJID, <<"Offline Storage">>).   

%%--------------------------------------------------------------------
%% Hook handler
%%--------------------------------------------------------------------

check_packet({From, To, XML} = Packet) ->
    #jid{luser = LUser, lserver = LServer} = From,
    case XML#xmlel.name of
        <<"message">> ->
            Type = xml:get_tag_attr_s(list_to_binary("type"), XML),
            case Type of
                <<"chat">> ->
                    DeltaTimeStampS = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("scheduled_time")}, cdata])),
                    case DeltaTimeStampS of
                        "" ->
                            Packet;
                        _ ->
                            RelativeId = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("delayed_msg_id")}, cdata])),
                            {DeltaTimeStampI, _Rest} = string:to_integer(DeltaTimeStampS),
                            case _Rest of
                                [] ->
                                    Action = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("delayed_msg_action")}, cdata])),
                                    ScheduledTimestamp = from_now_to_microseconds(erlang:now(),DeltaTimeStampI),
                                    NewChildren = lists:delete(lists:keyfind(<<"scheduled_time">>, 2, XML#xmlel.children),XML#xmlel.children),
                                    NewXML = XML#xmlel{ children = NewChildren },
                                    case Action of
                                        "edit" ->                                           
                                            edit_delayed_message(LServer, binary_to_list(From#jid.luser), binary_to_list(To#jid.luser), binary_to_list(To#jid.lserver), ScheduledTimestamp, NewXML,RelativeId);

                                        "delete" ->
                                            remove_delayed_message(LServer,#delayed_msg{from = binary_to_list(From#jid.luser), relativeId = RelativeId});                                           
                                        _ ->
                                            add_delayed_message(LServer, binary_to_list(From#jid.luser), binary_to_list(To#jid.luser), binary_to_list(To#jid.lserver), ScheduledTimestamp, NewXML,RelativeId)
                                        end
                            end,
                            drop
                    end;
                    <<"groupchat">> ->
                    DeltaTimeStampS = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("scheduled_time")}, cdata])),
                    case DeltaTimeStampS of
                        "" ->
                            Packet;
                        _ ->
                            RelativeId = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("delayed_msg_id")}, cdata])),
                            {DeltaTimeStampI, _Rest} = string:to_integer(DeltaTimeStampS),
                            case _Rest of
                                [] ->
                                    Action = binary_to_list(xml:get_path_s(XML, [{elem, list_to_binary("delayed_msg_action")}, cdata])),
                                    ScheduledTimestamp = from_now_to_microseconds(erlang:now(),DeltaTimeStampI),
                                    NewChildren = lists:delete(lists:keyfind(<<"scheduled_time">>, 2, XML#xmlel.children),XML#xmlel.children),
                                    NewXML = XML#xmlel{ children = NewChildren },
                                    case Action of
                                        "edit" ->                                           
                                            edit_delayed_message(LServer, binary_to_list(From#jid.luser), binary_to_list(To#jid.luser), binary_to_list(To#jid.lserver), ScheduledTimestamp, NewXML,RelativeId);

                                        "delete" ->
                                            remove_delayed_message(LServer,#delayed_msg{from = binary_to_list(From#jid.luser), relativeId = RelativeId});                                           
                                        _ ->
                                            add_delayed_message(LServer, binary_to_list(From#jid.luser), binary_to_list(To#jid.luser), binary_to_list(To#jid.lserver), ScheduledTimestamp, NewXML,RelativeId)
                                        end
                            end,
                            drop
                    end;
                _ -> Packet
            end;
        _ -> Packet
    end.


process_sm_iq(_From, _To, #iq{type = get, xmlns = ?NS_DELAYMSG} = IQ) ->
    ?INFO_MSG("Processing IQ Get query:~n ~p", [IQ]),
    IQ#iq{type = result, sub_el = [{xmlelement, "value", [], [{xmlcdata, "Hello World of Testing."}]}]};
process_sm_iq(_From, _To, #iq{type = set} = IQ) ->
    ?INFO_MSG("Processing IQ Set: it does nothing", []),
    IQ#iq{type = result, sub_el = []};
process_sm_iq(_From, _To, #iq{sub_el = SubEl} = IQ) ->
    ?INFO_MSG("Processing IQ other type: it does nothing", []),
    IQ#iq{type = error, sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}.

%%--------------------------------------------------------------------
%% ODBC Functions
%%--------------------------------------------------------------------


remove_delayed_message(LServer, #delayed_msg{from=FromUserName, relativeId = RelativeId}) ->
        QR = ejabberd_odbc:sql_query(
          LServer,
          [<<"delete from delayed_message "
                "where from_jid = '">>, ejabberd_odbc:escape(FromUserName#jid.luser),<<"' and relative_id = '">>,ejabberd_odbc:escape(list_to_binary(RelativeId)),<<"';">>]),
        ?DEBUG("DELETE ~p", [QR]).

prepare_delayed_message(SFromUserName, SToUsername, SServer, SScheduledTimestamp, SPacket,SRelativeId) ->
    [<<"('">>,   ejabberd_odbc:escape(list_to_binary(SFromUserName)),
     <<"', '">>, ejabberd_odbc:escape(list_to_binary(SToUsername)),
     <<"', '">>, ejabberd_odbc:escape(list_to_binary(SServer)),
     <<"', ">>,   integer_to_list(SScheduledTimestamp),
     <<", '">>, ejabberd_odbc:escape(xml:element_to_binary(SPacket)),
     <<"', '">>, ejabberd_odbc:escape(list_to_binary(SRelativeId)),
     <<"')">>].

add_delayed_message(LServer, SFromUserName, SToUsername, SServer, SScheduledTimestamp, SPacket, SRelativeId) ->
    Rows = prepare_delayed_message(SFromUserName, SToUsername, SServer, SScheduledTimestamp, SPacket,SRelativeId),
    QR = ejabberd_odbc:sql_query(
      LServer,
      [<<"insert into delayed_message(from_jid,to_jid,server,scheduled_time,packet,relative_id) "
       "values ">>, join(Rows, "")]),
       ?DEBUG("Delayed message inserted? ~p", [QR]).

edit_delayed_message(LServer,SFromUserName, SToUsername, SServer, SScheduledTimestamp, SPacket, SRelativeId) ->
    ejabberd_odbc:sql_query(
      LServer,
      [<<"update delayed_message set to_jid='">>,ejabberd_odbc:escape(list_to_binary(SToUsername)),
       <<"' , server='">>,ejabberd_odbc:escape(list_to_binary(SServer)),
       <<"' , scheduled_time=">>,integer_to_list(SScheduledTimestamp),
       <<", packet='">>,ejabberd_odbc:escape(xml:element_to_binary(SPacket)),
       <<"' where from_jid='">>,ejabberd_odbc:escape(list_to_binary(SFromUserName)),
       <<"' AND relative_id = '">>, ejabberd_odbc:escape(list_to_binary(SRelativeId)),<<"';">>]).

search_delayed_messages(LServer, SScheduledTimestamp) ->
    ScheduledTimestamp = encode_timestamp(SScheduledTimestamp),
    Query = [<<"select id,from_jid,to_jid,server,scheduled_time,packet,relative_id from delayed_message where ">>,
        <<"(scheduled_time < ">>,ScheduledTimestamp,<<" OR ">>,ScheduledTimestamp,<<" = 0);">>],

    case ejabberd_odbc:sql_query(LServer,Query) of
        {selected, [<<"id">>,<<"from_jid">>,<<"to_jid">>,<<"server">>,<<"scheduled_time">>,<<"packet">>,<<"relative_id">>], Rows} ->
            {ok, rows_to_records(Rows)};
        {aborted, Reason} ->
            {error, Reason};
        {error, Reason} ->
            {error, Reason}
    end.

    rows_to_records(Rows) ->
    [row_to_record(Row) || Row <- Rows].

row_to_record({SId, SFromUserName, SToUsername, SServer, SScheduledTimestamp, SPacket,SRelativeId}) ->

    Id = list_to_integer(binary_to_list(SId)),
    Server = binary_to_list(SServer),
    From = jlib:make_jid(SFromUserName,SServer,<<"fb">>),
    To = jlib:make_jid(SToUsername,SServer,<<"fb">>),
    ScheduledTimestamp = microseconds_to_now(list_to_integer(binary_to_list(SScheduledTimestamp))),
    Packet = xml_stream:parse_element(SPacket),
    RelativeId = binary_to_list(SRelativeId),
    #delayed_msg{id = Id,
             from = From,
             to = To,
             server = Server,
             scheduledTimestamp = ScheduledTimestamp,
             packet = Packet,
             relativeId = RelativeId}.


%% ------------------------------------------------------------------
%% Helpers

choose_strategy(true,  true, get) -> get;
choose_strategy(true,  true, set) -> set;
choose_strategy(false, _,    _  ) -> not_allowed;
choose_strategy(_,     _,    _  ) -> forbidden.

compare_bare_jids(#jid{luser = LUser, lserver = LServer},
                  #jid{luser = LUser, lserver = LServer}) -> true;
compare_bare_jids(_, _) -> false.

element_to_namespace(#xmlel{attrs = Attrs}) ->
    xml:get_attr_s(<<"xmlns">>, Attrs);
element_to_namespace(_) ->
    <<>>.

%% Skip invalid elements.
to_map(Elems) ->
    [{NS, Elem} || Elem <- Elems, is_valid_namespace(NS = element_to_namespace(Elem))].

is_valid_namespace(Namespace) -> Namespace =/= <<>>.

error_iq(IQ=#iq{sub_el=SubElem}, ErrorStanza) ->
    IQ#iq{type = error, sub_el = [SubElem, ErrorStanza]}.


from_now_to_microseconds({Mega, Secs, Micro}, FromNow) ->
    Mega*1000*1000*1000*1000 + Secs * 1000 * 1000 + Micro + FromNow.

now_to_microseconds({Mega, Secs, Micro}) ->
    Mega*1000*1000*1000*1000 + Secs * 1000 * 1000 + Micro.


encode_timestamp(TimeStamp) ->
    integer_to_list(TimeStamp).

maybe_encode_timestamp(never) ->
    "null";
maybe_encode_timestamp(TimeStamp) ->
    encode_timestamp(TimeStamp).

microseconds_to_now(MicroSeconds) when is_integer(MicroSeconds) ->
    Seconds = MicroSeconds div 1000000,
    {Seconds div 1000000, Seconds rem 1000000, MicroSeconds rem 1000000}.

join([], _Sep) ->
    [];
join([H|T], Sep) ->
    [H, [[Sep, X] || X <- T]].

3 个答案:

答案 0 :(得分:0)

我从未使用过ejabberd,所以我的评论非常笼统。

您同时使用gen_mod行为(所有ejjaberd模块必需)和来自OTP的gen_server行为。我知道你这样做是为了使用tick信息,但你可以使用函数timer:apply_interval(Time,Module,Function,Args)来获得相同的结果。因此,您可以删除所有gen_server行为及其call_back,它将仅保留start和stop gen_mod回调。

在init / 1函数中,您调用process_flag(trap_exit, true),我认为这通常是一个坏主意,尤其是当您没有任何错误消息管理时(在您的代码中它应该是由一个handle_info子句处理,但这里是匹配的handle_info(_Info, State) -> {noreply, State}.

答案 1 :(得分:0)

一般情况下,除非有必要编写应插入监管层级的自定义流程或行为,否则不应使用terminate

我刚刚浏览过代码,所以如果我的任何假设出错,请纠正我。

你在这里处理两件事:一个ejabberd模块和一个支持它的一些功能的过程,你把它们的初始化交织得太多了。

通过 ejabberd模块我指的是服务器的一个组件(它并不一定意味着当来自组件的代码时,来自客户端的任何节在服务器处理时都会传递进程边界是 叫)。

您介绍了一个能够测量时间刻度的过程,这很好。但是,您还将一些模块初始化放入进程初始化函数(init/1)。

我看到你的IQ / hook处理程序实际上没有在你的代码中调用你的进程 - 这很好,因为这意味着他们并不真正依赖于进程的存在与否(就像它重新启动时一样)主管)。

我建议在start/2中设置您的模块(注册钩子处理程序,IQ处理程序,......),在stop/1中将其拆除并假设主管将重新启动该过程运行时错误的情况 - 不要通过在init / terminate中放置处理程序(de)来绑定模块设置/拆卸和处理生命周期。如果主管必须重新启动你的过程,它应该几乎是立即的 - 即使这样,你的IQ /钩子处理程序也不依赖于那里的模块 - 为什么要将它们绑定在init / {{1} }?

还有一件事 - 如果您希望模块仅在进程运行后启动(有时可能需要,但这里并不严格要求),请记住terminate是同步的,阻止 - 只有在新孩子成功启动后才会返回。一旦你有这个保证(即调用返回正确的返回值),你可以安全地继续模块设置(钩子/ IQ处理程序设置)。

如果您要求您的代码能够很好地扩展,那么timer is not the best choice - 使用supervisor:start_childerlang:send_after/3回调所需的返回值的Timeout部分。

答案 2 :(得分:0)

感谢您的回答。因为我对Erlang很新,所以我不知道我必须编译erl文件。我编译了它,现在正在工作。

这是编译erl文件的指令:

erlc -I / path / to / include / files / module_name.erl