diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 4e58f9ffb..c3c8a2b92 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -518,10 +518,10 @@ select_and_send(#jid{lserver = LServer} = From, DBType). select_and_send(From, To, Start, End, With, RSM, IQ, DBType) -> - {Msgs, Count} = select_and_start(From, To, Start, End, With, - RSM, DBType), + {Msgs, IsComplete, Count} = select_and_start(From, To, Start, End, With, + RSM, DBType), SortedMsgs = lists:keysort(2, Msgs), - send(From, To, SortedMsgs, RSM, Count, IQ). + send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). select_and_start(From, _To, StartUser, End, With, RSM, DB) -> {JidRequestor, Start, With2} = case With of @@ -538,17 +538,17 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, Start, End, With, RSM, mnesia) -> MS = make_matchspec(LUser, LServer, Start, End, With), Msgs = mnesia:dirty_select(archive_msg, MS), - FilteredMsgs = filter_by_rsm(Msgs, RSM), + {FilteredMsgs, IsComplete} = filter_by_rsm(Msgs, RSM), Count = length(Msgs), {lists:map( fun(Msg) -> {Msg#archive_msg.id, jlib:binary_to_integer(Msg#archive_msg.id), msg_to_el(Msg, JidRequestor)} - end, FilteredMsgs), Count}; + end, FilteredMsgs), IsComplete, Count}; select(#jid{luser = LUser, lserver = LServer} = JidRequestor, Start, End, With, RSM, {odbc, Host}) -> - {Query, CountQuery} = make_sql_query(LUser, LServer, + {Query, CountQuery} = make_sql_query(LUser, LServer, Start, End, With, RSM), % XXX TODO from XEP-0313: % To conserve resources, a server MAY place a reasonable limit on @@ -559,6 +559,20 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, case {ejabberd_odbc:sql_query(Host, Query), ejabberd_odbc:sql_query(Host, CountQuery)} of {{selected, _, Res}, {selected, _, [[Count]]}} -> + {Max, Direction} = case RSM of + #rsm_in{max = M, direction = D} -> {M, D}; + _ -> {undefined, undefined} + end, + {Res1, IsComplete} = + if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> + if Direction == before -> + {lists:nthtail(1, Res), false}; + true -> + {lists:sublist(Res, Max), false} + end; + true -> + {Res, true} + end, {lists:map( fun([TS, XML, PeerBin]) -> #xmlel{} = El = xml_stream:parse_element(XML), @@ -569,9 +583,9 @@ select(#jid{luser = LUser, lserver = LServer} = JidRequestor, packet = El, peer = PeerJid}, JidRequestor)} - end, Res), jlib:binary_to_integer(Count)}; - _ -> - {[], 0} + end, Res1), IsComplete, jlib:binary_to_integer(Count)}; + _ -> + {[], false, 0} end. msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, peer = Peer}, @@ -599,14 +613,19 @@ maybe_update_from_to(Pkt, JidRequestor, Peer) -> _ -> Pkt end. -send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> +send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> QID = xml:get_tag_attr_s(<<"queryid">>, SubEl), NS = xml:get_tag_attr_s(<<"xmlns">>, SubEl), QIDAttr = if QID /= <<>> -> - [{<<"queryid">>, QID}]; - true -> - [] - end, + [{<<"queryid">>, QID}]; + true -> + [] + end, + CompleteAttr = if NS == ?NS_MAM_TMP -> + []; + NS == ?NS_MAM_0 -> + [{<<"complete">>, jlib:atom_to_binary(IsComplete)}] + end, Els = lists:map( fun({ID, _IDInt, El}) -> #xmlel{name = <<"message">>, @@ -615,7 +634,7 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> {<<"id">>, ID}|QIDAttr], children = [El]}]} end, Msgs), - RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr, NS), + RSMOut = make_rsm_out(Msgs, RSM, Count, QIDAttr ++ CompleteAttr, NS), case NS of ?NS_MAM_TMP -> lists:foreach( @@ -637,30 +656,30 @@ send(From, To, Msgs, RSM, Count, #iq{sub_el = SubEl} = IQ) -> end. -make_rsm_out(_Msgs, none, _Count, _QIDAttr, ?NS_MAM_TMP) -> +make_rsm_out(_Msgs, none, _Count, _Attrs, ?NS_MAM_TMP) -> []; -make_rsm_out(_Msgs, none, _Count, QIDAttr, ?NS_MAM_0) -> - [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|QIDAttr]}]; -make_rsm_out([], #rsm_in{}, Count, QIDAttr, NS) -> +make_rsm_out(_Msgs, none, _Count, Attrs, ?NS_MAM_0) -> + [#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_MAM_0}|Attrs]}]; +make_rsm_out([], #rsm_in{}, Count, Attrs, NS) -> Tag = if NS == ?NS_MAM_TMP -> <<"query">>; true -> <<"fin">> end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], - children = jlib:rsm_encode(#rsm_out{count = Count})}]; -make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, QIDAttr, NS) -> + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], + children = jlib:rsm_encode(#rsm_out{count = Count})}]; +make_rsm_out([{FirstID, _, _}|_] = Msgs, #rsm_in{}, Count, Attrs, NS) -> {LastID, _, _} = lists:last(Msgs), Tag = if NS == ?NS_MAM_TMP -> <<"query">>; true -> <<"fin">> end, - [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|QIDAttr], - children = jlib:rsm_encode( - #rsm_out{first = FirstID, count = Count, - last = LastID})}]. + [#xmlel{name = Tag, attrs = [{<<"xmlns">>, NS}|Attrs], + children = jlib:rsm_encode( + #rsm_out{first = FirstID, count = Count, + last = LastID})}]. filter_by_rsm(Msgs, none) -> - Msgs; -filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max =< 0 -> - []; + {Msgs, true}; +filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> + {[], true}; filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> NewMsgs = case Direction of aft when ID /= <<"">> -> @@ -683,11 +702,11 @@ filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> filter_by_max(NewMsgs, Max). filter_by_max(Msgs, undefined) -> - Msgs; + {Msgs, true}; filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> - lists:sublist(Msgs, Len); + {lists:sublist(Msgs, Len), length(Msgs) =< Len}; filter_by_max(_Msgs, _Junk) -> - []. + {[], true}. make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> ets:fun2ms( @@ -729,10 +748,10 @@ make_sql_query(LUser, _LServer, Start, End, With, RSM) -> {none, none, <<>>} end, LimitClause = if is_integer(Max), Max >= 0 -> - [<<" limit ">>, jlib:integer_to_binary(Max)]; - true -> - [] - end, + [<<" limit ">>, jlib:integer_to_binary(Max+1)]; + true -> + [] + end, WithClause = case With of {text, <<>>} -> []; diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl index 8cd21d5e9..075a53fe2 100644 --- a/test/ejabberd_SUITE.erl +++ b/test/ejabberd_SUITE.erl @@ -1690,7 +1690,7 @@ mam_query_all(Config, NS) -> if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I, sub_els = []}); true -> - ?recv1(#message{sub_els = [#mam_fin{id = QID}]}) + ?recv1(#message{sub_els = [#mam_fin{complete = true, id = QID}]}) end. mam_query_with(Config, JID, NS) -> @@ -1726,7 +1726,7 @@ mam_query_with(Config, JID, NS) -> if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I, sub_els = []}); true -> - ?recv1(#message{sub_els = [#mam_fin{}]}) + ?recv1(#message{sub_els = [#mam_fin{complete = true}]}) end. maybe_recv_iq_result(?NS_MAM_0, I1) -> @@ -1767,6 +1767,7 @@ mam_query_rsm(Config, NS) -> rsm = #rsm_set{last = Last, count = 5}}]}); true -> ?recv1(#message{sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{last = Last, count = 10}}]}) end, %% Get the next items starting from the `Last`. @@ -1802,15 +1803,16 @@ mam_query_rsm(Config, NS) -> true -> ?recv1(#message{ sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{ count = 10, first = #rsm_first{data = First}}}]}) end, - %% Paging back. Should receive 2 elements: 2, 3. + %% Paging back. Should receive 3 elements: 1, 2, 3. I3 = send(Config, #iq{type = Type, sub_els = [#mam_query{xmlns = NS, - rsm = #rsm_set{max = 2, + rsm = #rsm_set{max = 3, before = First}}]}), maybe_recv_iq_result(NS, I3), lists:foreach( @@ -1827,13 +1829,14 @@ mam_query_rsm(Config, NS) -> [#message{ from = MyJID, to = Peer, body = [Text]}]}]}]}) - end, lists:seq(2, 3)), + end, lists:seq(1, 3)), if NS == ?NS_MAM_TMP -> ?recv1(#iq{type = result, id = I3, sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); true -> ?recv1(#message{ - sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]}) + sub_els = [#mam_fin{complete = true, + rsm = #rsm_set{count = 10}}]}) end, %% Getting the item count. Should be 5 (or 10). I4 = send(Config, @@ -1851,6 +1854,7 @@ mam_query_rsm(Config, NS) -> true -> ?recv1(#message{ sub_els = [#mam_fin{ + complete = false, rsm = #rsm_set{count = 10, first = undefined, last = undefined}}]}) @@ -1882,7 +1886,8 @@ mam_query_rsm(Config, NS) -> sub_els = [#mam_query{xmlns = NS, rsm = #rsm_set{count = 5}}]}); true -> ?recv1(#message{ - sub_els = [#mam_fin{rsm = #rsm_set{count = 10}}]}) + sub_els = [#mam_fin{complete = false, + rsm = #rsm_set{count = 10}}]}) end. client_state_master(Config) -> diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl index 08a2e84ca..987eb7c41 100644 --- a/tools/xmpp_codec.erl +++ b/tools/xmpp_codec.erl @@ -2082,7 +2082,7 @@ encode({mam_result, _, _, _, _} = Result) -> encode_mam_result(Result, []); encode({mam_prefs, _, _, _, _} = Prefs) -> encode_mam_prefs(Prefs, []); -encode({mam_fin, _, _} = Fin) -> +encode({mam_fin, _, _, _, _} = Fin) -> encode_mam_fin(Fin, [{<<"xmlns">>, <<"urn:xmpp:mam:0">>}]); encode({forwarded, _, _} = Forwarded) -> @@ -2308,7 +2308,7 @@ get_ns({rsm_first, _, _}) -> get_ns({rsm_set, _, _, _, _, _, _, _}) -> <<"http://jabber.org/protocol/rsm">>; get_ns({mam_archived, _, _}) -> <<"urn:xmpp:mam:tmp">>; -get_ns({mam_fin, _, _}) -> <<"urn:xmpp:mam:0">>; +get_ns({mam_fin, _, _, _, _}) -> <<"urn:xmpp:mam:0">>; get_ns({forwarded, _, _}) -> <<"urn:xmpp:forward:0">>; get_ns({carbons_disable}) -> <<"urn:xmpp:carbons:2">>; get_ns({carbons_enable}) -> <<"urn:xmpp:carbons:2">>; @@ -2505,7 +2505,7 @@ pp(mam_query, 7) -> pp(mam_archived, 2) -> [by, id]; pp(mam_result, 4) -> [xmlns, queryid, id, sub_els]; pp(mam_prefs, 4) -> [xmlns, default, always, never]; -pp(mam_fin, 2) -> [id, rsm]; +pp(mam_fin, 4) -> [id, rsm, stable, complete]; pp(forwarded, 2) -> [delay, sub_els]; pp(carbons_disable, 0) -> []; pp(carbons_enable, 0) -> []; @@ -3709,9 +3709,10 @@ decode_mam_fin(__TopXMLNS, __IgnoreEls, {xmlel, <<"fin">>, _attrs, _els}) -> Rsm = decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, undefined), - Id = decode_mam_fin_attrs(__TopXMLNS, _attrs, - undefined), - {mam_fin, Id, Rsm}. + {Id, Stable, Complete} = + decode_mam_fin_attrs(__TopXMLNS, _attrs, undefined, + undefined, undefined), + {mam_fin, Id, Rsm, Stable, Complete}. decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [], Rsm) -> Rsm; @@ -3729,16 +3730,37 @@ decode_mam_fin_els(__TopXMLNS, __IgnoreEls, [_ | _els], decode_mam_fin_els(__TopXMLNS, __IgnoreEls, _els, Rsm). decode_mam_fin_attrs(__TopXMLNS, - [{<<"queryid">>, _val} | _attrs], _Id) -> - decode_mam_fin_attrs(__TopXMLNS, _attrs, _val); -decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id) -> - decode_mam_fin_attrs(__TopXMLNS, _attrs, Id); -decode_mam_fin_attrs(__TopXMLNS, [], Id) -> - decode_mam_fin_attr_queryid(__TopXMLNS, Id). + [{<<"queryid">>, _val} | _attrs], _Id, Stable, + Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, _val, Stable, + Complete); +decode_mam_fin_attrs(__TopXMLNS, + [{<<"stable">>, _val} | _attrs], Id, _Stable, + Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, _val, + Complete); +decode_mam_fin_attrs(__TopXMLNS, + [{<<"complete">>, _val} | _attrs], Id, Stable, + _Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable, + _val); +decode_mam_fin_attrs(__TopXMLNS, [_ | _attrs], Id, + Stable, Complete) -> + decode_mam_fin_attrs(__TopXMLNS, _attrs, Id, Stable, + Complete); +decode_mam_fin_attrs(__TopXMLNS, [], Id, Stable, + Complete) -> + {decode_mam_fin_attr_queryid(__TopXMLNS, Id), + decode_mam_fin_attr_stable(__TopXMLNS, Stable), + decode_mam_fin_attr_complete(__TopXMLNS, Complete)}. -encode_mam_fin({mam_fin, Id, Rsm}, _xmlns_attrs) -> +encode_mam_fin({mam_fin, Id, Rsm, Stable, Complete}, + _xmlns_attrs) -> _els = lists:reverse('encode_mam_fin_$rsm'(Rsm, [])), - _attrs = encode_mam_fin_attr_queryid(Id, _xmlns_attrs), + _attrs = encode_mam_fin_attr_complete(Complete, + encode_mam_fin_attr_stable(Stable, + encode_mam_fin_attr_queryid(Id, + _xmlns_attrs))), {xmlel, <<"fin">>, _attrs, _els}. 'encode_mam_fin_$rsm'(undefined, _acc) -> _acc; @@ -3755,6 +3777,35 @@ encode_mam_fin_attr_queryid(undefined, _acc) -> _acc; encode_mam_fin_attr_queryid(_val, _acc) -> [{<<"queryid">>, _val} | _acc]. +decode_mam_fin_attr_stable(__TopXMLNS, undefined) -> + undefined; +decode_mam_fin_attr_stable(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"stable">>, <<"fin">>, __TopXMLNS}}); + _res -> _res + end. + +encode_mam_fin_attr_stable(undefined, _acc) -> _acc; +encode_mam_fin_attr_stable(_val, _acc) -> + [{<<"stable">>, enc_bool(_val)} | _acc]. + +decode_mam_fin_attr_complete(__TopXMLNS, undefined) -> + undefined; +decode_mam_fin_attr_complete(__TopXMLNS, _val) -> + case catch dec_bool(_val) of + {'EXIT', _} -> + erlang:error({xmpp_codec, + {bad_attr_value, <<"complete">>, <<"fin">>, + __TopXMLNS}}); + _res -> _res + end. + +encode_mam_fin_attr_complete(undefined, _acc) -> _acc; +encode_mam_fin_attr_complete(_val, _acc) -> + [{<<"complete">>, enc_bool(_val)} | _acc]. + decode_mam_prefs(__TopXMLNS, __IgnoreEls, {xmlel, <<"prefs">>, _attrs, _els}) -> {Never, Always} = decode_mam_prefs_els(__TopXMLNS, diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl index fa8e5e74d..7996f6a11 100644 --- a/tools/xmpp_codec.hrl +++ b/tools/xmpp_codec.hrl @@ -288,7 +288,9 @@ max :: non_neg_integer()}). -record(mam_fin, {id :: binary(), - rsm :: #rsm_set{}}). + rsm :: #rsm_set{}, + stable :: any(), + complete :: any()}). -record(vcard_tel, {home = false :: boolean(), work = false :: boolean(), diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec index 326d1de36..38508ce6c 100644 --- a/tools/xmpp_codec.spec +++ b/tools/xmpp_codec.spec @@ -2210,8 +2210,14 @@ -xml(mam_fin, #elem{name = <<"fin">>, xmlns = <<"urn:xmpp:mam:0">>, - result = {mam_fin, '$id', '$rsm'}, - attrs = [#attr{name = <<"queryid">>, label = '$id'}], + result = {mam_fin, '$id', '$rsm', '$stable', '$complete'}, + attrs = [#attr{name = <<"queryid">>, label = '$id'}, + #attr{name = <<"stable">>, label = '$stable', + dec = {dec_bool, []}, + enc = {enc_bool, []}}, + #attr{name = <<"complete">>, label = '$complete', + dec = {dec_bool, []}, + enc = {enc_bool, []}}], refs = [#ref{name = rsm_set, min = 0, max = 1, label = '$rsm'}]}). -xml(forwarded,