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