我正在使用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]].
答案 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_child
或erlang:send_after/3
回调所需的返回值的Timeout
部分。
答案 2 :(得分:0)
感谢您的回答。因为我对Erlang很新,所以我不知道我必须编译erl文件。我编译了它,现在正在工作。
这是编译erl文件的指令:
erlc -I / path / to / include / files / module_name.erl