2015-10-06 60 views
0

我創建使用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, "[email protected]@"), 
    %%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]]. 
+0

你應該給整個模塊的代碼,或者至少一個工作子集。當調用init時,這段代碼不會執行任何操作,因爲它不會循環或產生任何進程。我想它是gen_server的一部分,或者你自己的服務器,但是所有這些代碼都沒有。 – Pascal

+0

確保你的程序不是'brutal_kill''d – Lol4t0

+0

@Pascal現在我發佈了所有的代碼 –

回答

0

我從來沒有使用過ejabberd,所以我的意見是很一般。

您同時使用gen_mod行爲(對所有ejjaberd模塊是必需的)和來自OTP的gen_server行爲。我瞭解您是否使用了滴答信息,但您可以使用函數timer:apply_interval(Time,Module,Function,Args)獲得相同的結果。因此,您可以刪除所有gen_server行爲及其call_back,並且它將只保留啓動和停止gen_mod回調。

在init/1函數中,你調用process_flag(trap_exit, true),我認爲這通常是一個壞主意,特別是當你沒有任何錯誤消息的管理時(在你的代碼中它應該由一個handle_info子句來處理,但在這裏是匹配的handle_info(_Info, State) -> {noreply, State}.)。

0

一般來說,除非必要,否則不應使用terminate,以編寫應插入監督層次結構的自定義進程或行爲。

我剛剛通過代碼脫脂,所以請糾正我,如果我錯了,在我的任何假設。

你在這裏處理兩件事:一個ejabberd模塊和一個支持它的一些功能的進程,你把它們的初始化交織得太多了。

通過ejabberd模塊我指的是服務器的組件(這並不一定意味着當由服務器處理時從分量代碼是 稱爲從客戶機來的任何節將傳遞進程邊界)。

你介紹一個過程,能夠測量時間蜱這是罰款。不過,你也把一些模塊初始化到您過程初始化函數(init/1)。

我看到沒有你的智商/鉤處理器的實際調用你的過程中他們的代碼 - 這是件好事,因爲這意味着他們並不是真正依賴的過程是有或沒有(當它被重新啓動的像導師)。

我建議在start/2(註冊掛鉤處理程序,IQ處理程序,...)中設置您的模塊,將其拆下stop/1並假設監控程序將在運行時出錯時重新啓動進程 - 不要綁定通過在init/terminate中放置處理程序(de)註冊模塊設置/拆卸和處理生命週期。如果主管必須重新啓動過程中,它應該是幾乎立即 - 即使如此,你的IQ /掛機處理不依賴於模塊正在那裏 - 爲什麼他們綁在init/terminate

還有一件事 - 如果您希望模塊只在進程運行後啓動(有時可能需要這樣做,儘管這裏不是嚴格要求),請記住supervisor:start_child是同步和阻塞的 - 它只會返回一次新小孩已成功開始。一旦你有這個保證(即調用已經返回正確的返回值),你可以安全地繼續模塊設置(鉤子/ IQ處理程序設置)。


如果你需要你的代碼具有良好的伸縮性,然後timer is not the best choice - 使用erlang:send_after/3handle_*回調預期的返回值的Timeout一部分。

0

謝謝你的回答。由於我對Erlang非常陌生,我不知道必須編譯erl文件。我編譯它,現在正在工作。

這是編譯ERL文件的指令:

erlc -I /路徑/到/包括/文件/ module_name.erl