./ct_report/coverage/mod_roster.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_roster.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Roster management
5 %%% Created : 11 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2013 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 %%% @doc Roster management (Mnesia storage).
27 %%%
28 %%% Includes support for XEP-0237: Roster Versioning.
29 %%% The roster versioning follows an all-or-nothing strategy:
30 %%% - If the version supplied by the client is the latest, return an empty response.
31 %%% - If not, return the entire new roster (with updated version string).
32 %%% Roster version is a hash digest of the entire roster.
33 %%% No additional data is stored in DB.
34
35 -module(mod_roster).
36 -author('alexey@process-one.net').
37 -xep([{xep, 237}, {version, "1.3"}]).
38 -xep([{xep, 83}, {version, "1.0"}]).
39 -xep([{xep, 93}, {version, "1.2"}]).
40 -behaviour(gen_mod).
41 -behaviour(mongoose_module_metrics).
42
43 -export([start/2,
44 stop/1,
45 config_spec/0,
46 supported_features/0,
47 process_iq/5,
48 get_roster_entry/4,
49 item_to_map/1,
50 set_items/3,
51 set_roster_entry/5,
52 remove_from_roster/3,
53 item_to_xml/1
54 ]).
55
56 % Main hooks
57 -export([
58 get_user_roster/2,
59 in_subscription/5,
60 out_subscription/4,
61 get_subscription_lists/2,
62 get_jid_info/4,
63 remove_user/3,
64 remove_domain/3,
65 get_versioning_feature/2,
66 get_personal_data/3
67 ]).
68
69 -export([transaction/2,
70 process_subscription_t/6,
71 get_user_rosters_length/2]). % for testing
72
73 -export([config_metrics/1]).
74
75 -ignore_xref([
76 behaviour_info/1, get_jid_info/4, get_personal_data/3, get_subscription_lists/2,
77 get_user_roster/2, get_user_rosters_length/2, get_versioning_feature/2,
78 in_subscription/5, item_to_xml/1, out_subscription/4, process_subscription_t/6,
79 remove_user/3, remove_domain/3, transaction/2
80 ]).
81
82 -include("mongoose.hrl").
83 -include("jlib.hrl").
84 -include("mod_roster.hrl").
85 -include("mongoose_config_spec.hrl").
86
87 -export_type([roster/0, sub_presence/0]).
88
89 -type roster() :: #roster{}.
90
91 -type sub_presence() :: subscribe | subscribed | unsubscribe | unsubscribed.
92
93 -type subscription_state() :: none | from | to | both | remove.
94
95 -type get_user_roster_strategy() :: db_versioning | hash_versioning | no_versioning.
96
97 %% Types used in the backend API
98
99 -type contact() :: jid:simple_jid().
100 -type transaction_state() :: in_transaction | no_transaction.
101 -type entry_format() :: full | short.
102 -type version() :: binary().
103
104 -export_type([contact/0, transaction_state/0, entry_format/0, version/0]).
105
106 %%--------------------------------------------------------------------
107 %% gdpr callback
108 %%--------------------------------------------------------------------
109
110 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) ->
111 gdpr:personal_data().
112 get_personal_data(Acc, HostType, #jid{ luser = LUser, lserver = LServer }) ->
113 35 Schema = ["jid", "name", "subscription", "ask", "groups", "askmessage", "xs"],
114 35 Records = get_roster(HostType, LUser, LServer),
115 35 SerializedRecords = lists:map(fun roster_record_to_gdpr_entry/1, Records),
116 35 [{roster, Schema, SerializedRecords} | Acc].
117
118 -spec roster_record_to_gdpr_entry(roster()) -> gdpr:entry().
119 roster_record_to_gdpr_entry(#roster{ jid = JID, name = Name,
120 subscription = Subscription, ask = Ask, groups = Groups,
121 askmessage = AskMessage, xs = XS }) ->
122 2 [
123 jid:to_binary(JID),
124 Name,
125 atom_to_binary(Subscription, utf8),
126 atom_to_binary(Ask, utf8),
127
:-(
string:join([ unicode:characters_to_list(G) || G <- Groups ], ", "),
128 AskMessage,
129
:-(
<< (exml:to_binary(X)) || X <- XS >>
130 ].
131
132 %%--------------------------------------------------------------------
133 %% mod_roster's callbacks
134 %%--------------------------------------------------------------------
135
136 -spec start(mongooseim:host_type(), list()) -> any().
137 start(HostType, Opts) ->
138 347 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
139 347 mod_roster_backend:init(HostType, Opts),
140 347 ejabberd_hooks:add(hooks(HostType)),
141 347 gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm,
142 fun ?MODULE:process_iq/5, #{}, IQDisc).
143
144 -spec stop(mongooseim:host_type()) -> any().
145 stop(HostType) ->
146 334 ejabberd_hooks:delete(hooks(HostType)),
147 334 gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ROSTER, ejabberd_sm).
148
149 -spec config_spec() -> mongoose_config_spec:config_section().
150 config_spec() ->
151 160 #section{
152 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
153 <<"versioning">> => #option{type = boolean},
154 <<"store_current_id">> => #option{type = boolean},
155 <<"backend">> => #option{type = atom,
156 validate = {module, mod_roster}},
157 <<"riak">> => riak_config_spec()
158 }
159 }.
160
161 riak_config_spec() ->
162 160 #section{items = #{<<"bucket_type">> => #option{type = binary,
163 validate = non_empty},
164 <<"version_bucket_type">> => #option{type = binary,
165 validate = non_empty}},
166 wrap = none
167 }.
168
169 142 supported_features() -> [dynamic_domains].
170
171 hooks(HostType) ->
172 681 [{roster_get, HostType, ?MODULE, get_user_roster, 50},
173 {roster_in_subscription, HostType, ?MODULE, in_subscription, 50},
174 {roster_out_subscription, HostType, ?MODULE, out_subscription, 50},
175 {roster_get_subscription_lists, HostType, ?MODULE, get_subscription_lists, 50},
176 {roster_get_jid_info, HostType, ?MODULE, get_jid_info, 50},
177 {remove_user, HostType, ?MODULE, remove_user, 50},
178 {remove_domain, HostType, ?MODULE, remove_domain, 50},
179 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
180 {roster_get_versioning_feature, HostType, ?MODULE, get_versioning_feature, 50},
181 {get_personal_data, HostType, ?MODULE, get_personal_data, 50}].
182
183 -spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
184 {mongoose_acc:t(), jlib:iq()}.
185 process_iq(Acc, From = #jid{lserver = LServer, luser = LUser},
186 To = #jid{lserver = LServer, luser = LUser}, IQ, _Extra) ->
187 64 process_local_iq(Acc, From, To, IQ);
188 process_iq(Acc, _From, _To, IQ = #iq{lang = Lang, sub_el = SubEl}, _Extra) ->
189 %% Error type 'forbidden' is specified in Section 2.3.3 of RFC6121 for iq set.
190 %% The behaviour is unspecified for iq get, but it makes sense to handle them consistently.
191 2 ErrorMsg = <<"It is forbidden to query the roster of another user">>,
192 2 ErrorEl = mongoose_xmpp_errors:forbidden(Lang, ErrorMsg),
193 2 {Acc, IQ#iq{type = error, sub_el = [SubEl, ErrorEl]}}.
194
195 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq()) ->
196 {mongoose_acc:t(), jlib:iq()}.
197 process_local_iq(Acc, From, To, #iq{type = Type} = IQ) ->
198 64 HostType = mongoose_acc:host_type(Acc),
199 64 IQReply = case Type of
200 37 set -> process_iq_set(HostType, From, To, IQ);
201 27 get -> process_iq_get(HostType, From, To, IQ)
202 end,
203 64 {Acc, IQReply}.
204
205 roster_hash(Items) ->
206 4 L = [R#roster{groups = lists:sort(Grs)} ||
207 4 R = #roster{groups = Grs} <- Items],
208 4 sha:sha1_hex(term_to_binary(lists:sort(L))).
209
210 -spec roster_versioning_enabled(mongooseim:host_type()) -> boolean().
211 roster_versioning_enabled(HostType) ->
212 2815 gen_mod:get_module_opt(HostType, ?MODULE, versioning, false).
213
214 -spec roster_version_on_db(mongooseim:host_type()) -> boolean().
215 roster_version_on_db(HostType) ->
216 349 gen_mod:get_module_opt(HostType, ?MODULE, store_current_id, false).
217
218 %% Returns a list that may contain an xmlel with the XEP-237 feature if it's enabled.
219 get_versioning_feature(Acc, HostType) ->
220 2502 case roster_versioning_enabled(HostType) of
221 true ->
222 4 Feature = #xmlel{name = <<"ver">>,
223 attrs = [{<<"xmlns">>, ?NS_ROSTER_VER}]},
224 4 [Feature | Acc];
225 2498 false -> []
226 end.
227
228 roster_version(HostType, #jid{luser = LUser, lserver = LServer} = JID) ->
229 2 case roster_version_on_db(HostType) of
230 true ->
231 1 case read_roster_version(HostType, LUser, LServer) of
232
:-(
error -> not_found;
233 1 V -> V
234 end;
235 false ->
236 1 R = get_roster_old(HostType, JID),
237 1 roster_hash(R)
238 end.
239
240 %% Load roster from DB only if neccesary.
241 %% It is neccesary if
242 %% - roster versioning is disabled in server OR
243 %% - roster versioning is not used by the client OR
244 %% - roster versioning is used by server and client,
245 %% BUT the server isn't storing versions on db OR
246 %% - the roster version from client don't match current version.
247 -spec process_iq_get(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq().
248 process_iq_get(HostType, From, To, #iq{sub_el = SubEl} = IQ) ->
249 27 AttrVer = exml_query:attr(SubEl, <<"ver">>), %% type binary() | undefined
250 27 VersioningRequested = is_binary(AttrVer),
251 27 VersioningEnabled = roster_versioning_enabled(HostType),
252 27 VersionOnDb = roster_version_on_db(HostType),
253 27 Strategy = choose_get_user_roster_strategy(VersioningRequested, VersioningEnabled, VersionOnDb),
254 27 {ItemsToSend, VersionToSend} =
255 get_user_roster_based_on_version(HostType, Strategy, AttrVer, From, To),
256 27 IQ#iq{type = result, sub_el = create_sub_el(ItemsToSend, VersionToSend)}.
257
258 -spec choose_get_user_roster_strategy(VersioningRequested :: boolean(),
259 VersioningEnabled :: boolean(),
260 VersionOnDb :: boolean()) ->
261 get_user_roster_strategy().
262 3 choose_get_user_roster_strategy(true, true, true) -> db_versioning;
263 3 choose_get_user_roster_strategy(true, true, false) -> hash_versioning;
264 21 choose_get_user_roster_strategy(_, _, _) -> no_versioning.
265
266 get_user_roster_based_on_version(HostType, db_versioning, RequestedVersion, From, To) ->
267 3 get_user_roster_db_versioning(HostType, RequestedVersion, From, To);
268 get_user_roster_based_on_version(HostType, hash_versioning, RequestedVersion, From, To) ->
269 3 get_user_roster_hash_versioning(HostType, RequestedVersion, From, To);
270 get_user_roster_based_on_version(HostType, no_versioning, _RequestedVersion, From, To) ->
271 21 get_user_roster_no_versioning(HostType, From, To).
272
273 get_user_roster_db_versioning(HostType, RequestedVersion, From, To)
274 when is_binary(RequestedVersion) ->
275 3 LUser = From#jid.luser,
276 3 LServer = From#jid.lserver,
277 3 case read_roster_version(HostType, LUser, LServer) of
278 error ->
279 1 RosterVersion = write_roster_version(HostType, LUser, LServer),
280 1 {lists:map(fun item_to_xml/1,
281 get_roster_old(HostType, To#jid.server, From)),
282 RosterVersion};
283 RequestedVersion ->
284 1 {false, false};
285 NewVersion ->
286 1 {lists:map(fun item_to_xml/1,
287 get_roster_old(HostType, To#jid.server, From)),
288 NewVersion}
289 end.
290
291 get_user_roster_hash_versioning(HostType, RequestedVersion, From, To)
292 when is_binary(RequestedVersion) ->
293 3 RosterItems = get_roster_old(HostType, To#jid.lserver, From),
294 3 case roster_hash(RosterItems) of
295 RequestedVersion ->
296 1 {false, false};
297 New ->
298 2 {lists:map(fun item_to_xml/1, RosterItems), New}
299 end.
300
301 get_user_roster_no_versioning(HostType, From, To) ->
302 21 {lists:map(fun item_to_xml/1,
303 get_roster_old(HostType, To#jid.lserver, From)),
304 false}.
305
306 create_sub_el(false, false) ->
307 2 [];
308 create_sub_el(Items, false) ->
309 21 [#xmlel{name = <<"query">>,
310 attrs = [{<<"xmlns">>, ?NS_ROSTER}],
311 children = Items}];
312 create_sub_el(Items, Version) ->
313 4 [#xmlel{name = <<"query">>,
314 attrs = [{<<"xmlns">>, ?NS_ROSTER},
315 {<<"ver">>, Version}],
316 children = Items}].
317
318 -spec get_user_roster(mongoose_acc:t(), jid:jid() | {jid:luser(), jid:lserver()}) ->
319 mongoose_acc:t().
320 get_user_roster(Acc, #jid{luser = LUser, lserver = LServer}) ->
321 2343 HostType = mongoose_acc:host_type(Acc),
322 2343 case mongoose_acc:get(roster, show_full_roster, false, Acc) of
323 true ->
324 31 Roster = get_roster(HostType, LUser, LServer),
325 31 mongoose_acc:append(roster, items, Roster, Acc);
326 _ ->
327 2312 Roster = lists:filter(fun (#roster{subscription = none, ask = in}) ->
328 5 false;
329 (_) ->
330 117 true
331 end, get_roster(HostType, LUser, LServer)),
332 2312 mongoose_acc:append(roster, items, Roster, Acc)
333 end.
334
335 item_to_xml(Item) ->
336 264 Attrs1 = [{<<"jid">>,
337 jid:to_binary(Item#roster.jid)}],
338 264 Attrs2 = case Item#roster.name of
339 159 <<"">> -> Attrs1;
340 105 Name -> [{<<"name">>, Name} | Attrs1]
341 end,
342 264 Attrs3 = case Item#roster.subscription of
343 125 none -> [{<<"subscription">>, <<"none">>} | Attrs2];
344 31 from -> [{<<"subscription">>, <<"from">>} | Attrs2];
345 39 to -> [{<<"subscription">>, <<"to">>} | Attrs2];
346 53 both -> [{<<"subscription">>, <<"both">>} | Attrs2];
347 16 remove -> [{<<"subscription">>, <<"remove">>} | Attrs2]
348 end,
349 264 Attrs4 = case ask_to_pending(Item#roster.ask) of
350 51 out -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
351 28 both -> [{<<"ask">>, <<"subscribe">>} | Attrs3];
352 185 _ -> Attrs3
353 end,
354 264 SubEls1 = lists:map(fun (G) ->
355 115 #xmlel{name = <<"group">>, attrs = [],
356 children = [{xmlcdata, G}]}
357 end,
358 Item#roster.groups),
359 264 SubEls = SubEls1 ++ Item#roster.xs,
360 264 #xmlel{name = <<"item">>, attrs = Attrs4,
361 children = SubEls}.
362
363 -spec process_iq_set(mongooseim:host_type(), jid:jid(), jid:jid(), jlib:iq()) -> jlib:iq().
364 process_iq_set(HostType, From, To, #iq{sub_el = SubEl} = IQ) ->
365 37 #xmlel{children = Els} = SubEl,
366 37 mongoose_hooks:roster_set(HostType, From, To, SubEl),
367 37 lists:foreach(fun(El) -> process_item_set(HostType, From, To, El) end, Els),
368 37 IQ#iq{type = result, sub_el = []}.
369
370 -spec process_item_set(mongooseim:host_type(), jid:jid(), jid:jid(), exml:element()) -> ok.
371 process_item_set(HostType, From, To, #xmlel{attrs = Attrs} = El) ->
372 37 JID1 = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)),
373 37 do_process_item_set(HostType, JID1, From, To, El);
374
:-(
process_item_set(_HostType, _From, _To, _) -> ok.
375
376 -spec do_process_item_set(mongooseim:host_type(), error | jid:jid(), jid:jid(), jid:jid(),
377 exml:element()) -> ok.
378
:-(
do_process_item_set(_, error, _, _, _) -> ok;
379 do_process_item_set(HostType, #jid{} = JID1, #jid{} = From, #jid{} = To,
380 #xmlel{attrs = Attrs, children = Els}) ->
381 37 MakeItem2 = fun(Item) ->
382 37 Item1 = process_item_attrs(Item, Attrs),
383 37 process_item_els(Item1, Els)
384 end,
385 37 set_roster_item(HostType, JID1, From, To, MakeItem2),
386 37 ok.
387
388 %% @doc this is run when a roster item is to be added, updated or removed
389 %% the interface of this func could probably be a bit simpler
390 -spec set_roster_item(mongooseim:host_type(),
391 ContactJID :: jid:jid(),
392 From :: jid:jid(),
393 To :: jid:jid(),
394 fun((roster()) -> roster())) -> ok | {error, any()}.
395 set_roster_item(HostType, ContactJID, From, To, MakeItem) ->
396 62 ContactLJID = jid:to_lower(ContactJID),
397 62 F = fun() -> set_roster_item_t(HostType, From, ContactLJID, MakeItem) end,
398 62 case transaction(HostType, F) of
399 {atomic, does_not_exist} ->
400 1 {error, does_not_exist};
401 {atomic, {OldItem, NewItem}} ->
402 61 push_item(HostType, From, To, NewItem),
403 61 case NewItem#roster.subscription of
404 remove ->
405 16 send_unsubscribing_presence(From, OldItem),
406 16 ok;
407 45 _ -> ok
408 end;
409 E ->
410
:-(
?LOG_ERROR(#{what => roster_set_item_failed, reason => E}),
411
:-(
{error, E}
412 end.
413
414 -spec set_roster_item_t(mongooseim:host_type(), jid:jid(), contact(),
415 fun((roster()) -> roster())) ->
416 does_not_exist | {roster(), roster()}.
417 set_roster_item_t(HostType, UserJid = #jid{luser = LUser, lserver = LServer},
418 ContactLJID, MakeItem) ->
419 62 InitialItem = get_roster_entry_t(HostType, UserJid, ContactLJID, short),
420 62 Item1 = case InitialItem of
421 33 does_not_exist -> new_roster_item(LUser, LServer, ContactLJID);
422 29 Item -> Item
423 end,
424 62 Item2 = MakeItem(Item1),
425 62 case {InitialItem, Item2} of
426 {does_not_exist, #roster{subscription = remove}} ->
427 %% Cannot remove a non-existing item
428 1 does_not_exist;
429 _ ->
430 61 case Item2#roster.subscription of
431 16 remove -> del_roster_t(HostType, LUser, LServer, ContactLJID);
432 45 _ -> update_roster_t(HostType, Item2)
433 end,
434 61 Item3 = mongoose_hooks:roster_process_item(HostType, LServer, Item2),
435 61 case roster_version_on_db(HostType) of
436 1 true -> write_roster_version_t(HostType, LUser, LServer);
437 60 false -> ok
438 end,
439 61 {Item1, Item3}
440 end.
441
442 -spec new_roster_item(jid:luser(), jid:lserver(), jid:simple_jid()) -> roster().
443 new_roster_item(LUser, LServer, ContactLJID) ->
444 33 #roster{usj = {LUser, LServer, ContactLJID},
445 us = {LUser, LServer},
446 jid = ContactLJID}.
447
448 process_item_attrs(Item, [{<<"jid">>, Val} | Attrs]) ->
449 37 case jid:from_binary(Val) of
450 error ->
451
:-(
process_item_attrs(Item, Attrs);
452 JID1 ->
453 37 JID = jid:to_lower(JID1),
454 37 process_item_attrs(Item#roster{jid = JID}, Attrs)
455 end;
456 process_item_attrs(Item, [{<<"name">>, Val} | Attrs]) ->
457 30 process_item_attrs(Item#roster{name = Val}, Attrs);
458 process_item_attrs(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) ->
459 7 process_item_attrs(Item#roster{subscription = remove}, Attrs);
460 process_item_attrs(Item, [_ | Attrs]) ->
461
:-(
process_item_attrs(Item, Attrs);
462 process_item_attrs(Item, []) ->
463 37 Item.
464
465 process_item_els(Item,
466 [#xmlel{name = Name, attrs = Attrs, children = SEls}
467 | Els]) ->
468 81 case Name of
469 <<"group">> ->
470 81 Groups = [xml:get_cdata(SEls) | Item#roster.groups],
471 81 process_item_els(Item#roster{groups = Groups}, Els);
472 _ ->
473
:-(
case xml:get_attr_s(<<"xmlns">>, Attrs) of
474
:-(
<<"">> -> process_item_els(Item, Els);
475 _ ->
476
:-(
XEls = [#xmlel{name = Name, attrs = Attrs,
477 children = SEls}
478 | Item#roster.xs],
479
:-(
process_item_els(Item#roster{xs = XEls}, Els)
480 end
481 end;
482 process_item_els(Item, [{xmlcdata, _} | Els]) ->
483
:-(
process_item_els(Item, Els);
484 104 process_item_els(Item, []) -> Item.
485
486 push_item(HostType, JID, From, Item) ->
487 286 ejabberd_sm:route(jid:make_noprep(<<>>, <<>>, <<>>), JID,
488 {broadcast, {item, Item#roster.jid, Item#roster.subscription}}),
489 286 case roster_versioning_enabled(HostType) of
490 true ->
491 2 push_item_version(JID, From, Item, roster_version(HostType, JID));
492 false ->
493 284 lists:foreach(fun(Resource) ->
494 232 push_item_without_version(HostType, JID, Resource, From, Item)
495 end,
496 ejabberd_sm:get_user_resources(JID))
497 end.
498
499 push_item_without_version(HostType, JID, Resource, From, Item) ->
500 232 mongoose_hooks:roster_push(HostType, From, Item),
501 232 push_item_final(jid:replace_resource(JID, Resource), From, Item, not_found).
502
503 push_item_version(JID, From, Item, RosterVersion) ->
504 2 lists:foreach(fun(Resource) ->
505 2 push_item_final(jid:replace_resource(JID, Resource),
506 From, Item, RosterVersion)
507 end, ejabberd_sm:get_user_resources(JID)).
508
509 push_item_final(JID, From, Item, RosterVersion) ->
510 234 ExtraAttrs = case RosterVersion of
511 232 not_found -> [];
512 2 _ -> [{<<"ver">>, RosterVersion}]
513 end,
514 234 ResIQ = #iq{type = set, xmlns = ?NS_ROSTER,
515 %% @doc Roster push, calculate and include the version attribute.
516 %% TODO: don't push to those who didn't load roster
517 id = <<"push", (mongoose_bin:gen_from_crypto())/binary>>,
518 sub_el =
519 [#xmlel{name = <<"query">>,
520 attrs = [{<<"xmlns">>, ?NS_ROSTER} | ExtraAttrs],
521 children = [item_to_xml(Item)]}]},
522 234 ejabberd_router:route(From, JID, jlib:iq_to_xml(ResIQ)).
523
524 -spec get_subscription_lists(Acc :: mongoose_acc:t(), JID :: jid:jid()) ->
525 mongoose_acc:t().
526 get_subscription_lists(Acc, #jid{luser = LUser, lserver = LServer} = JID) ->
527 4826 Items = mod_roster_backend:get_subscription_lists(Acc, LUser, LServer),
528 4826 SubLists = fill_subscription_lists(JID, LServer, Items, [], [], []),
529 4826 mongoose_acc:set(roster, subscription_lists, SubLists, Acc).
530
531 fill_subscription_lists(JID, LServer, [#roster{} = I | Is], F, T, P) ->
532 42 J = element(3, I#roster.usj),
533 42 NewP = build_pending(I, JID, P),
534 42 case I#roster.subscription of
535 both ->
536 25 fill_subscription_lists(JID, LServer, Is, [J | F], [J | T], NewP);
537 from ->
538 1 fill_subscription_lists(JID, LServer, Is, [J | F], T, NewP);
539
:-(
to -> fill_subscription_lists(JID, LServer, Is, F, [J | T], NewP);
540 16 _ -> fill_subscription_lists(JID, LServer, Is, F, T, NewP)
541 end;
542 4826 fill_subscription_lists(_, _LServer, [], F, T, P) -> {F, T, P}.
543
544 build_pending(#roster{ask = Ask} = I, JID, P)
545 when Ask == in; Ask == both ->
546 6 Status = case I#roster.askmessage of
547 6 Message when is_binary(Message) -> Message;
548
:-(
true -> <<>>
549 end,
550 6 StatusEl = #xmlel{
551 name = <<"status">>,
552 children = [#xmlcdata{content = Status}]},
553 6 El = #xmlel{
554 name = <<"presence">>,
555 attrs = [{<<"from">>, jid:to_binary(I#roster.jid)},
556 {<<"to">>, jid:to_binary(JID)},
557 {<<"type">>, <<"subscribe">>}],
558 children = [StatusEl]},
559 6 [El | P];
560 build_pending(_, _, P) ->
561 36 P.
562
563
:-(
ask_to_pending(subscribe) -> out;
564
:-(
ask_to_pending(unsubscribe) -> none;
565 264 ask_to_pending(Ask) -> Ask.
566
567 -spec in_subscription(Acc :: mongoose_acc:t(),
568 ToJID :: jid:jid(),
569 FromJID :: jid:jid(),
570 Type :: sub_presence(),
571 Reason :: iodata()) ->
572 mongoose_acc:t().
573 in_subscription(Acc, ToJID, FromJID, Type, Reason) ->
574 178 HostType = mongoose_acc:host_type(Acc),
575 178 Res = process_subscription(HostType, in, ToJID, FromJID, Type, Reason),
576 178 mongoose_acc:set(hook, result, Res, Acc).
577
578 -spec out_subscription(Acc :: mongoose_acc:t(),
579 FromJID :: jid:jid(),
580 ToJID :: jid:jid(),
581 Type :: sub_presence()) ->
582 mongoose_acc:t().
583 out_subscription(Acc, FromJID, ToJID, Type) ->
584 104 HostType = mongoose_acc:host_type(Acc),
585 104 Res = process_subscription(HostType, out, FromJID, ToJID, Type, <<>>),
586 104 mongoose_acc:set(hook, result, Res, Acc).
587
588 -spec process_subscription(mongooseim:host_type(), in | out, jid:jid(), jid:jid(),
589 sub_presence(), iodata()) ->
590 boolean().
591 process_subscription(HostType, Direction, JID, ContactJID, Type, Reason) ->
592 282 TransactionFun =
593 fun() ->
594 286 process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason)
595 end,
596 282 case transaction(HostType, TransactionFun) of
597 {atomic, {Push, AutoReply}} ->
598 282 case AutoReply of
599 250 none -> ok;
600 _ ->
601 32 PresenceStanza = #xmlel{name = <<"presence">>,
602 attrs = [{<<"type">>, autoreply_to_type(AutoReply)}],
603 children = []},
604 32 ejabberd_router:route(JID, ContactJID, PresenceStanza)
605 end,
606 282 case Push of
607 {push, #roster{subscription = none, ask = in}} ->
608 34 true;
609 {push, Item} ->
610 225 push_item(HostType, JID, JID, Item),
611 225 true;
612 23 none -> false
613 end;
614
:-(
_ -> false
615 end.
616
617
:-(
autoreply_to_type(subscribed) -> <<"subscribed">>;
618 32 autoreply_to_type(unsubscribed) -> <<"unsubscribed">>.
619
620 -spec process_subscription_t(mongooseim:host_type(), in | out, jid:jid(), jid:jid(),
621 sub_presence(), iodata()) ->
622 {Push :: none | {push, roster()},
623 AutoReply :: none | subscribed | unsubscribed}.
624 process_subscription_t(HostType, Direction, JID, ContactJID, Type, Reason) ->
625 286 #jid{luser = LUser, lserver = LServer} = JID,
626 286 ContactLJID = jid:to_lower(ContactJID),
627 286 Item = case get_roster_entry_t(HostType, JID, ContactLJID, full) of
628 does_not_exist ->
629 64 #roster{usj = {LUser, LServer, ContactLJID},
630 us = {LUser, LServer},
631 jid = ContactLJID};
632 222 R -> R
633 end,
634 286 NewState = case Direction of
635 out ->
636 104 out_state_change(Item#roster.subscription,
637 Item#roster.ask, Type);
638 in ->
639 182 in_state_change(Item#roster.subscription,
640 Item#roster.ask, Type)
641 end,
642 286 AutoReply = case Direction of
643 104 out -> none;
644 in ->
645 182 in_auto_reply(Item#roster.subscription,
646 Item#roster.ask, Type)
647 end,
648 286 AskMessage = case NewState of
649 28 {_, both} -> Reason;
650 50 {_, in} -> Reason;
651 208 _ -> <<"">>
652 end,
653 286 case NewState of
654 20 none -> {none, AutoReply};
655 {none, none}
656 when Item#roster.subscription == none,
657 Item#roster.ask == in ->
658 3 del_roster_t(HostType, LUser, LServer, ContactLJID), {none, AutoReply};
659 {Subscription, Pending} ->
660 263 NewItem = Item#roster{subscription = Subscription,
661 ask = Pending,
662 askmessage = iolist_to_binary(AskMessage)},
663 263 roster_subscribe_t(HostType, NewItem),
664 259 case roster_version_on_db(HostType) of
665
:-(
true -> write_roster_version_t(HostType, LUser, LServer);
666 259 false -> ok
667 end,
668 259 {{push, NewItem}, AutoReply}
669 end.
670
671 %% in_state_change(Subscription, Pending, Type) -> NewState
672 %% NewState = none | {NewSubscription, NewPending}
673 -ifdef(ROSTER_GATEWAY_WORKAROUND).
674
675 -define(NNSD, {to, none}).
676
677 -define(NISD, {to, in}).
678
679 -else.
680
681 -define(NNSD, none).
682
683 -define(NISD, none).
684
685 -endif.
686
687 34 in_state_change(none, none, subscribe) -> {none, in};
688
:-(
in_state_change(none, none, subscribed) -> ?NNSD;
689 4 in_state_change(none, none, unsubscribe) -> none;
690 10 in_state_change(none, none, unsubscribed) -> none;
691 14 in_state_change(none, out, subscribe) -> {none, both};
692 15 in_state_change(none, out, subscribed) -> {to, none};
693
:-(
in_state_change(none, out, unsubscribe) -> none;
694 3 in_state_change(none, out, unsubscribed) -> {none, none};
695 1 in_state_change(none, in, subscribe) -> none;
696
:-(
in_state_change(none, in, subscribed) -> ?NISD;
697
:-(
in_state_change(none, in, unsubscribe) -> {none, none};
698
:-(
in_state_change(none, in, unsubscribed) -> none;
699
:-(
in_state_change(none, both, subscribe) -> none;
700 14 in_state_change(none, both, subscribed) -> {to, in};
701
:-(
in_state_change(none, both, unsubscribe) -> {none, out};
702
:-(
in_state_change(none, both, unsubscribed) -> {none, in};
703 2 in_state_change(to, none, subscribe) -> {to, in};
704
:-(
in_state_change(to, none, subscribed) -> none;
705
:-(
in_state_change(to, none, unsubscribe) -> none;
706 27 in_state_change(to, none, unsubscribed) -> {none, none};
707
:-(
in_state_change(to, in, subscribe) -> none;
708
:-(
in_state_change(to, in, subscribed) -> none;
709
:-(
in_state_change(to, in, unsubscribe) -> {to, none};
710
:-(
in_state_change(to, in, unsubscribed) -> {none, in};
711
:-(
in_state_change(from, none, subscribe) -> none;
712
:-(
in_state_change(from, none, subscribed) -> {both, none};
713 9 in_state_change(from, none, unsubscribe) -> {none, none};
714
:-(
in_state_change(from, none, unsubscribed) -> none;
715
:-(
in_state_change(from, out, subscribe) -> none;
716 16 in_state_change(from, out, subscribed) -> {both, none};
717
:-(
in_state_change(from, out, unsubscribe) -> {none, out};
718
:-(
in_state_change(from, out, unsubscribed) -> {from, none};
719
:-(
in_state_change(both, none, subscribe) -> none;
720
:-(
in_state_change(both, none, subscribed) -> none;
721 26 in_state_change(both, none, unsubscribe) -> {to, none};
722 7 in_state_change(both, none, unsubscribed) -> {from, none}.
723
724 34 out_state_change(none, none, subscribe) -> {none, out};
725 4 out_state_change(none, none, subscribed) -> none;
726 1 out_state_change(none, none, unsubscribe) -> none;
727
:-(
out_state_change(none, none, unsubscribed) -> none;
728 out_state_change(none, out, subscribe) ->
729
:-(
{none, out}; %% We need to resend query (RFC3921, section 9.2)
730
:-(
out_state_change(none, out, subscribed) -> none;
731
:-(
out_state_change(none, out, unsubscribe) -> {none, none};
732
:-(
out_state_change(none, out, unsubscribed) -> none;
733 14 out_state_change(none, in, subscribe) -> {none, both};
734 13 out_state_change(none, in, subscribed) -> {from, none};
735
:-(
out_state_change(none, in, unsubscribe) -> none;
736 3 out_state_change(none, in, unsubscribed) -> {none, none};
737
:-(
out_state_change(none, both, subscribe) -> none;
738 14 out_state_change(none, both, subscribed) -> {from, out};
739
:-(
out_state_change(none, both, unsubscribe) -> {none, in};
740
:-(
out_state_change(none, both, unsubscribed) -> {none, out};
741
:-(
out_state_change(to, none, subscribe) -> none;
742
:-(
out_state_change(to, none, subscribed) -> {both, none};
743 2 out_state_change(to, none, unsubscribe) -> {none, none};
744
:-(
out_state_change(to, none, unsubscribed) -> none;
745
:-(
out_state_change(to, in, subscribe) -> none;
746 16 out_state_change(to, in, subscribed) -> {both, none};
747
:-(
out_state_change(to, in, unsubscribe) -> {none, in};
748
:-(
out_state_change(to, in, unsubscribed) -> {to, none};
749 2 out_state_change(from, none, subscribe) -> {from, out};
750
:-(
out_state_change(from, none, subscribed) -> none;
751
:-(
out_state_change(from, none, unsubscribe) -> none;
752
:-(
out_state_change(from, none, unsubscribed) -> {none, none};
753
:-(
out_state_change(from, out, subscribe) -> none;
754
:-(
out_state_change(from, out, subscribed) -> none;
755
:-(
out_state_change(from, out, unsubscribe) -> {from, none};
756
:-(
out_state_change(from, out, unsubscribed) -> {none, out};
757
:-(
out_state_change(both, none, subscribe) -> none;
758
:-(
out_state_change(both, none, subscribed) -> none;
759
:-(
out_state_change(both, none, unsubscribe) -> {from, none};
760 1 out_state_change(both, none, unsubscribed) -> {to, none}.
761
762
:-(
in_auto_reply(from, none, subscribe) -> subscribed;
763
:-(
in_auto_reply(from, out, subscribe) -> subscribed;
764
:-(
in_auto_reply(both, none, subscribe) -> subscribed;
765
:-(
in_auto_reply(none, in, unsubscribe) -> unsubscribed;
766
:-(
in_auto_reply(none, both, unsubscribe) -> unsubscribed;
767
:-(
in_auto_reply(to, in, unsubscribe) -> unsubscribed;
768 9 in_auto_reply(from, none, unsubscribe) -> unsubscribed;
769
:-(
in_auto_reply(from, out, unsubscribe) -> unsubscribed;
770 26 in_auto_reply(both, none, unsubscribe) -> unsubscribed;
771 147 in_auto_reply(_, _, _) -> none.
772
773 get_user_rosters_length(HostType, JID) ->
774 1 length(get_roster_old(HostType, JID)).
775
776 -spec remove_user(mongoose_acc:t(), jid:luser(), jid:lserver()) -> mongoose_acc:t().
777 remove_user(Acc, LUser, LServer) ->
778 2257 HostType = mongoose_acc:host_type(Acc),
779 2257 JID = jid:make_noprep(LUser, LServer, <<>>),
780 2257 Acc1 = try_send_unsubscription_to_rosteritems(Acc, JID),
781 2257 F = fun() -> mod_roster_backend:remove_user_t(HostType, LUser, LServer) end,
782 2257 case transaction(HostType, F) of
783 {atomic, ok} ->
784 2257 ok;
785 Result ->
786
:-(
?LOG_ERROR(#{what => remove_user_transaction_failed, reason => Result})
787 end,
788 2257 Acc1.
789
790 try_send_unsubscription_to_rosteritems(Acc, JID) ->
791 2257 try
792 2257 send_unsubscription_to_rosteritems(Acc, JID)
793 catch
794 E:R:S ->
795
:-(
?LOG_WARNING(#{what => roster_unsubcribe_failed,
796
:-(
class => E, reason => R, stacktrace => S}),
797
:-(
Acc
798 end.
799
800 %% For each contact with Subscription:
801 %% Both or From, send a "unsubscribed" presence stanza;
802 %% Both or To, send a "unsubscribe" presence stanza.
803 send_unsubscription_to_rosteritems(Acc, JID) ->
804 2257 Acc1 = get_user_roster(Acc, JID),
805 2257 RosterItems = mongoose_acc:get(roster, items, [], Acc1),
806 2257 lists:foreach(fun(RosterItem) ->
807 79 send_unsubscribing_presence(JID, RosterItem)
808 end, RosterItems),
809 2257 Acc1.
810
811 -spec send_unsubscribing_presence(From :: jid:jid(), Item :: roster()) -> ok | mongoose_acc:t().
812 send_unsubscribing_presence(From, #roster{ subscription = Subscription } = Item) ->
813 95 BareFrom = jid:to_bare(From),
814 95 ContactJID = jid:make_noprep(Item#roster.jid),
815 95 IsTo = Subscription == both orelse Subscription == to,
816 95 IsFrom = Subscription == both orelse Subscription == from,
817 95 case IsTo of
818 39 true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribe">>);
819 56 _ -> ok
820 end,
821 95 case IsFrom of
822 33 true -> send_presence_type(BareFrom, ContactJID, <<"unsubscribed">>);
823 62 _ -> ok
824 end.
825
826 send_presence_type(From, To, Type) ->
827 72 ejabberd_router:route(From, To,
828 #xmlel{name = <<"presence">>,
829 attrs = [{<<"type">>, Type}], children = []}).
830
831 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
832
833 -spec remove_domain(mongoose_hooks:simple_acc(),
834 mongooseim:host_type(), jid:lserver()) ->
835 mongoose_hooks:simple_acc().
836 remove_domain(Acc, HostType, Domain) ->
837
:-(
case backend_module:is_exported(mod_roster_backend, remove_domain_t, 2) of
838 true ->
839
:-(
F = fun() -> mod_roster_backend:remove_domain_t(HostType, Domain) end,
840
:-(
case transaction(HostType, F) of
841 {atomic, ok} ->
842
:-(
ok;
843 Result ->
844
:-(
?LOG_ERROR(#{what => remove_domain_transaction_failed,
845
:-(
reason => Result})
846 end;
847 false ->
848
:-(
ok
849 end,
850
:-(
Acc.
851
852 -spec set_items(mongooseim:host_type(), jid:jid(), exml:element()) -> ok | {error, any()}.
853 set_items(HostType, #jid{luser = LUser, lserver = LServer}, SubEl) ->
854 67 F = fun() -> set_items_t(HostType, LUser, LServer, SubEl) end,
855 67 case transaction(HostType, F) of
856 {atomic, _} ->
857 67 ok;
858 Result ->
859
:-(
{error, Result}
860 end.
861
862 set_items_t(HostType, LUser, LServer, #xmlel{children = Els}) ->
863 67 lists:foreach(fun(El) ->
864 67 process_item_set_t(HostType, LUser, LServer, El)
865 end, Els).
866
867 %% @doc add a contact to roster, or update
868 -spec set_roster_entry(mongooseim:host_type(), jid:jid(), jid:jid(), binary(), [binary()]) ->
869 ok | {error, any()}.
870 set_roster_entry(HostType, UserJid, ContactJid, Name, Groups) ->
871 15 UpdateF = fun(Item) -> Item#roster{name = Name, groups = Groups} end,
872 15 set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF).
873
874 %% @doc remove from roster - in practice it means changing subscription state to 'remove'
875 -spec remove_from_roster(mongooseim:host_type(), UserJid :: jid:jid(), ContactJid :: jid:jid()) ->
876 ok | {error, any()}.
877 remove_from_roster(HostType, UserJid, ContactJid) ->
878 10 UpdateF = fun(Item) -> Item#roster{subscription = remove} end,
879 10 set_roster_item(HostType, ContactJid, UserJid, UserJid, UpdateF).
880
881 process_item_set_t(HostType, LUser, LServer,
882 #xmlel{attrs = Attrs, children = Els}) ->
883 67 JID1 = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)),
884 67 case JID1 of
885
:-(
error -> ok;
886 _ ->
887 67 JID = {JID1#jid.user, JID1#jid.server,
888 JID1#jid.resource},
889 67 LJID = {JID1#jid.luser, JID1#jid.lserver,
890 JID1#jid.lresource},
891 67 Item = #roster{usj = {LUser, LServer, LJID},
892 us = {LUser, LServer}, jid = JID},
893 67 Item1 = process_item_attrs_ws(Item, Attrs),
894 67 Item2 = process_item_els(Item1, Els),
895 67 case Item2#roster.subscription of
896 14 remove -> del_roster_t(HostType, LUser, LServer, LJID);
897 53 _ -> update_roster_t(HostType, Item2)
898 end
899 end;
900
:-(
process_item_set_t(_HostType, _LUser, _LServer, _) -> ok.
901
902 process_item_attrs_ws(Item, [{<<"jid">>, Val} | Attrs]) ->
903 67 case jid:from_binary(Val) of
904 error ->
905
:-(
process_item_attrs_ws(Item, Attrs);
906 JID1 ->
907 67 JID = {JID1#jid.luser, JID1#jid.lserver, JID1#jid.lresource},
908 67 process_item_attrs_ws(Item#roster{jid = JID}, Attrs)
909 end;
910 process_item_attrs_ws(Item, [{<<"name">>, Val} | Attrs]) ->
911 53 process_item_attrs_ws(Item#roster{name = Val}, Attrs);
912 process_item_attrs_ws(Item, [{<<"subscription">>, <<"remove">>} | Attrs]) ->
913 14 process_item_attrs_ws(Item#roster{subscription = remove}, Attrs);
914 process_item_attrs_ws(Item, [{<<"subscription">>, <<"none">>} | Attrs]) ->
915
:-(
process_item_attrs_ws(Item#roster{subscription = none}, Attrs);
916 process_item_attrs_ws(Item, [{<<"subscription">>, <<"both">>} | Attrs]) ->
917 46 process_item_attrs_ws(Item#roster{subscription = both}, Attrs);
918 process_item_attrs_ws(Item, [{<<"subscription">>, <<"from">>} | Attrs]) ->
919 4 process_item_attrs_ws(Item#roster{subscription = from}, Attrs);
920 process_item_attrs_ws(Item, [{<<"subscription">>, <<"to">>} | Attrs]) ->
921 3 process_item_attrs_ws(Item#roster{subscription = to}, Attrs);
922 process_item_attrs_ws(Item, [_ | Attrs]) ->
923
:-(
process_item_attrs_ws(Item, Attrs);
924 process_item_attrs_ws(Item, []) ->
925 67 Item.
926
927 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
928
929 -spec get_jid_info(HookAcc, mongooseim:host_type(),
930 ToJID :: jid:jid(),
931 JID :: jid:jid() | jid:ljid()) -> HookAcc
932 when HookAcc :: {subscription_state(), [binary()]}.
933 get_jid_info(_, HostType, ToJID, JID) ->
934 67 case get_roster_entry(HostType, ToJID, JID, full) of
935
:-(
error -> {none, []};
936 does_not_exist ->
937 52 LRJID = jid:to_bare(jid:to_lower(JID)),
938 52 case get_roster_entry(HostType, ToJID, LRJID, full) of
939
:-(
error -> {none, []};
940 30 does_not_exist -> {none, []};
941 22 R -> {R#roster.subscription, R#roster.groups}
942 end;
943 15 Re -> {Re#roster.subscription, Re#roster.groups}
944 end.
945
946 get_roster_old(HostType, #jid{lserver = LServer} = JID) ->
947 2 get_roster_old(HostType, LServer, JID).
948
949 get_roster_old(HostType, DestServer, JID) ->
950 28 A = mongoose_acc:new(#{ location => ?LOCATION,
951 lserver => DestServer,
952 host_type => HostType,
953 element => undefined }),
954 28 A2 = mongoose_hooks:roster_get(A, JID),
955 28 mongoose_acc:get(roster, items, [], A2).
956
957 -spec item_to_map(roster()) -> map().
958 item_to_map(#roster{} = Roster) ->
959 19 ContactJid = jid:make_noprep(jid:to_bare(Roster#roster.jid)),
960 19 ContactName = Roster#roster.name,
961 19 Subs = Roster#roster.subscription,
962 19 Groups = Roster#roster.groups,
963 19 Ask = Roster#roster.ask,
964 19 #{jid => ContactJid, name => ContactName, subscription => Subs,
965 groups => Groups, ask => Ask}.
966
967 config_metrics(HostType) ->
968 221 OptsToReport = [{backend, mnesia}], % list of tuples {option, default_value}
969 221 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport).
970
971 %% Backend API wrappers
972
973 -spec transaction(mongooseim:host_type(), fun(() -> any())) ->
974 {aborted, any()} | {atomic, any()} | {error, any()}.
975 transaction(HostType, F) ->
976 2668 mod_roster_backend:transaction(HostType, F).
977
978 -spec read_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
979 binary() | error.
980 read_roster_version(HostType, LUser, LServer) ->
981 4 mod_roster_backend:read_roster_version(HostType, LUser, LServer).
982
983 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver()) -> version().
984 write_roster_version(HostType, LUser, LServer) ->
985 1 write_roster_version(HostType, LUser, LServer, no_transaction).
986
987 -spec write_roster_version_t(mongooseim:host_type(), jid:luser(), jid:lserver()) -> version().
988 write_roster_version_t(HostType, LUser, LServer) ->
989 1 write_roster_version(HostType, LUser, LServer, in_transaction).
990
991 -spec write_roster_version(mongooseim:host_type(), jid:luser(), jid:lserver(),
992 transaction_state()) -> version().
993 write_roster_version(HostType, LUser, LServer, TransactionState) ->
994 2 Ver = sha:sha1_hex(term_to_binary(os:timestamp())),
995 2 mod_roster_backend:write_roster_version(HostType, LUser, LServer, TransactionState, Ver),
996 2 Ver.
997
998 -spec get_roster(mongooseim:host_type(), jid:luser(), jid:lserver()) -> [roster()].
999 get_roster(HostType, LUser, LServer) ->
1000 2378 mod_roster_backend:get_roster(HostType, LUser, LServer).
1001
1002 -spec get_roster_entry(mongooseim:host_type(), jid:jid(), contact(), entry_format()) ->
1003 roster() | does_not_exist | error.
1004 get_roster_entry(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) ->
1005 124 mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, no_transaction, Format).
1006
1007 -spec get_roster_entry_t(mongooseim:host_type(), jid:jid(), contact(), entry_format()) ->
1008 roster() | does_not_exist | error.
1009 get_roster_entry_t(HostType, #jid{luser = LUser, lserver = LServer}, LJid, Format) ->
1010 348 mod_roster_backend:get_roster_entry(HostType, LUser, LServer, LJid, in_transaction, Format).
1011
1012 -spec roster_subscribe_t(mongooseim:host_type(), roster()) -> ok.
1013 roster_subscribe_t(HostType, Item) ->
1014 263 mod_roster_backend:roster_subscribe_t(HostType, Item).
1015
1016 -spec update_roster_t(mongooseim:host_type(), roster()) -> ok.
1017 update_roster_t(HostType, Item) ->
1018 98 mod_roster_backend:update_roster_t(HostType, Item).
1019
1020 -spec del_roster_t(mongooseim:host_type(), jid:luser(), jid:lserver(), contact()) -> ok.
1021 del_roster_t(HostType, LUser, LServer, LJID) ->
1022 33 mod_roster_backend:del_roster_t(HostType, LUser, LServer, LJID).
Line Hits Source