1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% @author Uvarov Michael <arcusfelis@gmail.com> |
3 |
|
%%% @copyright (C) 2013, Uvarov Michael |
4 |
|
%%% @doc General functions for MAM. |
5 |
|
%%% @end |
6 |
|
%%%------------------------------------------------------------------- |
7 |
|
-module(mod_mam_utils). |
8 |
|
%% Time |
9 |
|
-export([maybe_microseconds/1]). |
10 |
|
|
11 |
|
%% UID |
12 |
|
-export([get_or_generate_mam_id/1, |
13 |
|
generate_message_id/1, |
14 |
|
encode_compact_uuid/2, |
15 |
|
decode_compact_uuid/1, |
16 |
|
mess_id_to_external_binary/1, |
17 |
|
external_binary_to_mess_id/1, |
18 |
|
wrapper_id/0]). |
19 |
|
|
20 |
|
%% XML |
21 |
|
-export([maybe_add_arcid_elems/4, |
22 |
|
maybe_log_deprecation/1, |
23 |
|
is_arcid_elem_for/3, |
24 |
|
replace_arcid_elem/4, |
25 |
|
replace_x_user_element/4, |
26 |
|
append_arcid_elem/4, |
27 |
|
delete_arcid_elem/3, |
28 |
|
delete_x_user_element/1, |
29 |
|
packet_to_x_user_jid/1, |
30 |
|
get_one_of_path/2, |
31 |
|
get_one_of_path/3, |
32 |
|
is_archivable_message/4, |
33 |
|
has_message_retraction/2, |
34 |
|
get_retract_id/2, |
35 |
|
get_origin_id/1, |
36 |
|
is_groupchat/1, |
37 |
|
should_page_be_flipped/1, |
38 |
|
tombstone/2, |
39 |
|
wrap_message/6, |
40 |
|
wrap_message/7, |
41 |
|
result_set/4, |
42 |
|
result_query/2, |
43 |
|
result_prefs/4, |
44 |
|
make_fin_element/7, |
45 |
|
parse_prefs/1, |
46 |
|
form_borders_decode/1, |
47 |
|
form_decode_optimizations/1, |
48 |
|
is_mam_result_message/1, |
49 |
|
make_metadata_element/0, |
50 |
|
make_metadata_element/4, |
51 |
|
features/2]). |
52 |
|
|
53 |
|
%% Forms |
54 |
|
-export([ |
55 |
|
message_form/3, |
56 |
|
form_to_text/1 |
57 |
|
]). |
58 |
|
|
59 |
|
%% Text search |
60 |
|
-export([ |
61 |
|
normalize_search_text/1, |
62 |
|
normalize_search_text/2, |
63 |
|
packet_to_search_body/2, |
64 |
|
has_full_text_search/2 |
65 |
|
]). |
66 |
|
|
67 |
|
%% JID serialization |
68 |
|
-export([jid_to_opt_binary/2, |
69 |
|
expand_minified_jid/2]). |
70 |
|
|
71 |
|
%% Other |
72 |
|
-export([maybe_integer/2, |
73 |
|
maybe_min/2, |
74 |
|
maybe_max/2, |
75 |
|
maybe_last/1, |
76 |
|
apply_start_border/2, |
77 |
|
apply_end_border/2, |
78 |
|
bare_jid/1, |
79 |
|
full_jid/1, |
80 |
|
calculate_msg_id_borders/3, |
81 |
|
calculate_msg_id_borders/4, |
82 |
|
maybe_encode_compact_uuid/2, |
83 |
|
wait_shaper/4, |
84 |
|
check_for_item_not_found/3, |
85 |
|
maybe_reverse_messages/2, |
86 |
|
get_msg_id_and_timestamp/1, |
87 |
|
is_mam_muc_enabled/2]). |
88 |
|
|
89 |
|
%% Ejabberd |
90 |
|
-export([send_message/4, |
91 |
|
maybe_set_client_xmlns/2, |
92 |
|
is_jid_in_user_roster/3]). |
93 |
|
|
94 |
|
%% Shared logic |
95 |
|
-export([check_result_for_policy_violation/2, |
96 |
|
lookup/3, |
97 |
|
lookup_first_and_last_messages/4, |
98 |
|
lookup_first_and_last_messages/5, |
99 |
|
incremental_delete_domain/5, |
100 |
|
db_message_codec/2, db_jid_codec/2]). |
101 |
|
|
102 |
|
-callback extra_fin_element(mongooseim:host_type(), |
103 |
|
mam_iq:lookup_params(), |
104 |
|
exml:element()) -> exml:element(). |
105 |
|
|
106 |
|
-ignore_xref([behaviour_info/1, append_arcid_elem/4, delete_arcid_elem/3, |
107 |
|
get_one_of_path/3, is_arcid_elem_for/3, maybe_encode_compact_uuid/2, |
108 |
|
maybe_last/1, result_query/2, send_message/4, wrap_message/7, wrapper_id/0]). |
109 |
|
|
110 |
|
%-define(MAM_INLINE_UTILS, true). |
111 |
|
|
112 |
|
-ifdef(MAM_INLINE_UTILS). |
113 |
|
-compile({inline, [ |
114 |
|
is_valid_message/4, |
115 |
|
is_valid_message_type/3, |
116 |
|
encode_compact_uuid/2, |
117 |
|
get_one_of_path/3, |
118 |
|
delay/2, |
119 |
|
forwarded/3, |
120 |
|
result/4, |
121 |
|
valid_behavior/1]}). |
122 |
|
-endif. |
123 |
|
|
124 |
|
-include("jlib.hrl"). |
125 |
|
-include_lib("exml/include/exml.hrl"). |
126 |
|
|
127 |
|
-ifdef(TEST). |
128 |
|
-include_lib("eunit/include/eunit.hrl"). |
129 |
|
-export([is_valid_message/4]). |
130 |
|
-endif. |
131 |
|
|
132 |
|
-include("mod_mam.hrl"). |
133 |
|
-include("mongoose_rsm.hrl"). |
134 |
|
-include("mongoose_ns.hrl"). |
135 |
|
|
136 |
|
-define(MAYBE_BIN(X), (is_binary(X) orelse (X) =:= undefined)). |
137 |
|
|
138 |
|
-export_type([direction/0, retraction_id/0, retraction_info/0]). |
139 |
|
|
140 |
|
%% ---------------------------------------------------------------------- |
141 |
|
%% Datetime types |
142 |
|
-type ne_binary() :: <<_:8, _:_*8>>. |
143 |
|
-type iso8601_datetime_binary() :: ne_binary(). |
144 |
|
%% Microseconds from 01.01.1970 |
145 |
|
-type unix_timestamp() :: mod_mam:unix_timestamp(). |
146 |
|
|
147 |
|
-type archive_behaviour() :: mod_mam:archive_behaviour(). |
148 |
|
-type archive_behaviour_bin() :: binary(). % `<<"roster">> | <<"always">> | <<"never">>'. |
149 |
|
|
150 |
|
-type direction() :: incoming | outgoing. |
151 |
|
-type retraction_id() :: {origin_id | stanza_id, binary()}. |
152 |
|
-type retraction_info() :: #{retract_on := origin_id | stanza_id, |
153 |
|
packet := exml:element(), |
154 |
|
message_id := mod_mam:message_id(), |
155 |
|
origin_id := null | binary()}. |
156 |
|
|
157 |
|
%% ----------------------------------------------------------------------- |
158 |
|
%% Time |
159 |
|
|
160 |
|
%% @doc Return a unix timestamp in microseconds. |
161 |
|
%% |
162 |
|
%% "maybe" means, that the function may return `undefined'. |
163 |
|
%% @end |
164 |
|
-spec maybe_microseconds(iso8601_datetime_binary()) -> unix_timestamp(); |
165 |
|
(<<>>) -> undefined. |
166 |
:-( |
maybe_microseconds(<<>>) -> undefined; |
167 |
|
maybe_microseconds(ISODateTime) -> |
168 |
201 |
try calendar:rfc3339_to_system_time(binary_to_list(ISODateTime), [{unit, microsecond}]) |
169 |
12 |
catch error:_Error -> undefined |
170 |
|
end. |
171 |
|
|
172 |
|
%% ----------------------------------------------------------------------- |
173 |
|
%% UID |
174 |
|
|
175 |
|
-spec get_or_generate_mam_id(mongoose_acc:t()) -> integer(). |
176 |
|
get_or_generate_mam_id(Acc) -> |
177 |
2313 |
case mongoose_acc:get(mam, mam_id, undefined, Acc) of |
178 |
|
undefined -> |
179 |
2292 |
CandidateStamp = mongoose_acc:timestamp(Acc), |
180 |
2292 |
generate_message_id(CandidateStamp); |
181 |
|
ExtMessId -> |
182 |
21 |
mod_mam_utils:external_binary_to_mess_id(ExtMessId) |
183 |
|
end. |
184 |
|
|
185 |
|
-spec generate_message_id(integer()) -> integer(). |
186 |
|
generate_message_id(CandidateStamp) -> |
187 |
2903 |
NodeNum = mongoose_node_num:node_num(), |
188 |
2903 |
UniqueStamp = mongoose_mam_id:next_unique(CandidateStamp), |
189 |
2903 |
encode_compact_uuid(UniqueStamp, NodeNum). |
190 |
|
|
191 |
|
%% @doc Create a message ID (UID). |
192 |
|
%% |
193 |
|
%% It removes a leading 0 from 64-bit binary representation. |
194 |
|
%% It puts node id as a last byte. |
195 |
|
%% The maximum date, that can be encoded is `{{4253, 5, 31}, {22, 20, 37}}'. |
196 |
|
-spec encode_compact_uuid(integer(), integer()) -> integer(). |
197 |
|
encode_compact_uuid(Microseconds, NodeNum) |
198 |
|
when is_integer(Microseconds), is_integer(NodeNum) -> |
199 |
9248 |
(Microseconds bsl 8) + NodeNum. |
200 |
|
|
201 |
|
|
202 |
|
%% @doc Extract date and node id from a message id. |
203 |
|
-spec decode_compact_uuid(integer()) -> {integer(), byte()}. |
204 |
|
decode_compact_uuid(Id) -> |
205 |
4614 |
Microseconds = Id bsr 8, |
206 |
4614 |
NodeNum = Id band 255, |
207 |
4614 |
{Microseconds, NodeNum}. |
208 |
|
|
209 |
|
|
210 |
|
%% @doc Encode a message ID to pass it to the user. |
211 |
|
-spec mess_id_to_external_binary(integer()) -> binary(). |
212 |
|
mess_id_to_external_binary(MessID) when is_integer(MessID) -> |
213 |
9456 |
integer_to_binary(MessID, 32). |
214 |
|
|
215 |
|
%% @doc Decode a message ID received from the user. |
216 |
|
-spec external_binary_to_mess_id(binary()) -> integer(). |
217 |
|
external_binary_to_mess_id(BExtMessID) when is_binary(BExtMessID) -> |
218 |
560 |
try binary_to_integer(BExtMessID, 32) |
219 |
21 |
catch error:badarg -> throw(invalid_stanza_id) |
220 |
|
end. |
221 |
|
|
222 |
|
%% ----------------------------------------------------------------------- |
223 |
|
%% XML |
224 |
|
|
225 |
|
-spec maybe_add_arcid_elems(To :: jid:simple_jid() | jid:jid(), |
226 |
|
MessID :: binary(), Packet :: exml:element(), |
227 |
|
AddStanzaid :: boolean()) -> |
228 |
|
AlteredPacket :: exml:element(). |
229 |
|
maybe_add_arcid_elems(To, MessID, Packet, AddStanzaid) -> |
230 |
1798 |
BareTo = jid:to_bare_binary(To), |
231 |
1798 |
case AddStanzaid of |
232 |
|
true -> |
233 |
1784 |
replace_arcid_elem(<<"stanza-id">>, BareTo, MessID, Packet); |
234 |
14 |
_ -> Packet |
235 |
|
end. |
236 |
|
|
237 |
|
maybe_log_deprecation(_IQ) -> |
238 |
1898 |
ok. %% May be reused for future MAM versions. |
239 |
|
|
240 |
|
%% @doc Return true, if the first element points on `By'. |
241 |
|
-spec is_arcid_elem_for(ElemName :: binary(), exml:element(), By :: binary()) -> boolean(). |
242 |
|
is_arcid_elem_for(<<"archived">>, #xmlel{name = <<"archived">>, attrs=As}, By) -> |
243 |
:-( |
lists:member({<<"by">>, By}, As); |
244 |
|
is_arcid_elem_for(<<"stanza-id">>, #xmlel{name = <<"stanza-id">>, attrs=As}, By) -> |
245 |
85 |
lists:member({<<"by">>, By}, As) andalso |
246 |
:-( |
lists:member({<<"xmlns">>, ?NS_STANZAID}, As); |
247 |
|
is_arcid_elem_for(_, _, _) -> |
248 |
2098 |
false. |
249 |
|
|
250 |
|
-spec replace_arcid_elem(ElemName :: binary(), By :: binary(), Id :: binary(), |
251 |
|
Packet :: exml:element()) -> exml:element(). |
252 |
|
replace_arcid_elem(ElemName, By, Id, Packet) -> |
253 |
1791 |
append_arcid_elem(ElemName, By, Id, |
254 |
|
delete_arcid_elem(ElemName, By, Packet)). |
255 |
|
|
256 |
|
-spec append_arcid_elem(ElemName :: binary(), By :: binary(), Id :: binary(), |
257 |
|
Packet :: exml:element()) ->exml:element(). |
258 |
|
append_arcid_elem(<<"stanza-id">>, By, Id, Packet) -> |
259 |
1791 |
Archived = #xmlel{ |
260 |
|
name = <<"stanza-id">>, |
261 |
|
attrs=[{<<"by">>, By}, {<<"id">>, Id}, {<<"xmlns">>, ?NS_STANZAID}]}, |
262 |
1791 |
xml:append_subtags(Packet, [Archived]); |
263 |
|
append_arcid_elem(ElemName, By, Id, Packet) -> |
264 |
:-( |
Archived = #xmlel{ |
265 |
|
name = ElemName, |
266 |
|
attrs=[{<<"by">>, By}, {<<"id">>, Id}]}, |
267 |
:-( |
xml:append_subtags(Packet, [Archived]). |
268 |
|
|
269 |
|
-spec delete_arcid_elem(ElemName :: binary(), By :: binary(), exml:element()) -> exml:element(). |
270 |
|
delete_arcid_elem(ElemName, By, Packet=#xmlel{children=Cs}) -> |
271 |
1791 |
Packet#xmlel{children=[C || C <- Cs, not is_arcid_elem_for(ElemName, C, By)]}. |
272 |
|
|
273 |
|
|
274 |
|
is_x_user_element(#xmlel{name = <<"x">>, attrs = As}) -> |
275 |
427 |
lists:member({<<"xmlns">>, ?NS_MUC_USER}, As); |
276 |
|
is_x_user_element(_) -> |
277 |
883 |
false. |
278 |
|
|
279 |
|
-spec replace_x_user_element(FromJID :: jid:jid(), Role :: mod_muc:role(), |
280 |
|
Affiliation :: mod_muc:affiliation(), exml:element()) -> exml:element(). |
281 |
|
replace_x_user_element(FromJID, Role, Affiliation, Packet) -> |
282 |
611 |
append_x_user_element(FromJID, Role, Affiliation, |
283 |
|
delete_x_user_element(Packet)). |
284 |
|
|
285 |
|
append_x_user_element(FromJID, Role, Affiliation, Packet) -> |
286 |
611 |
ItemElem = x_user_item(FromJID, Role, Affiliation), |
287 |
611 |
X = #xmlel{ |
288 |
|
name = <<"x">>, |
289 |
|
attrs = [{<<"xmlns">>, ?NS_MUC_USER}], |
290 |
|
children = [ItemElem]}, |
291 |
611 |
xml:append_subtags(Packet, [X]). |
292 |
|
|
293 |
|
x_user_item(FromJID, Role, Affiliation) -> |
294 |
611 |
#xmlel{ |
295 |
|
name = <<"item">>, |
296 |
|
attrs = [{<<"affiliation">>, atom_to_binary(Affiliation, latin1)}, |
297 |
|
{<<"jid">>, jid:to_binary(FromJID)}, |
298 |
|
{<<"role">>, atom_to_binary(Role, latin1)}]}. |
299 |
|
|
300 |
|
-spec delete_x_user_element(exml:element()) -> exml:element(). |
301 |
|
delete_x_user_element(Packet=#xmlel{children=Cs}) -> |
302 |
711 |
Packet#xmlel{children=[C || C <- Cs, not is_x_user_element(C)]}. |
303 |
|
|
304 |
|
-spec packet_to_x_user_jid(exml:element()) -> jid:jid() | error | undefined. |
305 |
|
packet_to_x_user_jid(#xmlel{children=Cs}) -> |
306 |
122 |
case [C || C <- Cs, is_x_user_element(C)] of |
307 |
14 |
[] -> undefined; |
308 |
|
[X|_] -> |
309 |
108 |
case exml_query:path(X, [{element, <<"item">>}, {attr, <<"jid">>}]) of |
310 |
:-( |
undefined -> undefined; |
311 |
108 |
BinaryJid -> jid:from_binary(BinaryJid) |
312 |
|
end |
313 |
|
end. |
314 |
|
|
315 |
|
-spec get_one_of_path(_, list(T)) -> T when T :: any(). |
316 |
|
get_one_of_path(Elem, List) -> |
317 |
1338 |
get_one_of_path(Elem, List, <<>>). |
318 |
|
|
319 |
|
|
320 |
|
-spec get_one_of_path(_, list(T), T) -> T when T :: any(). |
321 |
|
get_one_of_path(Elem, [H|T], Def) -> |
322 |
2004 |
case exml_query:path(Elem, H) of |
323 |
1332 |
undefined -> get_one_of_path(Elem, T, Def); |
324 |
672 |
Val -> Val |
325 |
|
end; |
326 |
|
get_one_of_path(_Elem, [], Def) -> |
327 |
666 |
Def. |
328 |
|
|
329 |
|
|
330 |
|
%% @doc In order to be archived, the message must be of type "normal", "chat" or "groupchat". |
331 |
|
%% It also must include a body or chat marker, as long as it doesn't include |
332 |
|
%% "result", "delay" or "no-store" elements. |
333 |
|
%% @end |
334 |
|
-spec is_archivable_message(module(), direction(), exml:element(), boolean()) -> boolean(). |
335 |
|
is_archivable_message(Mod, Dir, Packet=#xmlel{name = <<"message">>}, ArchiveChatMarkers) -> |
336 |
6267 |
Type = exml_query:attr(Packet, <<"type">>, <<"normal">>), |
337 |
6267 |
is_valid_message_type(Mod, Dir, Type) andalso |
338 |
6079 |
is_valid_message(Mod, Dir, Packet, ArchiveChatMarkers); |
339 |
|
is_archivable_message(_, _, _, _) -> |
340 |
8776 |
false. |
341 |
|
|
342 |
284 |
is_valid_message_type(_, _, <<"normal">>) -> true; |
343 |
3113 |
is_valid_message_type(_, _, <<"chat">>) -> true; |
344 |
1578 |
is_valid_message_type(mod_inbox, _, <<"groupchat">>) -> true; |
345 |
1104 |
is_valid_message_type(_, incoming, <<"groupchat">>) -> true; |
346 |
188 |
is_valid_message_type(_, _, _) -> false. |
347 |
|
|
348 |
|
is_valid_message(_Mod, _Dir, Packet, ArchiveChatMarkers) -> |
349 |
6079 |
Body = exml_query:subelement(Packet, <<"body">>, false), |
350 |
6079 |
ChatMarker = ArchiveChatMarkers |
351 |
2638 |
andalso has_chat_marker(Packet), |
352 |
6079 |
Retract = get_retract_id(Packet) =/= none, |
353 |
|
%% Used in MAM |
354 |
6079 |
Result = exml_query:subelement(Packet, <<"result">>, false), |
355 |
|
%% Used in mod_offline |
356 |
6079 |
Delay = exml_query:subelement(Packet, <<"delay">>, false), |
357 |
|
%% Message Processing Hints (XEP-0334) |
358 |
6079 |
NoStore = exml_query:path(Packet, [{element_with_ns, <<"no-store">>, ?NS_HINTS}], false), |
359 |
|
%% Message Processing Hints (XEP-0334) |
360 |
6079 |
Store = exml_query:path(Packet, [{element_with_ns, <<"store">>, ?NS_HINTS}], false), |
361 |
|
|
362 |
6079 |
has_any([Store, Body, ChatMarker, Retract]) andalso not has_any([Result, Delay, NoStore]). |
363 |
|
|
364 |
|
has_any(Elements) -> |
365 |
11902 |
lists:any(fun(El) -> El =/= false end, Elements). |
366 |
|
|
367 |
|
has_chat_marker(Packet) -> |
368 |
2638 |
mongoose_chat_markers:has_chat_markers(Packet). |
369 |
|
|
370 |
|
-spec get_retract_id(false, exml:element()) -> none; |
371 |
|
(true, exml:element()) -> none | retraction_id(). |
372 |
|
get_retract_id(true = _Enabled, Packet) -> |
373 |
7865 |
get_retract_id(Packet); |
374 |
|
get_retract_id(false, _Packet) -> |
375 |
28 |
none. |
376 |
|
|
377 |
|
-spec get_retract_id(exml:element()) -> none | retraction_id(). |
378 |
|
get_retract_id(Packet) -> |
379 |
13944 |
case exml_query:path(Packet, [{element_with_ns, <<"apply-to">>, ?NS_FASTEN}], none) of |
380 |
13790 |
none -> none; |
381 |
|
Fasten -> |
382 |
154 |
case {exml_query:path(Fasten, [{element, <<"retract">>}, {attr, <<"xmlns">>}], none), |
383 |
|
exml_query:path(Fasten, [{attr, <<"id">>}], none)} of |
384 |
:-( |
{none, _} -> none; |
385 |
14 |
{_, none} -> none; |
386 |
98 |
{?NS_RETRACT, OriginId} -> {origin_id, OriginId}; |
387 |
42 |
{?NS_ESL_RETRACT, StanzaId} -> {stanza_id, StanzaId} |
388 |
|
end |
389 |
|
end. |
390 |
|
|
391 |
|
get_origin_id(Packet) -> |
392 |
3218 |
exml_query:path(Packet, [{element_with_ns, <<"origin-id">>, ?NS_STANZAID}, |
393 |
|
{attr, <<"id">>}], none). |
394 |
|
|
395 |
|
is_groupchat(<<"groupchat">>) -> |
396 |
147 |
true; |
397 |
|
is_groupchat(_) -> |
398 |
2166 |
false. |
399 |
|
|
400 |
|
-spec should_page_be_flipped(exml:element()) -> boolean(). |
401 |
|
should_page_be_flipped(Packet) -> |
402 |
1338 |
case exml_query:path(Packet, [{element, <<"flip-page">>}], none) of |
403 |
1296 |
none -> false; |
404 |
42 |
_ -> true |
405 |
|
end. |
406 |
|
|
407 |
|
-spec maybe_reverse_messages(mam_iq:lookup_params(), [mod_mam:message_row()]) -> |
408 |
|
[mod_mam:message_row()]. |
409 |
42 |
maybe_reverse_messages(#{flip_page := true}, Messages) -> lists:reverse(Messages); |
410 |
1226 |
maybe_reverse_messages(#{flip_page := false}, Messages) -> Messages. |
411 |
|
|
412 |
|
-spec get_msg_id_and_timestamp(mod_mam:message_row()) -> {binary(), binary()}. |
413 |
|
get_msg_id_and_timestamp(#{id := MsgID}) -> |
414 |
56 |
{Microseconds, _NodeMessID} = decode_compact_uuid(MsgID), |
415 |
56 |
TS = calendar:system_time_to_rfc3339(Microseconds, [{offset, "Z"}, {unit, microsecond}]), |
416 |
56 |
ExtID = mess_id_to_external_binary(MsgID), |
417 |
56 |
{ExtID, list_to_binary(TS)}. |
418 |
|
|
419 |
|
tombstone(RetractionInfo = #{packet := Packet}, LocJid) -> |
420 |
42 |
Packet#xmlel{children = [retracted_element(RetractionInfo, LocJid)]}. |
421 |
|
|
422 |
|
-spec retracted_element(retraction_info(), jid:jid()) -> exml:element(). |
423 |
|
retracted_element(#{retract_on := origin_id, |
424 |
|
origin_id := OriginID}, _LocJid) -> |
425 |
21 |
Timestamp = calendar:system_time_to_rfc3339(erlang:system_time(second), [{offset, "Z"}]), |
426 |
21 |
#xmlel{name = <<"retracted">>, |
427 |
|
attrs = [{<<"xmlns">>, ?NS_RETRACT}, |
428 |
|
{<<"stamp">>, list_to_binary(Timestamp)}], |
429 |
|
children = [#xmlel{name = <<"origin-id">>, |
430 |
|
attrs = [{<<"xmlns">>, ?NS_STANZAID}, |
431 |
|
{<<"id">>, OriginID}]} |
432 |
|
]}; |
433 |
|
retracted_element(#{retract_on := stanza_id, |
434 |
|
message_id := MessID} = Env, LocJid) -> |
435 |
21 |
Timestamp = calendar:system_time_to_rfc3339(erlang:system_time(second), [{offset, "Z"}]), |
436 |
21 |
StanzaID = mod_mam_utils:mess_id_to_external_binary(MessID), |
437 |
21 |
MaybeOriginId = maybe_append_origin_id(Env), |
438 |
21 |
#xmlel{name = <<"retracted">>, |
439 |
|
attrs = [{<<"xmlns">>, ?NS_ESL_RETRACT}, |
440 |
|
{<<"stamp">>, list_to_binary(Timestamp)}], |
441 |
|
children = [#xmlel{name = <<"stanza-id">>, |
442 |
|
attrs = [{<<"xmlns">>, ?NS_STANZAID}, |
443 |
|
{<<"id">>, StanzaID}, |
444 |
|
{<<"by">>, jid:to_bare_binary(LocJid)}]} | |
445 |
|
MaybeOriginId |
446 |
|
]}. |
447 |
|
|
448 |
|
-spec maybe_append_origin_id(retraction_info()) -> [exml:element()]. |
449 |
|
maybe_append_origin_id(#{origin_id := OriginID}) when is_binary(OriginID), <<>> =/= OriginID -> |
450 |
21 |
[#xmlel{name = <<"origin-id">>, attrs = [{<<"xmlns">>, ?NS_STANZAID}, {<<"id">>, OriginID}]}]; |
451 |
|
maybe_append_origin_id(_) -> |
452 |
:-( |
[]. |
453 |
|
|
454 |
|
%% @doc Forms `<forwarded/>' element, according to the XEP. |
455 |
|
-spec wrap_message(MamNs :: binary(), Packet :: exml:element(), QueryID :: binary(), |
456 |
|
MessageUID :: term(), TS :: jlib:rfc3339_string(), |
457 |
|
SrcJID :: jid:jid()) -> Wrapper :: exml:element(). |
458 |
|
wrap_message(MamNs, Packet, QueryID, MessageUID, TS, SrcJID) -> |
459 |
4374 |
wrap_message(MamNs, Packet, QueryID, MessageUID, wrapper_id(), TS, SrcJID). |
460 |
|
|
461 |
|
-spec wrap_message(MamNs :: binary(), Packet :: exml:element(), QueryID :: binary(), |
462 |
|
MessageUID :: term(), WrapperI :: binary(), |
463 |
|
TS :: jlib:rfc3339_string(), |
464 |
|
SrcJID :: jid:jid()) -> Wrapper :: exml:element(). |
465 |
|
wrap_message(MamNs, Packet, QueryID, MessageUID, WrapperID, TS, SrcJID) -> |
466 |
4374 |
#xmlel{ name = <<"message">>, |
467 |
|
attrs = [{<<"id">>, WrapperID}], |
468 |
|
children = [result(MamNs, QueryID, MessageUID, |
469 |
|
[forwarded(Packet, TS, SrcJID)])] }. |
470 |
|
|
471 |
|
-spec forwarded(exml:element(), jlib:rfc3339_string(), jid:jid()) |
472 |
|
-> exml:element(). |
473 |
|
forwarded(Packet, TS, SrcJID) -> |
474 |
4374 |
#xmlel{ |
475 |
|
name = <<"forwarded">>, |
476 |
|
attrs = [{<<"xmlns">>, ?NS_FORWARD}], |
477 |
|
%% Two places to include SrcJID: |
478 |
|
%% - delay.from - optional XEP-0297 (TODO: depricate adding it?) |
479 |
|
%% - message.from - required XEP-0313 |
480 |
|
%% Also, mod_mam_muc will replace it again with SrcJID |
481 |
|
children = [delay(TS, SrcJID), replace_from_attribute(SrcJID, Packet)]}. |
482 |
|
|
483 |
|
-spec delay(jlib:rfc3339_string(), jid:jid()) -> exml:element(). |
484 |
|
delay(TS, SrcJID) -> |
485 |
4374 |
jlib:timestamp_to_xml(TS, SrcJID, <<>>). |
486 |
|
|
487 |
|
replace_from_attribute(From, Packet=#xmlel{attrs = Attrs}) -> |
488 |
4374 |
Attrs1 = lists:keydelete(<<"from">>, 1, Attrs), |
489 |
4374 |
Attrs2 = [{<<"from">>, jid:to_binary(From)} | Attrs1], |
490 |
4374 |
Packet#xmlel{attrs = Attrs2}. |
491 |
|
|
492 |
|
%% @doc Generates tag `<result />'. |
493 |
|
%% This element will be added in each forwarded message. |
494 |
|
-spec result(binary(), _, MessageUID :: binary(), Children :: [exml:element(), ...]) |
495 |
|
-> exml:element(). |
496 |
|
result(MamNs, QueryID, MessageUID, Children) when is_list(Children) -> |
497 |
|
%% <result xmlns='urn:xmpp:mam:tmp' queryid='f27' id='28482-98726-73623' /> |
498 |
4374 |
#xmlel{ |
499 |
|
name = <<"result">>, |
500 |
3240 |
attrs = [{<<"queryid">>, QueryID} || QueryID =/= undefined, QueryID =/= <<>>] ++ |
501 |
|
[{<<"xmlns">>, MamNs}, |
502 |
|
{<<"id">>, MessageUID}], |
503 |
|
children = Children}. |
504 |
|
|
505 |
|
|
506 |
|
%% @doc Generates `<set />' tag. |
507 |
|
%% |
508 |
|
%% This element will be added into "iq/query". |
509 |
|
%% @end |
510 |
|
-spec result_set(FirstId :: binary() | undefined, |
511 |
|
LastId :: binary() | undefined, |
512 |
|
FirstIndexI :: non_neg_integer() | undefined, |
513 |
|
CountI :: non_neg_integer() | undefined) -> exml:element(). |
514 |
|
result_set(FirstId, LastId, undefined, undefined) |
515 |
|
when ?MAYBE_BIN(FirstId), ?MAYBE_BIN(LastId) -> |
516 |
|
%% Simple response |
517 |
1056 |
FirstEl = [#xmlel{name = <<"first">>, |
518 |
|
children = [#xmlcdata{content = FirstId}] |
519 |
|
} |
520 |
1056 |
|| FirstId =/= undefined], |
521 |
1056 |
LastEl = [#xmlel{name = <<"last">>, |
522 |
|
children = [#xmlcdata{content = LastId}] |
523 |
|
} |
524 |
1056 |
|| LastId =/= undefined], |
525 |
1056 |
#xmlel{ |
526 |
|
name = <<"set">>, |
527 |
|
attrs = [{<<"xmlns">>, ?NS_RSM}], |
528 |
|
children = FirstEl ++ LastEl}; |
529 |
|
result_set(FirstId, LastId, FirstIndexI, CountI) |
530 |
|
when ?MAYBE_BIN(FirstId), ?MAYBE_BIN(LastId) -> |
531 |
1093 |
FirstEl = [#xmlel{name = <<"first">>, |
532 |
|
attrs = [{<<"index">>, integer_to_binary(FirstIndexI)}], |
533 |
|
children = [#xmlcdata{content = FirstId}] |
534 |
|
} |
535 |
1093 |
|| FirstId =/= undefined], |
536 |
1093 |
LastEl = [#xmlel{name = <<"last">>, |
537 |
|
children = [#xmlcdata{content = LastId}] |
538 |
|
} |
539 |
1093 |
|| LastId =/= undefined], |
540 |
1093 |
CountEl = #xmlel{ |
541 |
|
name = <<"count">>, |
542 |
|
children = [#xmlcdata{content = integer_to_binary(CountI)}]}, |
543 |
1093 |
#xmlel{ |
544 |
|
name = <<"set">>, |
545 |
|
attrs = [{<<"xmlns">>, ?NS_RSM}], |
546 |
|
children = FirstEl ++ LastEl ++ [CountEl]}. |
547 |
|
|
548 |
|
|
549 |
|
-spec result_query(jlib:xmlcdata() | exml:element(), binary()) -> exml:element(). |
550 |
|
result_query(SetEl, Namespace) -> |
551 |
35 |
#xmlel{ |
552 |
|
name = <<"query">>, |
553 |
|
attrs = [{<<"xmlns">>, Namespace}], |
554 |
|
children = [SetEl]}. |
555 |
|
|
556 |
|
-spec result_prefs(DefaultMode :: archive_behaviour(), |
557 |
|
AlwaysJIDs :: [jid:literal_jid()], |
558 |
|
NeverJIDs :: [jid:literal_jid()], |
559 |
|
Namespace :: binary()) -> exml:element(). |
560 |
|
result_prefs(DefaultMode, AlwaysJIDs, NeverJIDs, Namespace) -> |
561 |
469 |
AlwaysEl = #xmlel{name = <<"always">>, |
562 |
|
children = encode_jids(AlwaysJIDs)}, |
563 |
469 |
NeverEl = #xmlel{name = <<"never">>, |
564 |
|
children = encode_jids(NeverJIDs)}, |
565 |
469 |
#xmlel{ |
566 |
|
name = <<"prefs">>, |
567 |
|
attrs = [{<<"xmlns">>, Namespace}, |
568 |
|
{<<"default">>, atom_to_binary(DefaultMode, utf8)}], |
569 |
|
children = [AlwaysEl, NeverEl] |
570 |
|
}. |
571 |
|
|
572 |
|
|
573 |
|
-spec encode_jids([binary() | string()]) -> [exml:element()]. |
574 |
|
encode_jids(JIDs) -> |
575 |
938 |
[#xmlel{name = <<"jid">>, children = [#xmlcdata{content = JID}]} |
576 |
938 |
|| JID <- JIDs]. |
577 |
|
|
578 |
|
|
579 |
|
%% MAM v0.4.1 and above |
580 |
|
-spec make_fin_element(mongooseim:host_type(), |
581 |
|
mam_iq:lookup_params(), |
582 |
|
binary(), |
583 |
|
boolean(), |
584 |
|
boolean(), |
585 |
|
exml:element(), |
586 |
|
module()) -> |
587 |
|
exml:element(). |
588 |
|
make_fin_element(HostType, Params, MamNs, IsComplete, IsStable, ResultSetEl, ExtFinMod) -> |
589 |
1268 |
FinEl = #xmlel{ |
590 |
|
name = <<"fin">>, |
591 |
|
attrs = [{<<"xmlns">>, MamNs}] |
592 |
904 |
++ [{<<"complete">>, <<"true">>} || IsComplete] |
593 |
:-( |
++ [{<<"stable">>, <<"false">>} || not IsStable], |
594 |
|
children = [ResultSetEl]}, |
595 |
1268 |
maybe_transform_fin_elem(ExtFinMod, HostType, Params, FinEl). |
596 |
|
|
597 |
|
maybe_transform_fin_elem(undefined, _HostType, _Params, FinEl) -> |
598 |
1268 |
FinEl; |
599 |
|
maybe_transform_fin_elem(Module, HostType, Params, FinEl) -> |
600 |
:-( |
Module:extra_fin_element(HostType, Params, FinEl). |
601 |
|
|
602 |
|
-spec make_metadata_element() -> exml:element(). |
603 |
|
make_metadata_element() -> |
604 |
14 |
#xmlel{ |
605 |
|
name = <<"metadata">>, |
606 |
|
attrs = [{<<"xmlns">>, ?NS_MAM_06}]}. |
607 |
|
|
608 |
|
-spec make_metadata_element(binary(), binary(), binary(), binary()) -> exml:element(). |
609 |
|
make_metadata_element(FirstMsgID, FirstMsgTS, LastMsgID, LastMsgTS) -> |
610 |
28 |
#xmlel{ |
611 |
|
name = <<"metadata">>, |
612 |
|
attrs = [{<<"xmlns">>, ?NS_MAM_06}], |
613 |
|
children = [#xmlel{name = <<"start">>, |
614 |
|
attrs = [{<<"id">>, FirstMsgID}, {<<"timestamp">>, FirstMsgTS}]}, |
615 |
|
#xmlel{name = <<"end">>, |
616 |
|
attrs = [{<<"id">>, LastMsgID}, {<<"timestamp">>, LastMsgTS}]}] |
617 |
|
}. |
618 |
|
|
619 |
|
-spec parse_prefs(PrefsEl :: exml:element()) -> mod_mam:preference(). |
620 |
|
parse_prefs(El = #xmlel{ name = <<"prefs">> }) -> |
621 |
308 |
Default = exml_query:attr(El, <<"default">>), |
622 |
308 |
AlwaysJIDs = parse_jid_list(El, <<"always">>), |
623 |
308 |
NeverJIDs = parse_jid_list(El, <<"never">>), |
624 |
308 |
{valid_behavior(Default), AlwaysJIDs, NeverJIDs}. |
625 |
|
|
626 |
|
|
627 |
|
-spec valid_behavior(archive_behaviour_bin()) -> archive_behaviour(). |
628 |
98 |
valid_behavior(<<"always">>) -> always; |
629 |
98 |
valid_behavior(<<"never">>) -> never; |
630 |
112 |
valid_behavior(<<"roster">>) -> roster. |
631 |
|
|
632 |
|
|
633 |
|
-spec parse_jid_list(exml:element(), binary()) -> [jid:literal_jid()]. |
634 |
|
parse_jid_list(El, Name) -> |
635 |
616 |
case exml_query:subelement(El, Name) of |
636 |
:-( |
undefined -> []; |
637 |
|
#xmlel{children = JIDEls} -> |
638 |
|
%% Ignore cdata between jid elements |
639 |
616 |
MaybeJids = [binary_jid_to_lower(exml_query:cdata(JIDEl)) |
640 |
616 |
|| JIDEl <- JIDEls, is_jid_element(JIDEl)], |
641 |
616 |
skip_bad_jids(MaybeJids) |
642 |
|
end. |
643 |
|
|
644 |
|
is_jid_element(#xmlel{name = <<"jid">>}) -> |
645 |
364 |
true; |
646 |
|
is_jid_element(_) -> %% ignore cdata |
647 |
:-( |
false. |
648 |
|
|
649 |
|
%% @doc Normalize JID to be used when comparing JIDs in DB |
650 |
|
binary_jid_to_lower(BinJid) when is_binary(BinJid) -> |
651 |
364 |
Jid = jid:from_binary(BinJid), |
652 |
364 |
case jid:to_lower(Jid) of |
653 |
|
error -> |
654 |
:-( |
error; |
655 |
|
LowerJid -> |
656 |
364 |
jid:to_binary(LowerJid) |
657 |
|
end. |
658 |
|
|
659 |
|
skip_bad_jids(MaybeJids) -> |
660 |
616 |
[Jid || Jid <- MaybeJids, is_binary(Jid)]. |
661 |
|
|
662 |
|
-spec form_borders_decode(mongoose_data_forms:kv_map()) -> 'undefined' | mod_mam:borders(). |
663 |
|
form_borders_decode(KVs) -> |
664 |
1338 |
AfterID = form_field_mess_id(KVs, <<"after-id">>), |
665 |
1338 |
BeforeID = form_field_mess_id(KVs, <<"before-id">>), |
666 |
1338 |
FromID = form_field_mess_id(KVs, <<"from-id">>), |
667 |
1317 |
ToID = form_field_mess_id(KVs, <<"to-id">>), |
668 |
1317 |
borders(AfterID, BeforeID, FromID, ToID). |
669 |
|
|
670 |
|
|
671 |
|
-spec borders(AfterID :: 'undefined' | non_neg_integer(), |
672 |
|
BeforeID :: 'undefined' | non_neg_integer(), |
673 |
|
FromID :: 'undefined' | non_neg_integer(), |
674 |
|
ToID :: 'undefined' | non_neg_integer() |
675 |
|
) -> 'undefined' | mod_mam:borders(). |
676 |
|
borders(undefined, undefined, undefined, undefined) -> |
677 |
1212 |
undefined; |
678 |
|
borders(AfterID, BeforeID, FromID, ToID) -> |
679 |
105 |
#mam_borders{ |
680 |
|
after_id = AfterID, |
681 |
|
before_id = BeforeID, |
682 |
|
from_id = FromID, |
683 |
|
to_id = ToID |
684 |
|
}. |
685 |
|
|
686 |
|
-spec form_field_mess_id(mongoose_data_forms:kv_map(), binary()) -> 'undefined' | integer(). |
687 |
|
form_field_mess_id(KVs, Name) -> |
688 |
5331 |
case KVs of |
689 |
168 |
#{Name := [BExtMessID]} -> external_binary_to_mess_id(BExtMessID); |
690 |
5163 |
#{} -> undefined |
691 |
|
end. |
692 |
|
|
693 |
|
-spec form_decode_optimizations(mongoose_data_forms:kv_map()) -> boolean(). |
694 |
|
form_decode_optimizations(#{<<"simple">> := [<<"true">>]}) -> |
695 |
168 |
true; |
696 |
|
form_decode_optimizations(#{}) -> |
697 |
1142 |
false. |
698 |
|
|
699 |
|
is_mam_result_message(Packet = #xmlel{name = <<"message">>}) -> |
700 |
10 |
Ns = maybe_get_result_namespace(Packet), |
701 |
10 |
is_mam_namespace(Ns); |
702 |
|
is_mam_result_message(_) -> |
703 |
:-( |
false. |
704 |
|
|
705 |
|
maybe_get_result_namespace(Packet) -> |
706 |
10 |
exml_query:path(Packet, [{element, <<"result">>}, {attr, <<"xmlns">>}], <<>>). |
707 |
|
|
708 |
|
is_mam_namespace(NS) -> |
709 |
10 |
lists:member(NS, mam_features()). |
710 |
|
|
711 |
|
features(Module, HostType) -> |
712 |
89 |
mam_features() ++ retraction_features(Module, HostType) |
713 |
|
++ groupchat_features(Module, HostType). |
714 |
|
|
715 |
|
mam_features() -> |
716 |
99 |
[?NS_MAM_04, ?NS_MAM_06]. |
717 |
|
|
718 |
|
retraction_features(Module, HostType) -> |
719 |
89 |
case has_message_retraction(Module, HostType) of |
720 |
75 |
true -> [?NS_RETRACT, ?NS_RETRACT_TOMBSTONE, ?NS_ESL_RETRACT]; |
721 |
14 |
false -> [?NS_RETRACT] |
722 |
|
end. |
723 |
|
|
724 |
|
groupchat_features(mod_mam_pm = Module, HostType) -> |
725 |
69 |
case gen_mod:get_module_opt(HostType, mod_mam, backend) of |
726 |
:-( |
cassandra -> []; |
727 |
|
_ -> |
728 |
69 |
case gen_mod:get_module_opt(HostType, Module, archive_groupchats) of |
729 |
7 |
true -> [?NS_MAM_GC_FIELD, ?NS_MAM_GC_AVAILABLE]; |
730 |
62 |
false -> [?NS_MAM_GC_FIELD] |
731 |
|
end |
732 |
|
end; |
733 |
|
groupchat_features(_, _) -> |
734 |
20 |
[]. |
735 |
|
|
736 |
|
%% ----------------------------------------------------------------------- |
737 |
|
%% Forms |
738 |
|
|
739 |
|
-spec message_form(Mod :: mod_mam_pm | mod_mam_muc, |
740 |
|
HostType :: mongooseim:host_type(), binary()) -> |
741 |
|
exml:element(). |
742 |
|
message_form(Module, HostType, MamNs) -> |
743 |
35 |
Fields = message_form_fields(Module, HostType, MamNs), |
744 |
35 |
Form = mongoose_data_forms:form(#{ns => MamNs, fields => Fields}), |
745 |
35 |
result_query(Form, MamNs). |
746 |
|
|
747 |
|
message_form_fields(Mod, HostType, <<"urn:xmpp:mam:1">>) -> |
748 |
28 |
TextSearch = |
749 |
|
case has_full_text_search(Mod, HostType) of |
750 |
21 |
true -> [#{type => <<"text-single">>, |
751 |
|
var => <<"{https://erlang-solutions.com/}full-text-search">>}]; |
752 |
7 |
false -> [] |
753 |
|
end, |
754 |
28 |
[#{type => <<"jid-single">>, var => <<"with">>}, |
755 |
|
#{type => <<"text-single">>, var => <<"start">>}, |
756 |
|
#{type => <<"text-single">>, var => <<"end">>} | TextSearch]; |
757 |
|
message_form_fields(Mod, HostType, <<"urn:xmpp:mam:2">>) -> |
758 |
7 |
TextSearch = |
759 |
|
case has_full_text_search(Mod, HostType) of |
760 |
7 |
true -> [#{type => <<"text-single">>, |
761 |
|
var => <<"{https://erlang-solutions.com/}full-text-search">>}]; |
762 |
:-( |
false -> [] |
763 |
|
end, |
764 |
7 |
[#{type => <<"jid-single">>, var => <<"with">>}, |
765 |
|
#{type => <<"text-single">>, var => <<"start">>}, |
766 |
|
#{type => <<"text-single">>, var => <<"end">>}, |
767 |
|
#{type => <<"text-single">>, var => <<"before-id">>}, |
768 |
|
#{type => <<"text-single">>, var => <<"after-id">>}, |
769 |
|
#{type => <<"boolean">>, var => <<"include-groupchat">>} | TextSearch]. |
770 |
|
|
771 |
|
-spec form_to_text(_) -> 'undefined' | binary(). |
772 |
|
form_to_text(#{<<"full-text-search">> := [Text]}) -> |
773 |
49 |
Text; |
774 |
|
form_to_text(#{}) -> |
775 |
1289 |
undefined. |
776 |
|
|
777 |
|
%% ----------------------------------------------------------------------- |
778 |
|
%% Text search tokenization |
779 |
|
%% ----------------------------------------------------------------------- |
780 |
|
|
781 |
|
%% ----------------------------------------------------------------------- |
782 |
|
%% @doc |
783 |
|
%% Normalize given text to improve text search in some MAM backends. |
784 |
|
%% This normalization involves making text all lowercase, replacing some word separators |
785 |
|
%% ([, .:;-?!]) with given one (by default "%") and removing all unicode characters that are |
786 |
|
%% considered non-alphanumerical. |
787 |
|
%% For example, text: "My cat, was eaten by: my dog?!? Why...?!?" will be normalized as: |
788 |
|
%% "my%cat%was%eaten%by%my%dog%why" |
789 |
|
%% @end |
790 |
|
%% ----------------------------------------------------------------------- |
791 |
|
-spec normalize_search_text(binary() | undefined) -> binary() | undefined. |
792 |
|
normalize_search_text(Text) -> |
793 |
1450 |
normalize_search_text(Text, <<"%">>). |
794 |
|
|
795 |
|
-spec normalize_search_text(binary() | undefined, binary()) -> binary() | undefined. |
796 |
|
normalize_search_text(undefined, _WordSeparator) -> |
797 |
1408 |
undefined; |
798 |
|
normalize_search_text(Text, WordSeparator) -> |
799 |
8118 |
BodyString = unicode:characters_to_list(Text), |
800 |
8118 |
LowerBody = string:to_lower(BodyString), |
801 |
8118 |
ReOpts = [{return, list}, global, unicode, ucp], |
802 |
8118 |
Re0 = re:replace(LowerBody, "[, .:;-?!]+", " ", ReOpts), |
803 |
8118 |
Re1 = re:replace(Re0, "([^\\w ]+)|(^\\s+)|(\\s+$)", "", ReOpts), |
804 |
8118 |
Re2 = re:replace(Re1, "\s+", unicode:characters_to_list(WordSeparator), ReOpts), |
805 |
8118 |
unicode:characters_to_binary(Re2). |
806 |
|
|
807 |
|
-spec packet_to_search_body(Enabled :: boolean(), |
808 |
|
Packet :: exml:element()) -> binary(). |
809 |
|
packet_to_search_body(true, Packet) -> |
810 |
8076 |
BodyValue = exml_query:path(Packet, [{element, <<"body">>}, cdata], <<>>), |
811 |
8076 |
mod_mam_utils:normalize_search_text(BodyValue, <<" ">>); |
812 |
|
packet_to_search_body(false, _Packet) -> |
813 |
:-( |
<<>>. |
814 |
|
|
815 |
|
-spec has_full_text_search(Module :: mod_mam_pm | mod_mam_muc, |
816 |
|
HostType :: mongooseim:host_type()) -> boolean(). |
817 |
|
has_full_text_search(Module, HostType) -> |
818 |
14099 |
gen_mod:get_module_opt(HostType, Module, full_text_search). |
819 |
|
|
820 |
|
%% Message retraction |
821 |
|
|
822 |
|
-spec has_message_retraction(Module :: mod_mam_pm | mod_mam_muc, |
823 |
|
HostType :: mongooseim:host_type()) -> boolean(). |
824 |
|
has_message_retraction(Module, HostType) -> |
825 |
14153 |
gen_mod:get_module_opt(HostType, Module, message_retraction). |
826 |
|
|
827 |
|
%% ----------------------------------------------------------------------- |
828 |
|
%% JID serialization |
829 |
|
|
830 |
|
-spec jid_to_opt_binary(UserJID :: jid:jid(), JID :: jid:jid() |
831 |
|
) -> jid:literal_jid(). |
832 |
|
jid_to_opt_binary(#jid{lserver = LServer}, |
833 |
|
#jid{lserver = LServer, luser = <<>>, lresource = <<>>}) -> |
834 |
:-( |
<<$:>>; |
835 |
|
jid_to_opt_binary(#jid{lserver = LServer, luser = LUser}, |
836 |
|
#jid{lserver = LServer, luser = LUser, lresource = <<>>}) -> |
837 |
1680 |
<<>>; |
838 |
|
jid_to_opt_binary(#jid{lserver = LServer, luser = LUser}, |
839 |
|
#jid{lserver = LServer, luser = LUser, lresource = LResource}) -> |
840 |
1021 |
<<$/, LResource/binary>>; |
841 |
|
jid_to_opt_binary(#jid{lserver = LServer}, |
842 |
|
#jid{lserver = LServer, luser = LUser, lresource = <<>>}) -> |
843 |
|
%% Both clients are on the same server. |
844 |
6879 |
<<LUser/binary>>; |
845 |
|
jid_to_opt_binary(#jid{lserver = LServer}, |
846 |
|
#jid{lserver = LServer, luser = <<>>, lresource = LResource}) -> |
847 |
|
%% Both clients are on the same server. |
848 |
:-( |
<<$:, $/, LResource/binary>>; |
849 |
|
jid_to_opt_binary(#jid{lserver = LServer}, |
850 |
|
#jid{lserver = LServer, luser = LUser, lresource = LResource}) -> |
851 |
|
%% Both clients are on the same server. |
852 |
915 |
<<LUser/binary, $/, LResource/binary>>; |
853 |
|
jid_to_opt_binary(_, |
854 |
|
#jid{lserver = LServer, luser = LUser, lresource = <<>>}) -> |
855 |
311 |
<<LServer/binary, $:, LUser/binary>>; |
856 |
|
jid_to_opt_binary(_, |
857 |
|
#jid{lserver = LServer, luser = LUser, lresource = LResource}) -> |
858 |
135 |
<<LServer/binary, $@, LUser/binary, $/, LResource/binary>>. |
859 |
|
|
860 |
|
|
861 |
|
-spec expand_minified_jid(UserJID :: jid:jid(), |
862 |
|
OptJID :: jid:literal_jid()) -> jid:literal_jid(). |
863 |
|
expand_minified_jid(#jid{lserver = LServer, luser = LUser}, <<>>) -> |
864 |
402 |
<<LUser/binary, $@, LServer/binary>>; |
865 |
|
expand_minified_jid(#jid{lserver = LServer, luser = <<>>}, <<$/, LResource/binary>>) -> |
866 |
:-( |
<<LServer/binary, $/, LResource/binary>>; |
867 |
|
expand_minified_jid(#jid{lserver = LServer, luser = LUser}, <<$/, LResource/binary>>) -> |
868 |
1901 |
<<LUser/binary, $@, LServer/binary, $/, LResource/binary>>; |
869 |
|
expand_minified_jid(UserJID, Encoded) -> |
870 |
427 |
Part = binary:match(Encoded, [<<$@>>, <<$/>>, <<$:>>]), |
871 |
427 |
expand_minified_jid(Part, UserJID, Encoded). |
872 |
|
|
873 |
|
-spec expand_minified_jid('nomatch' | {non_neg_integer(), 1}, jid:jid(), |
874 |
|
Encoded :: jid:luser() | binary()) -> binary(). |
875 |
|
expand_minified_jid(nomatch, #jid{lserver = ThisServer}, LUser) -> |
876 |
22 |
<<LUser/binary, $@, ThisServer/binary>>; |
877 |
|
expand_minified_jid({Pos, 1}, #jid{lserver = ThisServer}, Encoded) -> |
878 |
405 |
case Encoded of |
879 |
|
<<$:, $/, LResource/binary>> -> |
880 |
:-( |
<<ThisServer/binary, $/, LResource/binary>>; |
881 |
|
<<$:>> -> |
882 |
:-( |
ThisServer; |
883 |
|
<<LServer:Pos/binary, $:>> -> |
884 |
:-( |
<<LServer/binary>>; |
885 |
|
<<LServer:Pos/binary, $:, LUser/binary>> -> |
886 |
48 |
<<LUser/binary, $@, LServer/binary>>; |
887 |
|
<<LServer:Pos/binary, $@, $/, LResource/binary>> -> |
888 |
:-( |
<<LServer/binary, $/, LResource/binary>>; |
889 |
|
<<LServer:Pos/binary, $@, Tail/binary>> -> |
890 |
59 |
[LUser, LResource] = binary:split(Tail, <<$/>>), |
891 |
59 |
<<LUser/binary, $@, LServer/binary, $/, LResource/binary>>; |
892 |
|
<<LUser:Pos/binary, $/, LResource/binary>> -> |
893 |
298 |
<<LUser/binary, $@, ThisServer/binary, $/, LResource/binary>> |
894 |
|
end. |
895 |
|
|
896 |
|
-ifdef(TEST). |
897 |
|
|
898 |
|
jid_to_opt_binary_test_() -> |
899 |
|
check_stringprep(), |
900 |
|
UserJID = jid:from_binary(<<"alice@room">>), |
901 |
|
[?_assertEqual(JID, |
902 |
|
(expand_minified_jid(UserJID, |
903 |
|
jid_to_opt_binary(UserJID, jid:from_binary(JID))))) |
904 |
|
|| JID <- test_jids()]. |
905 |
|
|
906 |
|
test_jids() -> |
907 |
|
[<<"alice@room">>, |
908 |
|
<<"alice@room/computer">>, |
909 |
|
<<"alice@street/mobile">>, |
910 |
|
<<"bob@room">>, |
911 |
|
<<"bob@room/mobile">>, |
912 |
|
<<"bob@street">>, |
913 |
|
<<"bob@street/mobile">>]. |
914 |
|
|
915 |
|
check_stringprep() -> |
916 |
|
is_loaded_application(jid) orelse start_stringprep(). |
917 |
|
|
918 |
|
start_stringprep() -> |
919 |
|
EJ = code:lib_dir(mongooseim), |
920 |
|
code:add_path(filename:join([EJ, "..", "..", "deps", "jid", "ebin"])), |
921 |
|
{ok, _} = application:ensure_all_started(jid). |
922 |
|
|
923 |
|
is_loaded_application(AppName) when is_atom(AppName) -> |
924 |
|
lists:keymember(AppName, 1, application:loaded_applications()). |
925 |
|
|
926 |
|
-endif. |
927 |
|
|
928 |
|
%% ----------------------------------------------------------------------- |
929 |
|
%% Other |
930 |
|
-spec bare_jid(undefined | jid:jid()) -> undefined | binary(). |
931 |
:-( |
bare_jid(undefined) -> undefined; |
932 |
|
bare_jid(JID) -> |
933 |
:-( |
jid:to_bare_binary(jid:to_lower(JID)). |
934 |
|
|
935 |
|
-spec full_jid(jid:jid()) -> binary(). |
936 |
|
full_jid(JID) -> |
937 |
:-( |
jid:to_binary(jid:to_lower(JID)). |
938 |
|
|
939 |
|
-spec maybe_integer(binary(), Default :: integer()) -> integer(). |
940 |
666 |
maybe_integer(<<>>, Def) -> Def; |
941 |
|
maybe_integer(Bin, _Def) when is_binary(Bin) -> |
942 |
672 |
binary_to_integer(Bin). |
943 |
|
|
944 |
|
-spec apply_start_border('undefined' | mod_mam:borders(), undefined | integer()) -> |
945 |
|
undefined | integer(). |
946 |
|
apply_start_border(undefined, StartID) -> |
947 |
1345 |
StartID; |
948 |
|
apply_start_border(#mam_borders{after_id=AfterID, from_id=FromID}, StartID) -> |
949 |
105 |
maybe_max(maybe_next_id(AfterID), maybe_max(FromID, StartID)). |
950 |
|
|
951 |
|
|
952 |
|
-spec apply_end_border('undefined' | mod_mam:borders(), undefined | integer()) -> |
953 |
|
undefined | integer(). |
954 |
|
apply_end_border(undefined, EndID) -> |
955 |
1345 |
EndID; |
956 |
|
apply_end_border(#mam_borders{before_id=BeforeID, to_id=ToID}, EndID) -> |
957 |
105 |
maybe_min(maybe_previous_id(BeforeID), maybe_min(ToID, EndID)). |
958 |
|
|
959 |
|
-spec calculate_msg_id_borders(mod_mam:borders() | undefined, |
960 |
|
mod_mam:unix_timestamp() | undefined, |
961 |
|
mod_mam:unix_timestamp() | undefined) -> R when |
962 |
|
R :: {integer() | undefined, integer() | undefined}. |
963 |
|
calculate_msg_id_borders(Borders, Start, End) -> |
964 |
:-( |
StartID = maybe_encode_compact_uuid(Start, 0), |
965 |
:-( |
EndID = maybe_encode_compact_uuid(End, 255), |
966 |
:-( |
{apply_start_border(Borders, StartID), |
967 |
|
apply_end_border(Borders, EndID)}. |
968 |
|
|
969 |
|
-spec calculate_msg_id_borders(RSM, Borders, Start, End) -> R when |
970 |
|
RSM :: jlib:rsm_in() | undefined, |
971 |
|
Borders :: mod_mam:borders() | undefined, |
972 |
|
Start :: mod_mam:unix_timestamp() | undefined, |
973 |
|
End :: mod_mam:unix_timestamp() | undefined, |
974 |
|
R :: {integer() | undefined, integer() | undefined}. |
975 |
|
calculate_msg_id_borders(undefined, Borders, Start, End) -> |
976 |
:-( |
calculate_msg_id_borders(Borders, Start, End); |
977 |
|
calculate_msg_id_borders(#rsm_in{id = undefined}, Borders, Start, End) -> |
978 |
:-( |
calculate_msg_id_borders(Borders, Start, End); |
979 |
|
calculate_msg_id_borders(#rsm_in{direction = aft, id = Id}, Borders, Start, End) |
980 |
|
when Id =/= undefined -> |
981 |
:-( |
{StartId, EndId} = mod_mam_utils:calculate_msg_id_borders(Borders, Start, End), |
982 |
:-( |
{mod_mam_utils:maybe_max(StartId, Id), EndId}; |
983 |
|
calculate_msg_id_borders(#rsm_in{direction = before, id = Id}, Borders, Start, End) |
984 |
|
when Id =/= undefined -> |
985 |
:-( |
{StartId, EndId} = mod_mam_utils:calculate_msg_id_borders(Borders, Start, End), |
986 |
:-( |
{StartId, mod_mam_utils:maybe_min(EndId, Id)}. |
987 |
|
|
988 |
|
-spec maybe_encode_compact_uuid(mod_mam:unix_timestamp() | undefined, integer()) -> |
989 |
|
undefined | integer(). |
990 |
|
maybe_encode_compact_uuid(undefined, _) -> |
991 |
:-( |
undefined; |
992 |
|
maybe_encode_compact_uuid(Microseconds, NodeID) -> |
993 |
:-( |
mod_mam_utils:encode_compact_uuid(Microseconds, NodeID). |
994 |
|
|
995 |
|
|
996 |
|
-spec maybe_min('undefined' | integer(), undefined | integer()) -> integer(). |
997 |
|
maybe_min(undefined, Y) -> |
998 |
168 |
Y; |
999 |
|
maybe_min(X, undefined) -> |
1000 |
42 |
X; |
1001 |
|
maybe_min(X, Y) -> |
1002 |
:-( |
min(X, Y). |
1003 |
|
|
1004 |
|
|
1005 |
|
-spec maybe_max('undefined' | integer(), undefined | integer()) -> integer(). |
1006 |
|
maybe_max(undefined, Y) -> |
1007 |
105 |
Y; |
1008 |
|
maybe_max(X, undefined) -> |
1009 |
105 |
X; |
1010 |
|
maybe_max(X, Y) -> |
1011 |
:-( |
max(X, Y). |
1012 |
|
|
1013 |
|
-spec maybe_last([T]) -> undefined | {ok, T}. |
1014 |
21 |
maybe_last([]) -> undefined; |
1015 |
49 |
maybe_last([_|_] = L) -> {ok, lists:last(L)}. |
1016 |
|
|
1017 |
|
-spec maybe_next_id('undefined' | non_neg_integer()) -> 'undefined' | pos_integer(). |
1018 |
|
maybe_next_id(undefined) -> |
1019 |
:-( |
undefined; |
1020 |
|
maybe_next_id(X) -> |
1021 |
105 |
X + 1. |
1022 |
|
|
1023 |
|
-spec maybe_previous_id('undefined' | non_neg_integer()) -> 'undefined' | integer(). |
1024 |
|
maybe_previous_id(undefined) -> |
1025 |
63 |
undefined; |
1026 |
|
maybe_previous_id(X) -> |
1027 |
42 |
X - 1. |
1028 |
|
|
1029 |
|
|
1030 |
|
%% @doc Returns true, if the current page is the final one in the result set. |
1031 |
|
%% If there are more pages with messages, than this function returns false. |
1032 |
|
%% |
1033 |
|
%% PageSize - maximum number of messages extracted in one lookup. |
1034 |
|
%% TotalCount - total number of messages in the Result Set. |
1035 |
|
%% Result Set - is subset of all messages in user's archive, |
1036 |
|
%% in a specified time period. |
1037 |
|
%% MessageRows - stuff we are about to send to the user. |
1038 |
|
%% Params - lookup parameters, coming from mam_iq module. |
1039 |
|
%% |
1040 |
|
%% TotalCount and Offset can be undefined, in case we use IsSimple=true. |
1041 |
|
%% IsSimple=true tells the server not to do heavy `SELECT COUNT(*)' queries. |
1042 |
|
%% |
1043 |
|
%% TODO It is possible to set complete flag WITH IsSimple=true, |
1044 |
|
%% if we select one extra message from archive, but don't send it to the client. |
1045 |
|
%% It's the most efficient way to query archive, if the client side does |
1046 |
|
%% not care about the total number of messages and if it's stateless |
1047 |
|
%% (i.e. web interface). |
1048 |
|
%% Handles case when we have TotalCount and Offset as integers |
1049 |
|
-spec is_complete_result_page_using_offset(Params, Result) -> |
1050 |
|
boolean() when |
1051 |
|
Params :: mam_iq:lookup_params(), |
1052 |
|
Result :: mod_mam:lookup_result_map(). |
1053 |
|
is_complete_result_page_using_offset(#{page_size := PageSize} = Params, |
1054 |
|
#{total_count := TotalCount, offset := Offset, |
1055 |
|
messages := MessageRows}) |
1056 |
|
when is_integer(TotalCount), is_integer(Offset) -> |
1057 |
1093 |
case maps:get(ordering_direction, Params, forward) of |
1058 |
|
forward -> |
1059 |
911 |
is_most_recent_page(PageSize, TotalCount, Offset, MessageRows); |
1060 |
|
backward -> |
1061 |
182 |
Offset =:= 0 |
1062 |
|
end. |
1063 |
|
|
1064 |
|
%% @doc Returns true, if the current page contains the most recent messages. |
1065 |
|
%% If there are some more recent messages in archive, this function returns false. |
1066 |
|
-spec is_most_recent_page(PageSize, TotalCount, Offset, MessageRows) -> boolean() when |
1067 |
|
PageSize :: non_neg_integer(), |
1068 |
|
TotalCount :: non_neg_integer()|undefined, |
1069 |
|
Offset :: non_neg_integer()|undefined, |
1070 |
|
MessageRows :: list(). |
1071 |
|
is_most_recent_page(PageSize, _TotalCount, _Offset, MessageRows) |
1072 |
|
when length(MessageRows) < PageSize -> |
1073 |
701 |
true; |
1074 |
|
is_most_recent_page(PageSize, TotalCount, Offset, MessageRows) |
1075 |
|
when is_integer(TotalCount), is_integer(Offset), |
1076 |
|
length(MessageRows) =:= PageSize -> |
1077 |
|
%% Number of messages on skipped pages from the beginning plus the current page |
1078 |
210 |
PagedCount = Offset + PageSize, |
1079 |
210 |
TotalCount =:= PagedCount; %% false means full page but not the last one in the result set |
1080 |
|
is_most_recent_page(_PageSize, _TotalCount, _Offset, _MessageRows) -> |
1081 |
|
%% When is_integer(TotalCount), is_integer(Offset) |
1082 |
|
%% it's not possible case: the page is bigger than page size. |
1083 |
|
%% Otherwise either TotalCount or Offset is undefined because of optimizations. |
1084 |
:-( |
false. |
1085 |
|
|
1086 |
|
-spec maybe_set_client_xmlns(boolean(), exml:element()) -> exml:element(). |
1087 |
|
maybe_set_client_xmlns(true, Packet) -> |
1088 |
4374 |
xml:replace_tag_attr(<<"xmlns">>, <<"jabber:client">>, Packet); |
1089 |
|
maybe_set_client_xmlns(false, Packet) -> |
1090 |
:-( |
Packet. |
1091 |
|
|
1092 |
|
-spec action_to_shaper_name(mam_iq:action()) -> atom(). |
1093 |
|
action_to_shaper_name(Action) -> |
1094 |
1884 |
list_to_atom(atom_to_list(Action) ++ "_shaper"). |
1095 |
|
|
1096 |
|
-spec action_to_global_shaper_name(mam_iq:action()) -> atom(). |
1097 |
|
action_to_global_shaper_name(Action) -> |
1098 |
1884 |
list_to_atom(atom_to_list(Action) ++ "_global_shaper"). |
1099 |
|
|
1100 |
|
-spec wait_shaper(mongooseim:host_type(), jid:server(), mam_iq:action(), jid:jid()) -> |
1101 |
|
continue | {error, max_delay_reached}. |
1102 |
|
wait_shaper(HostType, Host, Action, From) -> |
1103 |
1884 |
case mongoose_shaper:wait( |
1104 |
|
HostType, Host, action_to_shaper_name(Action), From, 1) of |
1105 |
|
continue -> |
1106 |
1884 |
mongoose_shaper:wait( |
1107 |
|
global, Host, action_to_global_shaper_name(Action), From, 1); |
1108 |
|
{error, max_delay_reached} -> |
1109 |
:-( |
{error, max_delay_reached} |
1110 |
|
end. |
1111 |
|
|
1112 |
|
%% ----------------------------------------------------------------------- |
1113 |
|
%% Ejabberd |
1114 |
|
|
1115 |
|
-spec send_message(mod_mam:message_row(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t(). |
1116 |
|
send_message(_Row, From, To, Mess) -> |
1117 |
4374 |
ejabberd_sm:route(From, To, Mess). |
1118 |
|
|
1119 |
|
-spec is_jid_in_user_roster(mongooseim:host_type(), jid:jid(), jid:jid()) -> boolean(). |
1120 |
|
is_jid_in_user_roster(HostType, #jid{} = ToJID, #jid{} = RemJID) -> |
1121 |
84 |
RemBareJID = jid:to_bare(RemJID), |
1122 |
84 |
{Subscription, _G} = mongoose_hooks:roster_get_jid_info(HostType, ToJID, RemBareJID), |
1123 |
84 |
Subscription == from orelse Subscription == both. |
1124 |
|
|
1125 |
|
%% @doc Returns a UUIDv4 canonical form binary. |
1126 |
|
-spec wrapper_id() -> binary(). |
1127 |
|
wrapper_id() -> |
1128 |
4374 |
uuid:uuid_to_string(uuid:get_v4(), binary_standard). |
1129 |
|
|
1130 |
|
|
1131 |
|
-spec check_result_for_policy_violation(Params, Result) -> Result when |
1132 |
|
Params :: mam_iq:lookup_params(), |
1133 |
|
Result :: {ok, mod_mam:lookup_result()} |
1134 |
|
| {error, 'policy-violation'} |
1135 |
|
| {error, Reason :: term()}. |
1136 |
|
check_result_for_policy_violation( |
1137 |
|
_Params = #{limit_passed := LimitPassed, |
1138 |
|
max_result_limit := MaxResultLimit}, |
1139 |
|
Result = {ok, {TotalCount, Offset, _MessageRows}}) |
1140 |
|
when is_integer(TotalCount), is_integer(Offset) -> |
1141 |
1093 |
case is_policy_violation(TotalCount, Offset, MaxResultLimit, LimitPassed) of |
1142 |
|
true -> |
1143 |
:-( |
{error, 'policy-violation'}; |
1144 |
|
false -> |
1145 |
1093 |
Result |
1146 |
|
end; |
1147 |
|
check_result_for_policy_violation(_Params, Result) -> |
1148 |
364 |
Result. |
1149 |
|
|
1150 |
|
is_policy_violation(TotalCount, Offset, MaxResultLimit, LimitPassed) -> |
1151 |
1093 |
TotalCount - Offset > MaxResultLimit andalso not LimitPassed. |
1152 |
|
|
1153 |
|
%% @doc Check for XEP-313 `item-not-found' error condition, |
1154 |
|
%% that is if a message ID passed in a `before'/`after' query is actually present in the archive. |
1155 |
|
%% See https://xmpp.org/extensions/xep-0313.html#query-paging for details. |
1156 |
|
%% |
1157 |
|
%% In a backend it's reasonable to query for PageSize + 1 messages, |
1158 |
|
%% so that once the interval endpoint with requested ID is discarded we actually |
1159 |
|
%% return (up to) PageSize messages. |
1160 |
|
%% @end |
1161 |
|
-spec check_for_item_not_found(RSM, PageSize, LookupResult) -> R when |
1162 |
|
RSM :: jlib:rsm_in() | undefined, |
1163 |
|
PageSize :: non_neg_integer(), |
1164 |
|
LookupResult :: mod_mam:lookup_result(), |
1165 |
|
R :: {ok, mod_mam:lookup_result()} | {error, item_not_found}. |
1166 |
|
check_for_item_not_found(#rsm_in{direction = before, id = ID}, |
1167 |
|
_PageSize, {TotalCount, Offset, MessageRows}) -> |
1168 |
70 |
case maybe_last(MessageRows) of |
1169 |
|
{ok, #{id := ID}} -> |
1170 |
49 |
{ok, {TotalCount, Offset, lists:droplast(MessageRows)}}; |
1171 |
|
undefined -> |
1172 |
21 |
{error, item_not_found} |
1173 |
|
end; |
1174 |
|
check_for_item_not_found(#rsm_in{direction = aft, id = ID}, |
1175 |
|
_PageSize, {TotalCount, Offset, MessageRows0}) -> |
1176 |
91 |
case MessageRows0 of |
1177 |
|
[#{id := ID} | MessageRows] -> |
1178 |
70 |
{ok, {TotalCount, Offset, MessageRows}}; |
1179 |
|
_ -> |
1180 |
21 |
{error, item_not_found} |
1181 |
|
end. |
1182 |
|
|
1183 |
|
-spec lookup(HostType :: mongooseim:host_type(), |
1184 |
|
Params :: mam_iq:lookup_params(), |
1185 |
|
F :: fun()) -> |
1186 |
|
{ok, mod_mam:lookup_result_map()} | {error, Reason :: term()}. |
1187 |
|
lookup(HostType, Params, F) -> |
1188 |
1387 |
F1 = patch_fun_to_make_result_as_map(F), |
1189 |
1387 |
process_lookup_with_complete_check(HostType, Params, F1). |
1190 |
|
|
1191 |
|
process_lookup_with_complete_check(HostType, Params = #{is_simple := true}, F) -> |
1192 |
245 |
process_simple_lookup_with_complete_check(HostType, Params, F); |
1193 |
|
process_lookup_with_complete_check(HostType, Params, F) -> |
1194 |
1142 |
case F(HostType, Params) of |
1195 |
|
{ok, Result} -> |
1196 |
1093 |
IsComplete = is_complete_result_page_using_offset(Params, Result), |
1197 |
1093 |
{ok, Result#{is_complete => IsComplete}}; |
1198 |
|
Other -> |
1199 |
49 |
Other |
1200 |
|
end. |
1201 |
|
|
1202 |
|
-spec lookup_first_and_last_messages(mongooseim:host_type(), mod_mam:archive_id(), |
1203 |
|
jid:jid(), fun()) -> |
1204 |
|
{mod_mam:message_row(), mod_mam:message_row()} | {error, term()} | empty_archive. |
1205 |
|
lookup_first_and_last_messages(HostType, ArcID, ArcJID, F) -> |
1206 |
21 |
lookup_first_and_last_messages(HostType, ArcID, ArcJID, ArcJID, F). |
1207 |
|
|
1208 |
|
-spec lookup_first_and_last_messages(mongooseim:host_type(), mod_mam:archive_id(), jid:jid(), |
1209 |
|
jid:jid(), fun()) -> |
1210 |
|
{mod_mam:message_row(), mod_mam:message_row()} | {error, term()} | empty_archive. |
1211 |
|
lookup_first_and_last_messages(HostType, ArcID, CallerJID, OwnerJID, F) -> |
1212 |
42 |
FirstMsgParams = create_lookup_params(undefined, forward, ArcID, CallerJID, OwnerJID), |
1213 |
42 |
LastMsgParams = create_lookup_params(#rsm_in{direction = before}, |
1214 |
|
backward, ArcID, CallerJID, OwnerJID), |
1215 |
42 |
case lookup(HostType, FirstMsgParams, F) of |
1216 |
|
{ok, #{messages := [FirstMsg]}} -> |
1217 |
28 |
case lookup(HostType, LastMsgParams, F) of |
1218 |
28 |
{ok, #{messages := [LastMsg]}} -> {FirstMsg, LastMsg}; |
1219 |
:-( |
ErrorLast -> ErrorLast |
1220 |
|
end; |
1221 |
14 |
{ok, #{messages := []}} -> empty_archive; |
1222 |
:-( |
ErrorFirst -> ErrorFirst |
1223 |
|
end. |
1224 |
|
|
1225 |
|
-spec create_lookup_params(jlib:rsm_in() | undefined, |
1226 |
|
backward | forward, |
1227 |
|
mod_mam:archive_id(), |
1228 |
|
jid:jid(), |
1229 |
|
jid:jid()) -> mam_iq:lookup_params(). |
1230 |
|
create_lookup_params(RSM, Direction, ArcID, CallerJID, OwnerJID) -> |
1231 |
84 |
#{now => erlang:system_time(microsecond), |
1232 |
|
is_simple => true, |
1233 |
|
rsm => RSM, |
1234 |
|
max_result_limit => 1, |
1235 |
|
archive_id => ArcID, |
1236 |
|
owner_jid => OwnerJID, |
1237 |
|
search_text => undefined, |
1238 |
|
with_jid => undefined, |
1239 |
|
start_ts => undefined, |
1240 |
|
page_size => 1, |
1241 |
|
end_ts => undefined, |
1242 |
|
borders => undefined, |
1243 |
|
flip_page => false, |
1244 |
|
ordering_direction => Direction, |
1245 |
|
limit_passed => true, |
1246 |
|
caller_jid => CallerJID}. |
1247 |
|
|
1248 |
|
patch_fun_to_make_result_as_map(F) -> |
1249 |
1387 |
fun(HostType, Params) -> result_to_map(F(HostType, Params)) end. |
1250 |
|
|
1251 |
|
result_to_map({ok, {TotalCount, Offset, MessageRows}}) -> |
1252 |
1338 |
{ok, #{total_count => TotalCount, offset => Offset, messages => MessageRows}}; |
1253 |
|
result_to_map(Other) -> |
1254 |
49 |
Other. |
1255 |
|
|
1256 |
|
%% We query an extra message by changing page_size. |
1257 |
|
%% After that we remove this message from the result set when returning. |
1258 |
|
process_simple_lookup_with_complete_check(HostType, Params = #{page_size := PageSize}, F) -> |
1259 |
245 |
Params2 = Params#{page_size => PageSize + 1}, |
1260 |
245 |
case F(HostType, Params2) of |
1261 |
|
{ok, Result} -> |
1262 |
245 |
{ok, set_complete_result_page_using_extra_message(PageSize, Params, Result)}; |
1263 |
|
Other -> |
1264 |
:-( |
Other |
1265 |
|
end. |
1266 |
|
|
1267 |
|
set_complete_result_page_using_extra_message(PageSize, Params, Result = #{messages := MessageRows}) -> |
1268 |
245 |
case length(MessageRows) =:= (PageSize + 1) of |
1269 |
|
true -> |
1270 |
91 |
Result#{is_complete => false, messages => remove_extra_message(Params, MessageRows)}; |
1271 |
|
false -> |
1272 |
154 |
Result#{is_complete => true} |
1273 |
|
end. |
1274 |
|
|
1275 |
|
remove_extra_message(Params, Messages) -> |
1276 |
91 |
case maps:get(ordering_direction, Params, forward) of |
1277 |
|
forward -> |
1278 |
35 |
lists:droplast(Messages); |
1279 |
|
backward -> |
1280 |
56 |
tl(Messages) |
1281 |
|
end. |
1282 |
|
|
1283 |
|
-spec db_jid_codec(mongooseim:host_type(), module()) -> module(). |
1284 |
|
db_jid_codec(HostType, Module) -> |
1285 |
14064 |
gen_mod:get_module_opt(HostType, Module, db_jid_format). |
1286 |
|
|
1287 |
|
-spec db_message_codec(mongooseim:host_type(), module()) -> module(). |
1288 |
|
db_message_codec(HostType, Module) -> |
1289 |
14064 |
gen_mod:get_module_opt(HostType, Module, db_message_format). |
1290 |
|
|
1291 |
|
-spec incremental_delete_domain( |
1292 |
|
mongooseim:host_type(), jid:lserver(), non_neg_integer(), [atom()], non_neg_integer()) -> |
1293 |
|
non_neg_integer(). |
1294 |
|
incremental_delete_domain(_HostType, _Domain, _Limit, [], TotalDeleted) -> |
1295 |
:-( |
TotalDeleted; |
1296 |
|
incremental_delete_domain(HostType, Domain, Limit, [Query | MoreQueries] = AllQueries, TotalDeleted) -> |
1297 |
:-( |
R1 = mongoose_rdbms:execute_successfully(HostType, Query, [Domain]), |
1298 |
:-( |
case is_removing_done(R1, Limit) of |
1299 |
|
{done, N} -> |
1300 |
:-( |
incremental_delete_domain(HostType, Domain, Limit, MoreQueries, N + TotalDeleted); |
1301 |
|
{remove_more, N} -> |
1302 |
:-( |
incremental_delete_domain(HostType, Domain, Limit, AllQueries, N + TotalDeleted) |
1303 |
|
end. |
1304 |
|
|
1305 |
|
-spec is_removing_done(LastResult :: {updated, non_neg_integer()}, Limit :: non_neg_integer()) -> |
1306 |
|
{done | remove_more, non_neg_integer()}. |
1307 |
|
is_removing_done({updated, N}, Limit) when N < Limit -> |
1308 |
:-( |
{done, N}; |
1309 |
|
is_removing_done({updated, N}, _)-> |
1310 |
:-( |
{remove_more, N}. |
1311 |
|
|
1312 |
|
-spec is_mam_muc_enabled(jid:lserver(), mongooseim:host_type()) -> boolean(). |
1313 |
|
is_mam_muc_enabled(MucDomain, HostType) -> |
1314 |
615 |
HostPattern = mongoose_config:get_opt([{modules, HostType}, mod_mam_muc, host]), |
1315 |
615 |
{ok, #{subdomain_pattern := SubDomainPattern}} = |
1316 |
|
mongoose_domain_api:get_subdomain_info(MucDomain), |
1317 |
615 |
HostPattern =:= SubDomainPattern. |