diff options
Diffstat (limited to 'src/mod_offline.erl')
-rw-r--r-- | src/mod_offline.erl | 1118 |
1 files changed, 524 insertions, 594 deletions
diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 77fbfcedd..a1bf3da04 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -25,6 +25,7 @@ %%%---------------------------------------------------------------------- -module(mod_offline). + -author('alexey@process-one.net'). -behaviour(gen_mod). @@ -47,13 +48,23 @@ webadmin_user_parse_query/5]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include("web/ejabberd_http.hrl"). + -include("web/ejabberd_web_admin.hrl"). --record(offline_msg, {us, timestamp, expire, from, to, packet}). +-record(offline_msg, + {us = {<<"">>, <<"">>} :: {binary(), binary()}, + timestamp = now() :: erlang:timestamp() | '_', + expire = now() :: erlang:timestamp() | never | '_', + from = #jid{} :: jid() | '_', + to = #jid{} :: jid() | '_', + packet = #xmlel{} :: xmlel() | '_'}). -define(PROCNAME, ejabberd_offline). + -define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000). %% default value for the maximum number of user messages @@ -61,18 +72,15 @@ start(Host, Opts) -> case gen_mod:db_type(Opts) of - mnesia -> - mnesia:create_table(offline_msg, - [{disc_only_copies, [node()]}, - {type, bag}, - {attributes, - record_info(fields, offline_msg)}]), - update_table(); - _ -> - ok + mnesia -> + mnesia:create_table(offline_msg, + [{disc_only_copies, [node()]}, {type, bag}, + {attributes, record_info(fields, offline_msg)}]), + update_table(); + _ -> ok end, - ejabberd_hooks:add(offline_message_hook, Host, - ?MODULE, store_packet, 50), + ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, + store_packet, 50), ejabberd_hooks:add(resend_offline_messages_hook, Host, ?MODULE, pop_offline_messages, 50), ejabberd_hooks:add(remove_user, Host, @@ -89,7 +97,7 @@ start(Host, Opts) -> ?MODULE, webadmin_user, 50), ejabberd_hooks:add(webadmin_user_parse_query, Host, ?MODULE, webadmin_user_parse_query, 50), - AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, max_user_offline_messages), + AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, fun(A) -> A end, max_user_offline_messages), register(gen_mod:get_module_proc(Host, ?PROCNAME), spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])). @@ -107,78 +115,68 @@ loop(Host, AccessMaxOfflineMsgs) -> loop(Host, AccessMaxOfflineMsgs) end. -store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, mnesia) -> - F = fun() -> - %% Only count messages if needed: - Count = if MaxOfflineMsgs =/= infinity -> - Len + p1_mnesia:count_records( - offline_msg, - #offline_msg{us=US, _='_'}); - true -> - 0 - end, - if - Count > MaxOfflineMsgs -> - discard_warn_sender(Msgs); - true -> - if - Len >= ?OFFLINE_TABLE_LOCK_THRESHOLD -> - mnesia:write_lock_table(offline_msg); - true -> - ok - end, - lists:foreach(fun(M) -> - mnesia:write(M) - end, Msgs) - end - end, +store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, + mnesia) -> + F = fun () -> + Count = if MaxOfflineMsgs =/= infinity -> + Len + + p1_mnesia:count_records(offline_msg, + #offline_msg{us = US, + _ = '_'}); + true -> 0 + end, + if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs); + true -> + if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) -> + mnesia:write_lock_table(offline_msg); + true -> ok + end, + lists:foreach(fun (M) -> mnesia:write(M) end, Msgs) + end + end, mnesia:transaction(F); store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) -> Count = if MaxOfflineMsgs =/= infinity -> - Len + count_offline_messages(User, Host); - true -> 0 - end, - if - Count > MaxOfflineMsgs -> - discard_warn_sender(Msgs); - true -> - Query = lists:map( - fun(M) -> - Username = - ejabberd_odbc:escape( - (M#offline_msg.to)#jid.luser), - From = M#offline_msg.from, - To = M#offline_msg.to, - {xmlelement, Name, Attrs, Els} = - M#offline_msg.packet, - Attrs2 = jlib:replace_from_to_attrs( - jlib:jid_to_string(From), - jlib:jid_to_string(To), - Attrs), - Packet = {xmlelement, Name, Attrs2, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - M#offline_msg.timestamp), - utc, - jlib:make_jid("", Host, ""), - "Offline Storage"), - %% TODO: Delete the next three lines once XEP-0091 is Obsolete - jlib:timestamp_to_xml( - calendar:now_to_universal_time( - M#offline_msg.timestamp))]}, - XML = - ejabberd_odbc:escape( - xml:element_to_binary(Packet)), - odbc_queries:add_spool_sql(Username, XML) - end, Msgs), - odbc_queries:add_spool(Host, Query) + Len + count_offline_messages(User, Host); + true -> 0 + end, + if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs); + true -> + Query = lists:map(fun (M) -> + Username = + ejabberd_odbc:escape((M#offline_msg.to)#jid.luser), + From = M#offline_msg.from, + To = M#offline_msg.to, + #xmlel{name = Name, attrs = Attrs, + children = Els} = + M#offline_msg.packet, + Attrs2 = + jlib:replace_from_to_attrs(jlib:jid_to_string(From), + jlib:jid_to_string(To), + Attrs), + Packet = #xmlel{name = Name, + attrs = Attrs2, + children = + Els ++ + [jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp), + utc, + jlib:make_jid(<<"">>, + Host, + <<"">>), + <<"Offline Storage">>), + jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp))]}, + XML = + ejabberd_odbc:escape(xml:element_to_binary(Packet)), + odbc_queries:add_spool_sql(Username, XML) + end, + Msgs), + odbc_queries:add_spool(Host, Query) end. %% Function copied from ejabberd_sm.erl: get_max_user_messages(AccessRule, {User, Server}, Host) -> case acl:match_rule( - Host, AccessRule, jlib:make_jid(User, Server, "")) of + Host, AccessRule, jlib:make_jid(User, Server, <<"">>)) of Max when is_integer(Max) -> Max; infinity -> infinity; _ -> ?MAX_USER_MESSAGES @@ -186,32 +184,22 @@ get_max_user_messages(AccessRule, {User, Server}, Host) -> receive_all(US, Msgs, DBType) -> receive - #offline_msg{us=US} = Msg -> - receive_all(US, [Msg | Msgs], DBType) - after 0 -> - %% FIXME: the diff between mnesia and odbc version: - %% - %% after 0 -> - %% - Msgs - %% + lists:reverse(Msgs) - %% end. - %% - %% Is it a bug in mnesia version? - case DBType of - mnesia -> - Msgs; - odbc -> - lists:reverse(Msgs) - end + #offline_msg{us = US} = Msg -> + receive_all(US, [Msg | Msgs], DBType) + after 0 -> + case DBType of + mnesia -> Msgs; + odbc -> lists:reverse(Msgs) + end end. stop(Host) -> ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, store_packet, 50), - ejabberd_hooks:delete(resend_offline_messages_hook, Host, - ?MODULE, pop_offline_messages, 50), - ejabberd_hooks:delete(remove_user, Host, - ?MODULE, remove_user, 50), + ejabberd_hooks:delete(resend_offline_messages_hook, + Host, ?MODULE, pop_offline_messages, 50), + ejabberd_hooks:delete(remove_user, Host, ?MODULE, + remove_user, 50), ejabberd_hooks:delete(anonymous_purge_hook, Host, ?MODULE, remove_user, 50), ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50), @@ -221,7 +209,7 @@ stop(Host) -> ejabberd_hooks:delete(webadmin_user, Host, ?MODULE, webadmin_user, 50), ejabberd_hooks:delete(webadmin_user_parse_query, Host, - ?MODULE, webadmin_user_parse_query, 50), + ?MODULE, webadmin_user_parse_query, 50), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), exit(whereis(Proc), stop), {wait, Proc}. @@ -242,256 +230,206 @@ get_sm_features(Acc, _From, _To, _Node, _Lang) -> store_packet(From, To, Packet) -> - Type = xml:get_tag_attr_s("type", Packet), - if - (Type /= "error") and (Type /= "groupchat") and - (Type /= "headline") -> - case check_event_chatstates(From, To, Packet) of - true -> - #jid{luser = LUser, lserver = LServer} = To, - TimeStamp = now(), - {xmlelement, _Name, _Attrs, Els} = Packet, - Expire = find_x_expire(TimeStamp, Els), - gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) ! - #offline_msg{us = {LUser, LServer}, - timestamp = TimeStamp, - expire = Expire, - from = From, - to = To, - packet = Packet}, - stop; - _ -> - ok - end; - true -> - ok + Type = xml:get_tag_attr_s(<<"type">>, Packet), + if (Type /= <<"error">>) and (Type /= <<"groupchat">>) + and (Type /= <<"headline">>) -> + case check_event(From, To, Packet) of + true -> + #jid{luser = LUser, lserver = LServer} = To, + TimeStamp = now(), + #xmlel{children = Els} = Packet, + Expire = find_x_expire(TimeStamp, Els), + gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) ! + #offline_msg{us = {LUser, LServer}, + timestamp = TimeStamp, expire = Expire, + from = From, to = To, packet = Packet}, + stop; + _ -> ok + end; + true -> ok end. %% Check if the packet has any content about XEP-0022 or XEP-0085 -check_event_chatstates(From, To, Packet) -> - {xmlelement, Name, Attrs, Els} = Packet, - case find_x_event_chatstates(Els, {false, false, false}) of - %% There wasn't any x:event or chatstates subelements - {false, false, _} -> - true; - %% There a chatstates subelement and other stuff, but no x:event - {false, CEl, true} when CEl /= false -> - true; - %% There was only a subelement: a chatstates - {false, CEl, false} when CEl /= false -> - %% Don't allow offline storage - false; - %% There was an x:event element, and maybe also other stuff - {El, _, _} when El /= false -> - case xml:get_subtag(El, "id") of - false -> - case xml:get_subtag(El, "offline") of - false -> - true; - _ -> - ID = case xml:get_tag_attr_s("id", Packet) of - "" -> - {xmlelement, "id", [], []}; - S -> - {xmlelement, "id", [], - [{xmlcdata, S}]} - end, - ejabberd_router:route( - To, From, {xmlelement, Name, Attrs, - [{xmlelement, "x", - [{"xmlns", ?NS_EVENT}], - [ID, - {xmlelement, "offline", [], []}]}] - }), - true - end; - _ -> - false - end +check_event(From, To, Packet) -> + #xmlel{name = Name, attrs = Attrs, children = Els} = + Packet, + case find_x_event(Els) of + false -> true; + El -> + case xml:get_subtag(El, <<"id">>) of + false -> + case xml:get_subtag(El, <<"offline">>) of + false -> true; + _ -> + ID = case xml:get_tag_attr_s(<<"id">>, Packet) of + <<"">> -> + #xmlel{name = <<"id">>, attrs = [], + children = []}; + S -> + #xmlel{name = <<"id">>, attrs = [], + children = [{xmlcdata, S}]} + end, + ejabberd_router:route(To, From, + #xmlel{name = Name, attrs = Attrs, + children = + [#xmlel{name = <<"x">>, + attrs = + [{<<"xmlns">>, + ?NS_EVENT}], + children = + [ID, + #xmlel{name + = + <<"offline">>, + attrs + = + [], + children + = + []}]}]}), + true + end; + _ -> false + end end. %% Check if the packet has subelements about XEP-0022, XEP-0085 or other -find_x_event_chatstates([], Res) -> - Res; -find_x_event_chatstates([{xmlcdata, _} | Els], Res) -> - find_x_event_chatstates(Els, Res); -find_x_event_chatstates([El | Els], {A, B, C}) -> - case xml:get_tag_attr_s("xmlns", El) of - ?NS_EVENT -> - find_x_event_chatstates(Els, {El, B, C}); - ?NS_CHATSTATES -> - find_x_event_chatstates(Els, {A, El, C}); - _ -> - find_x_event_chatstates(Els, {A, B, true}) +find_x_event([]) -> false; +find_x_event([{xmlcdata, _} | Els]) -> + find_x_event(Els); +find_x_event([El | Els]) -> + case xml:get_tag_attr_s(<<"xmlns">>, El) of + ?NS_EVENT -> El; + _ -> find_x_event(Els) end. -find_x_expire(_, []) -> - never; +find_x_expire(_, []) -> never; find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) -> find_x_expire(TimeStamp, Els); find_x_expire(TimeStamp, [El | Els]) -> - case xml:get_tag_attr_s("xmlns", El) of - ?NS_EXPIRE -> - Val = xml:get_tag_attr_s("seconds", El), - case catch list_to_integer(Val) of - {'EXIT', _} -> - never; - Int when Int > 0 -> - {MegaSecs, Secs, MicroSecs} = TimeStamp, - S = MegaSecs * 1000000 + Secs + Int, - MegaSecs1 = S div 1000000, - Secs1 = S rem 1000000, - {MegaSecs1, Secs1, MicroSecs}; - _ -> - never - end; - _ -> - find_x_expire(TimeStamp, Els) + case xml:get_tag_attr_s(<<"xmlns">>, El) of + ?NS_EXPIRE -> + Val = xml:get_tag_attr_s(<<"seconds">>, El), + case catch jlib:binary_to_integer(Val) of + {'EXIT', _} -> never; + Int when Int > 0 -> + {MegaSecs, Secs, MicroSecs} = TimeStamp, + S = MegaSecs * 1000000 + Secs + Int, + MegaSecs1 = S div 1000000, + Secs1 = S rem 1000000, + {MegaSecs1, Secs1, MicroSecs}; + _ -> never + end; + _ -> find_x_expire(TimeStamp, Els) end. - resend_offline_messages(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, - F = fun() -> + F = fun () -> Rs = mnesia:wread({offline_msg, US}), mnesia:delete({offline_msg, US}), Rs end, case mnesia:transaction(F) of - {atomic, Rs} -> - lists:foreach( - fun(R) -> - {xmlelement, Name, Attrs, Els} = R#offline_msg.packet, - ejabberd_sm ! - {route, - R#offline_msg.from, - R#offline_msg.to, - {xmlelement, Name, Attrs, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp), - utc, - jlib:make_jid("", Server, ""), - "Offline Storage"), - %% TODO: Delete the next three lines once XEP-0091 is Obsolete - jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp))]}} - end, - lists:keysort(#offline_msg.timestamp, Rs)); - _ -> - ok + {atomic, Rs} -> + lists:foreach(fun (R) -> + #xmlel{name = Name, attrs = Attrs, + children = Els} = + R#offline_msg.packet, + ejabberd_sm ! + {route, R#offline_msg.from, R#offline_msg.to, + #xmlel{name = Name, attrs = Attrs, + children = + Els ++ + [jlib:timestamp_to_xml(calendar:now_to_universal_time(R#offline_msg.timestamp))]}} + end, + lists:keysort(#offline_msg.timestamp, Rs)); + _ -> ok end. pop_offline_messages(Ls, User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), pop_offline_messages(Ls, LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). + gen_mod:db_type(LServer, ?MODULE)). pop_offline_messages(Ls, LUser, LServer, mnesia) -> US = {LUser, LServer}, - F = fun() -> + F = fun () -> Rs = mnesia:wread({offline_msg, US}), mnesia:delete({offline_msg, US}), Rs end, case mnesia:transaction(F) of - {atomic, Rs} -> - TS = now(), - Ls ++ lists:map( - fun(R) -> - {xmlelement, Name, Attrs, Els} = R#offline_msg.packet, - {route, - R#offline_msg.from, - R#offline_msg.to, - {xmlelement, Name, Attrs, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp), - utc, - jlib:make_jid("", LServer, ""), - "Offline Storage"), - %% TODO: Delete the next three lines once XEP-0091 is Obsolete - jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp))]}} - end, - lists:filter( - fun(R) -> - case R#offline_msg.expire of - never -> - true; - TimeStamp -> - TS < TimeStamp - end + {atomic, Rs} -> + TS = now(), + Ls ++ + lists:map(fun (R) -> + offline_msg_to_route(LServer, R) end, - lists:keysort(#offline_msg.timestamp, Rs))); - _ -> - Ls + lists:filter(fun (R) -> + case R#offline_msg.expire of + never -> true; + TimeStamp -> TS < TimeStamp + end + end, + lists:keysort(#offline_msg.timestamp, Rs))); + _ -> Ls end; pop_offline_messages(Ls, LUser, LServer, odbc) -> EUser = ejabberd_odbc:escape(LUser), - case odbc_queries:get_and_del_spool_msg_t(LServer, EUser) of - {atomic, {selected, ["username","xml"], Rs}} -> - Ls ++ lists:flatmap( - fun({_, XML}) -> - case xml_stream:parse_element(XML) of - {error, _Reason} -> - []; - El -> - To = jlib:string_to_jid( - xml:get_tag_attr_s("to", El)), - From = jlib:string_to_jid( - xml:get_tag_attr_s("from", El)), - if - (To /= error) and - (From /= error) -> - [{route, From, To, El}]; - true -> - [] - end - end - end, Rs); - _ -> - Ls + case odbc_queries:get_and_del_spool_msg_t(LServer, + EUser) + of + {atomic, {selected, [<<"username">>, <<"xml">>], Rs}} -> + Ls ++ + lists:flatmap(fun ([_, XML]) -> + case xml_stream:parse_element(XML) of + {error, _Reason} -> + []; + El -> + case offline_msg_to_route(LServer, El) of + error -> + []; + RouteMsg -> + [RouteMsg] + end + end + end, + Rs); + _ -> Ls end. remove_expired_messages(Server) -> LServer = jlib:nameprep(Server), - remove_expired_messages(LServer, gen_mod:db_type(LServer, ?MODULE)). + remove_expired_messages(LServer, + gen_mod:db_type(LServer, ?MODULE)). remove_expired_messages(_LServer, mnesia) -> TimeStamp = now(), - F = fun() -> + F = fun () -> mnesia:write_lock_table(offline_msg), - mnesia:foldl( - fun(Rec, _Acc) -> - case Rec#offline_msg.expire of - never -> - ok; - TS -> - if - TS < TimeStamp -> - mnesia:delete_object(Rec); - true -> - ok - end - end - end, ok, offline_msg) + mnesia:foldl(fun (Rec, _Acc) -> + case Rec#offline_msg.expire of + never -> ok; + TS -> + if TS < TimeStamp -> + mnesia:delete_object(Rec); + true -> ok + end + end + end, + ok, offline_msg) end, mnesia:transaction(F); -remove_expired_messages(_LServer, odbc) -> - %% TODO - {atomic, ok}. +remove_expired_messages(_LServer, odbc) -> {atomic, ok}. remove_old_messages(Days, Server) -> LServer = jlib:nameprep(Server), - remove_old_messages(Days, LServer, gen_mod:db_type(LServer, ?MODULE)). + remove_old_messages(Days, LServer, + gen_mod:db_type(LServer, ?MODULE)). remove_old_messages(Days, _LServer, mnesia) -> {MegaSecs, Secs, _MicroSecs} = now(), @@ -499,401 +437,393 @@ remove_old_messages(Days, _LServer, mnesia) -> MegaSecs1 = S div 1000000, Secs1 = S rem 1000000, TimeStamp = {MegaSecs1, Secs1, 0}, - F = fun() -> + F = fun () -> mnesia:write_lock_table(offline_msg), - mnesia:foldl( - fun(#offline_msg{timestamp = TS} = Rec, _Acc) - when TS < TimeStamp -> - mnesia:delete_object(Rec); - (_Rec, _Acc) -> ok - end, ok, offline_msg) + mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec, + _Acc) + when TS < TimeStamp -> + mnesia:delete_object(Rec); + (_Rec, _Acc) -> ok + end, + ok, offline_msg) end, mnesia:transaction(F); remove_old_messages(_Days, _LServer, odbc) -> - %% TODO {atomic, ok}. remove_user(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), - remove_user(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). + remove_user(LUser, LServer, + gen_mod:db_type(LServer, ?MODULE)). remove_user(LUser, LServer, mnesia) -> US = {LUser, LServer}, - F = fun() -> - mnesia:delete({offline_msg, US}) - end, + F = fun () -> mnesia:delete({offline_msg, US}) end, mnesia:transaction(F); remove_user(LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), odbc_queries:del_spool_msg(LServer, Username). +jid_to_binary(#jid{user = U, server = S, resource = R, + luser = LU, lserver = LS, lresource = LR}) -> + #jid{user = iolist_to_binary(U), + server = iolist_to_binary(S), + resource = iolist_to_binary(R), + luser = iolist_to_binary(LU), + lserver = iolist_to_binary(LS), + lresource = iolist_to_binary(LR)}. + update_table() -> Fields = record_info(fields, offline_msg), case mnesia:table_info(offline_msg, attributes) of - Fields -> - ok; - [user, timestamp, expire, from, to, packet] -> - ?INFO_MSG("Converting offline_msg table from " - "{user, timestamp, expire, from, to, packet} format", []), - Host = ?MYNAME, - {atomic, ok} = mnesia:create_table( - mod_offline_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, offline_msg}, - {attributes, record_info(fields, offline_msg)}]), - mnesia:transform_table(offline_msg, ignore, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_offline_tmp_table), - mnesia:foldl( - fun(#offline_msg{us = U} = R, _) -> - mnesia:dirty_write( - mod_offline_tmp_table, - R#offline_msg{us = {U, Host}}) - end, ok, offline_msg) - end, - mnesia:transaction(F1), - mnesia:clear_table(offline_msg), - F2 = fun() -> - mnesia:write_lock_table(offline_msg), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_offline_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_offline_tmp_table); - [user, timestamp, from, to, packet] -> - ?INFO_MSG("Converting offline_msg table from " - "{user, timestamp, from, to, packet} format", []), - Host = ?MYNAME, - {atomic, ok} = mnesia:create_table( - mod_offline_tmp_table, - [{disc_only_copies, [node()]}, - {type, bag}, - {local_content, true}, - {record_name, offline_msg}, - {attributes, record_info(fields, offline_msg)}]), - mnesia:transform_table( - offline_msg, - fun({_, U, TS, F, T, P}) -> - {xmlelement, _Name, _Attrs, Els} = P, - Expire = find_x_expire(TS, Els), - #offline_msg{us = U, - timestamp = TS, - expire = Expire, - from = F, - to = T, - packet = P} - end, Fields), - F1 = fun() -> - mnesia:write_lock_table(mod_offline_tmp_table), - mnesia:foldl( - fun(#offline_msg{us = U} = R, _) -> - mnesia:dirty_write( - mod_offline_tmp_table, - R#offline_msg{us = {U, Host}}) - end, ok, offline_msg) - end, - mnesia:transaction(F1), - mnesia:clear_table(offline_msg), - F2 = fun() -> - mnesia:write_lock_table(offline_msg), - mnesia:foldl( - fun(R, _) -> - mnesia:dirty_write(R) - end, ok, mod_offline_tmp_table) - end, - mnesia:transaction(F2), - mnesia:delete_table(mod_offline_tmp_table); - _ -> - ?INFO_MSG("Recreating offline_msg table", []), - mnesia:transform_table(offline_msg, ignore, Fields) + Fields -> + ejabberd_config:convert_table_to_binary( + offline_msg, Fields, bag, + fun(#offline_msg{us = {U, _}}) -> U end, + fun(#offline_msg{us = {U, S}, + from = From, + to = To, + packet = El} = R) -> + R#offline_msg{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + from = jid_to_binary(From), + to = jid_to_binary(To), + packet = xml:to_xmlel(El)} + end); + _ -> + ?INFO_MSG("Recreating offline_msg table", []), + mnesia:transform_table(offline_msg, ignore, Fields) end. - %% Helper functions: %% Warn senders that their messages have been discarded: discard_warn_sender(Msgs) -> - lists:foreach( - fun(#offline_msg{from=From, to=To, packet=Packet}) -> - ErrText = "Your contact offline message queue is full. The message has been discarded.", - Lang = xml:get_tag_attr_s("xml:lang", Packet), - Err = jlib:make_error_reply( - Packet, ?ERRT_RESOURCE_CONSTRAINT(Lang, ErrText)), - ejabberd_router:route( - To, - From, Err) - end, Msgs). - + lists:foreach(fun (#offline_msg{from = From, to = To, + packet = Packet}) -> + ErrText = <<"Your contact offline message queue is " + "full. The message has been discarded.">>, + Lang = xml:get_tag_attr_s(<<"xml:lang">>, Packet), + Err = jlib:make_error_reply(Packet, + ?ERRT_RESOURCE_CONSTRAINT(Lang, + ErrText)), + ejabberd_router:route(To, From, Err) + end, + Msgs). webadmin_page(_, Host, - #request{us = _US, - path = ["user", U, "queue"], - q = Query, - lang = Lang} = _Request) -> - Res = user_queue(U, Host, Query, Lang), - {stop, Res}; - + #request{us = _US, path = [<<"user">>, U, <<"queue">>], + q = Query, lang = Lang} = + _Request) -> + Res = user_queue(U, Host, Query, Lang), {stop, Res}; webadmin_page(Acc, _, _) -> Acc. +offline_msg_to_route(LServer, #offline_msg{} = R) -> + El = #xmlel{children = Els} = R#offline_msg.packet, + {route, R#offline_msg.from, R#offline_msg.to, + El#xmlel{children = + Els ++ + [jlib:timestamp_to_xml( + calendar:now_to_universal_time( + R#offline_msg.timestamp), + utc, + jlib:make_jid(<<"">>, LServer, <<"">>), + <<"Offline Storage">>), + jlib:timestamp_to_xml( + calendar:now_to_universal_time( + R#offline_msg.timestamp))]}}; +offline_msg_to_route(_LServer, #xmlel{} = El) -> + To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, El)), + From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>, El)), + if (To /= error) and (From /= error) -> + {route, From, To, El}; + true -> + error + end. + read_all_msgs(LUser, LServer, mnesia) -> US = {LUser, LServer}, lists:keysort(#offline_msg.timestamp, - mnesia:dirty_read({offline_msg, US})); + mnesia:dirty_read({offline_msg, US})); read_all_msgs(LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( - LServer, - ["select xml from spool" - " where username='", Username, "'" - " order by seq;"]) of - {selected, ["username", "xml"], Rs} -> - lists:flatmap( - fun({XML}) -> - case xml_stream:parse_element(XML) of - {error, _Reason} -> - []; - El -> - [El] - end - end, Rs); - _ -> - [] + case catch ejabberd_odbc:sql_query(LServer, + [<<"select xml from spool where username='">>, + Username, <<"' order by seq;">>]) + of + {selected, [<<"xml">>], Rs} -> + lists:flatmap(fun ([XML]) -> + case xml_stream:parse_element(XML) of + {error, _Reason} -> []; + El -> [El] + end + end, + Rs); + _ -> [] end. format_user_queue(Msgs, mnesia) -> - lists:map( - fun(#offline_msg{timestamp = TimeStamp, from = From, to = To, - packet = {xmlelement, Name, Attrs, Els}} = Msg) -> - ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))), - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - Time = lists:flatten( - io_lib:format( - "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, Hour, Minute, Second])), - SFrom = jlib:jid_to_string(From), - STo = jlib:jid_to_string(To), - Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs), - Packet = {xmlelement, Name, Attrs2, Els}, - FPacket = ejabberd_web_admin:pretty_print_xml(Packet), - ?XE("tr", - [?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]), - ?XAC("td", [{"class", "valign"}], Time), - ?XAC("td", [{"class", "valign"}], SFrom), - ?XAC("td", [{"class", "valign"}], STo), - ?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])] - ) - end, Msgs); + lists:map(fun (#offline_msg{timestamp = TimeStamp, + from = From, to = To, + packet = + #xmlel{name = Name, attrs = Attrs, + children = Els}} = + Msg) -> + ID = jlib:encode_base64((term_to_binary(Msg))), + {{Year, Month, Day}, {Hour, Minute, Second}} = + calendar:now_to_local_time(TimeStamp), + Time = + iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, Month, Day, + Hour, Minute, + Second])), + SFrom = jlib:jid_to_string(From), + STo = jlib:jid_to_string(To), + Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs), + Packet = #xmlel{name = Name, attrs = Attrs2, + children = Els}, + FPacket = ejabberd_web_admin:pretty_print_xml(Packet), + ?XE(<<"tr">>, + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo), + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?XC(<<"pre">>, FPacket)])]) + end, + Msgs); format_user_queue(Msgs, odbc) -> - lists:map( - fun({xmlelement, _Name, _Attrs, _Els} = Msg) -> - ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))), - Packet = Msg, - FPacket = ejabberd_web_admin:pretty_print_xml(Packet), - ?XE("tr", - [?XAE("td", [{"class", "valign"}], [?INPUT("checkbox", "selected", ID)]), - ?XAE("td", [{"class", "valign"}], [?XC("pre", FPacket)])] - ) - end, Msgs). + lists:map(fun (#xmlel{} = Msg) -> + ID = jlib:encode_base64((term_to_binary(Msg))), + Packet = Msg, + FPacket = ejabberd_web_admin:pretty_print_xml(Packet), + ?XE(<<"tr">>, + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?XC(<<"pre">>, FPacket)])]) + end, + Msgs). user_queue(User, Server, Query, Lang) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), US = {LUser, LServer}, DBType = gen_mod:db_type(LServer, ?MODULE), - Res = user_queue_parse_query(LUser, LServer, Query, DBType), + Res = user_queue_parse_query(LUser, LServer, Query, + DBType), MsgsAll = read_all_msgs(LUser, LServer, DBType), - Msgs = get_messages_subset(User, Server, MsgsAll, DBType), + Msgs = get_messages_subset(US, Server, MsgsAll, + DBType), FMsgs = format_user_queue(Msgs, DBType), - [?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"), - [us_to_list(US)]))] ++ - case Res of - ok -> [?XREST("Submitted")]; - nothing -> [] - end ++ - [?XAE("form", [{"action", ""}, {"method", "post"}], - [?XE("table", - [?XE("thead", - [?XE("tr", - [?X("td"), - ?XCT("td", "Time"), - ?XCT("td", "From"), - ?XCT("td", "To"), - ?XCT("td", "Packet") - ])]), - ?XE("tbody", - if - FMsgs == [] -> - [?XE("tr", - [?XAC("td", [{"colspan", "4"}], " ")] - )]; - true -> - FMsgs - end - )]), + [?XC(<<"h1">>, + list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>), + [us_to_list(US)])))] + ++ + case Res of + ok -> [?XREST(<<"Submitted">>)]; + nothing -> [] + end + ++ + [?XAE(<<"form">>, + [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], + [?XE(<<"table">>, + [?XE(<<"thead">>, + [?XE(<<"tr">>, + [?X(<<"td">>), ?XCT(<<"td">>, <<"Time">>), + ?XCT(<<"td">>, <<"From">>), + ?XCT(<<"td">>, <<"To">>), + ?XCT(<<"td">>, <<"Packet">>)])]), + ?XE(<<"tbody">>, + if FMsgs == [] -> + [?XE(<<"tr">>, + [?XAC(<<"td">>, [{<<"colspan">>, <<"4">>}], + <<" ">>)])]; + true -> FMsgs + end)]), ?BR, - ?INPUTT("submit", "delete", "Delete Selected") - ])]. + ?INPUTT(<<"submit">>, <<"delete">>, + <<"Delete Selected">>)])]. user_queue_parse_query(LUser, LServer, Query, mnesia) -> US = {LUser, LServer}, - case lists:keysearch("delete", 1, Query) of - {value, _} -> - Msgs = lists:keysort(#offline_msg.timestamp, - mnesia:dirty_read({offline_msg, US})), - F = fun() -> - lists:foreach( - fun(Msg) -> - ID = jlib:encode_base64( - binary_to_list(term_to_binary(Msg))), - case lists:member({"selected", ID}, Query) of - true -> - mnesia:delete_object(Msg); - false -> - ok - end - end, Msgs) - end, - mnesia:transaction(F), - ok; - false -> - nothing + case lists:keysearch(<<"delete">>, 1, Query) of + {value, _} -> + Msgs = lists:keysort(#offline_msg.timestamp, + mnesia:dirty_read({offline_msg, US})), + F = fun () -> + lists:foreach(fun (Msg) -> + ID = + jlib:encode_base64((term_to_binary(Msg))), + case lists:member({<<"selected">>, + ID}, + Query) + of + true -> mnesia:delete_object(Msg); + false -> ok + end + end, + Msgs) + end, + mnesia:transaction(F), + ok; + false -> nothing end; user_queue_parse_query(LUser, LServer, Query, odbc) -> Username = ejabberd_odbc:escape(LUser), - case lists:keysearch("delete", 1, Query) of - {value, _} -> - Msgs = case catch ejabberd_odbc:sql_query( - LServer, - ["select xml, seq from spool" - " where username='", Username, "'" - " order by seq;"]) of - {selected, ["xml", "seq"], Rs} -> - lists:flatmap( - fun({XML, Seq}) -> - case xml_stream:parse_element(XML) of - {error, _Reason} -> - []; - El -> - [{El, Seq}] - end - end, Rs); - _ -> - [] - end, - F = fun() -> - lists:foreach( - fun({Msg, Seq}) -> - ID = jlib:encode_base64( - binary_to_list(term_to_binary(Msg))), - case lists:member({"selected", ID}, Query) of - true -> - SSeq = ejabberd_odbc:escape(Seq), - catch ejabberd_odbc:sql_query( - LServer, - ["delete from spool" - " where username='", Username, "'" - " and seq='", SSeq, "';"]); - false -> - ok - end - end, Msgs) - end, - mnesia:transaction(F), - ok; - false -> - nothing + case lists:keysearch(<<"delete">>, 1, Query) of + {value, _} -> + Msgs = case catch ejabberd_odbc:sql_query(LServer, + [<<"select xml, seq from spool where username='">>, + Username, + <<"' order by seq;">>]) + of + {selected, [<<"xml">>, <<"seq">>], Rs} -> + lists:flatmap(fun ([XML, Seq]) -> + case xml_stream:parse_element(XML) + of + {error, _Reason} -> []; + El -> [{El, Seq}] + end + end, + Rs); + _ -> [] + end, + F = fun () -> + lists:foreach(fun ({Msg, Seq}) -> + ID = + jlib:encode_base64((term_to_binary(Msg))), + case lists:member({<<"selected">>, + ID}, + Query) + of + true -> + SSeq = + ejabberd_odbc:escape(Seq), + catch + ejabberd_odbc:sql_query(LServer, + [<<"delete from spool where username='">>, + Username, + <<"' and seq='">>, + SSeq, + <<"';">>]); + false -> ok + end + end, + Msgs) + end, + mnesia:transaction(F), + ok; + false -> nothing end. us_to_list({User, Server}) -> - jlib:jid_to_string({User, Server, ""}). + jlib:jid_to_string({User, Server, <<"">>}). get_queue_length(LUser, LServer) -> - get_queue_length(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). + get_queue_length(LUser, LServer, + gen_mod:db_type(LServer, ?MODULE)). get_queue_length(LUser, LServer, mnesia) -> - length(mnesia:dirty_read({offline_msg, {LUser, LServer}})); + length(mnesia:dirty_read({offline_msg, + {LUser, LServer}})); get_queue_length(LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( - LServer, - ["select count(*) from spool" - " where username='", Username, "';"]) of - {selected, [_], [{SCount}]} -> - list_to_integer(SCount); - _ -> - 0 + case catch ejabberd_odbc:sql_query(LServer, + [<<"select count(*) from spool where username='">>, + Username, <<"';">>]) + of + {selected, [_], [[SCount]]} -> + jlib:binary_to_integer(SCount); + _ -> 0 end. get_messages_subset(User, Host, MsgsAll, DBType) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages, + fun(A) when is_atom(A) -> A end, max_user_offline_messages), - MaxOfflineMsgs = case get_max_user_messages(Access, {User, Host}, Host) of - Number when is_integer(Number) -> Number; - _ -> 100 + MaxOfflineMsgs = case get_max_user_messages(Access, + User, Host) + of + Number when is_integer(Number) -> Number; + _ -> 100 end, Length = length(MsgsAll), - get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll, DBType). + get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll, + DBType). -get_messages_subset2(Max, Length, MsgsAll, _DBType) when Length =< Max*2 -> +get_messages_subset2(Max, Length, MsgsAll, _DBType) + when Length =< Max * 2 -> MsgsAll; get_messages_subset2(Max, Length, MsgsAll, mnesia) -> FirstN = Max, {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll), - MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2), - NoJID = jlib:make_jid("...", "...", ""), - IntermediateMsg = #offline_msg{timestamp = now(), from = NoJID, to = NoJID, - packet = {xmlelement, "...", [], []}}, + MsgsLastN = lists:nthtail(Length - FirstN - FirstN, + Msgs2), + NoJID = jlib:make_jid(<<"...">>, <<"...">>, <<"">>), + IntermediateMsg = #offline_msg{timestamp = now(), + from = NoJID, to = NoJID, + packet = + #xmlel{name = <<"...">>, attrs = [], + children = []}}, MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN; get_messages_subset2(Max, Length, MsgsAll, odbc) -> FirstN = Max, {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll), - MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2), - IntermediateMsg = {xmlelement, "...", [], []}, + MsgsLastN = lists:nthtail(Length - FirstN - FirstN, + Msgs2), + IntermediateMsg = #xmlel{name = <<"...">>, attrs = [], + children = []}, MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN. webadmin_user(Acc, User, Server, Lang) -> - QueueLen = get_queue_length(jlib:nodeprep(User), jlib:nameprep(Server)), - FQueueLen = [?AC("queue/", - integer_to_list(QueueLen))], - Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")]. + QueueLen = get_queue_length(jlib:nodeprep(User), + jlib:nameprep(Server)), + FQueueLen = [?AC(<<"queue/">>, + (iolist_to_binary(integer_to_list(QueueLen))))], + Acc ++ + [?XCT(<<"h3">>, <<"Offline Messages:">>)] ++ + FQueueLen ++ + [?C(<<" ">>), + ?INPUTT(<<"submit">>, <<"removealloffline">>, + <<"Remove All Offline Messages">>)]. delete_all_msgs(User, Server) -> LUser = jlib:nodeprep(User), LServer = jlib:nameprep(Server), - delete_all_msgs(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). + delete_all_msgs(LUser, LServer, + gen_mod:db_type(LServer, ?MODULE)). delete_all_msgs(LUser, LServer, mnesia) -> US = {LUser, LServer}, - F = fun() -> - mnesia:write_lock_table(offline_msg), - lists:foreach( - fun(Msg) -> - mnesia:delete_object(Msg) - end, mnesia:dirty_read({offline_msg, US})) - end, + F = fun () -> + mnesia:write_lock_table(offline_msg), + lists:foreach(fun (Msg) -> mnesia:delete_object(Msg) + end, + mnesia:dirty_read({offline_msg, US})) + end, mnesia:transaction(F); delete_all_msgs(LUser, LServer, odbc) -> Username = ejabberd_odbc:escape(LUser), odbc_queries:del_spool_msg(LServer, Username), - %% TODO: process the output {atomic, ok}. -webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) -> +webadmin_user_parse_query(_, <<"removealloffline">>, + User, Server, _Query) -> case delete_all_msgs(User, Server) of - {aborted, Reason} -> - ?ERROR_MSG("Failed to remove offline messages: ~p", [Reason]), - {stop, error}; - {atomic, ok} -> - ?INFO_MSG("Removed all offline messages for ~s@~s", [User, Server]), - {stop, ok} + {aborted, Reason} -> + ?ERROR_MSG("Failed to remove offline messages: ~p", + [Reason]), + {stop, error}; + {atomic, ok} -> + ?INFO_MSG("Removed all offline messages for ~s@~s", + [User, Server]), + {stop, ok} end; -webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) -> +webadmin_user_parse_query(Acc, _Action, _User, _Server, + _Query) -> Acc. %% Returns as integer the number of offline messages for a given user |