aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc_room.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc_room.erl')
-rw-r--r--src/mod_muc_room.erl450
1 files changed, 323 insertions, 127 deletions
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index 2fa08dc79..035e851fd 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -641,7 +641,7 @@ handle_event({service_message, Msg}, _StateName,
MessagePkt = #message{type = groupchat, body = xmpp:mk_text(Msg)},
send_wrapped_multiple(
StateData#state.jid,
- get_users_and_subscribers(StateData),
+ get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
MessagePkt,
?NS_MUCSUB_NODES_MESSAGES,
StateData),
@@ -705,7 +705,7 @@ handle_sync_event({change_state, NewStateData}, _From,
true ->
ok;
_ ->
- erlang:put(muc_subscribers, NewStateData#state.subscribers)
+ erlang:put(muc_subscribers, NewStateData#state.muc_subscribers#muc_subscribers.subscribers)
end,
{reply, {ok, NewStateData}, StateName, NewStateData};
handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData) ->
@@ -717,8 +717,10 @@ handle_sync_event({process_item_change, Item, UJID}, _From, StateName, StateData
{reply, {ok, NSD}, StateName, NSD}
end;
handle_sync_event(get_subscribers, _From, StateName, StateData) ->
- JIDs = lists:map(fun jid:make/1,
- maps:keys(StateData#state.subscribers)),
+ JIDs = muc_subscribers_fold(
+ fun(_LBareJID, #subscriber{jid = JID}, Acc) ->
+ [JID | Acc]
+ end, [], StateData#state.muc_subscribers),
{reply, {ok, JIDs}, StateName, StateData};
handle_sync_event({muc_subscribe, From, Nick, Nodes}, _From,
StateName, StateData) ->
@@ -762,7 +764,8 @@ handle_sync_event({muc_unsubscribe, From}, _From, StateName,
{reply, {error, get_error_text(Err)}, StateName, StateData}
end;
handle_sync_event({is_subscribed, From}, _From, StateName, StateData) ->
- IsSubs = try maps:get(jid:split(From), StateData#state.subscribers) of
+ IsSubs = try muc_subscribers_get(
+ jid:split(From), StateData#state.muc_subscribers) of
#subscriber{nick = Nick, nodes = Nodes} -> {true, Nick, Nodes}
catch _:{badkey, _} -> false
end,
@@ -899,7 +902,8 @@ terminate(Reason, _StateName,
_ -> ok
end,
tab_remove_online_user(JID, StateData)
- end, [], get_users_and_subscribers(StateData)),
+ end, [], get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
disable_hibernate_timer(StateData),
case StateData#state.hibernate_timer of
@@ -991,7 +995,7 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
end,
send_wrapped_multiple(
jid:replace_resource(StateData#state.jid, FromNick),
- get_users_and_subscribers(StateData),
+ get_users_and_subscribers_with_node(Node, StateData),
NewPacket, Node, NewStateData1),
NewStateData2 = case has_body_or_subject(NewPacket) of
true ->
@@ -1197,8 +1201,8 @@ get_participant_data(From, StateData) ->
#user{nick = FromNick, role = Role} ->
{FromNick, Role}
catch _:{badkey, _} ->
- try maps:get(jid:tolower(jid:remove_resource(From)),
- StateData#state.subscribers) of
+ try muc_subscribers_get(jid:tolower(jid:remove_resource(From)),
+ StateData#state.muc_subscribers) of
#subscriber{nick = FromNick} ->
{FromNick, none}
catch _:{badkey, _} ->
@@ -1329,7 +1333,7 @@ maybe_strip_status_from_presence(From, Packet, StateData) ->
close_room_if_temporary_and_empty(StateData1) ->
case not (StateData1#state.config)#config.persistent
andalso maps:size(StateData1#state.users) == 0
- andalso maps:size(StateData1#state.subscribers) == 0 of
+ andalso muc_subscribers_size(StateData1#state.muc_subscribers) == 0 of
true ->
?INFO_MSG("Destroyed MUC room ~ts because it's temporary "
"and empty",
@@ -1342,6 +1346,17 @@ close_room_if_temporary_and_empty(StateData1) ->
-spec get_users_and_subscribers(state()) -> users().
get_users_and_subscribers(StateData) ->
+ get_users_and_subscribers_aux(
+ StateData#state.muc_subscribers#muc_subscribers.subscribers,
+ StateData).
+
+-spec get_users_and_subscribers_with_node(binary(), state()) -> users().
+get_users_and_subscribers_with_node(Node, StateData) ->
+ get_users_and_subscribers_aux(
+ muc_subscribers_get_by_node(Node, StateData#state.muc_subscribers),
+ StateData).
+
+get_users_and_subscribers_aux(Subscribers, StateData) ->
OnlineSubscribers = maps:fold(
fun(LJID, _, Acc) ->
LBareJID = jid:remove_resource(LJID),
@@ -1365,7 +1380,7 @@ get_users_and_subscribers(StateData) ->
true ->
Acc
end
- end, StateData#state.users, StateData#state.subscribers).
+ end, StateData#state.users, Subscribers).
-spec is_user_online(jid(), state()) -> boolean().
is_user_online(JID, StateData) ->
@@ -1375,7 +1390,7 @@ is_user_online(JID, StateData) ->
-spec is_subscriber(jid(), state()) -> boolean().
is_subscriber(JID, StateData) ->
LJID = jid:tolower(jid:remove_resource(JID)),
- maps:is_key(LJID, StateData#state.subscribers).
+ muc_subscribers_is_key(LJID, StateData#state.muc_subscribers).
%% Check if the user is occupant of the room, or at least is an admin or owner.
-spec is_occupant_or_admin(jid(), state()) -> boolean().
@@ -1869,16 +1884,15 @@ set_subscriber(JID, Nick, Nodes,
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = jid:remove_resource(JID),
LBareJID = jid:tolower(BareJID),
- Subscribers = maps:put(LBareJID,
- #subscriber{jid = BareJID,
- nick = Nick,
- nodes = Nodes},
- StateData#state.subscribers),
- Nicks = maps:put(Nick, [LBareJID], StateData#state.subscriber_nicks),
- NewStateData = StateData#state{subscribers = Subscribers,
- subscriber_nicks = Nicks},
+ MUCSubscribers =
+ muc_subscribers_put(
+ #subscriber{jid = BareJID,
+ nick = Nick,
+ nodes = Nodes},
+ StateData#state.muc_subscribers),
+ NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
store_room(NewStateData, [{add_subscription, BareJID, Nick, Nodes}]),
- case not maps:is_key(LBareJID, StateData#state.subscribers) of
+ case not muc_subscribers_is_key(LBareJID, StateData#state.muc_subscribers) of
true ->
send_subscriptions_change_notifications(BareJID, Nick, subscribe, NewStateData),
ejabberd_hooks:run(muc_subscribed, ServerHost, [ServerHost, Room, Host, BareJID]);
@@ -1956,7 +1970,8 @@ add_user_presence_un(JID, Presence, StateData) ->
-spec find_jids_by_nick(binary(), state()) -> [jid()].
find_jids_by_nick(Nick, StateData) ->
Users = case maps:get(Nick, StateData#state.nicks, []) of
- [] -> maps:get(Nick, StateData#state.subscriber_nicks, []);
+ [] -> muc_subscribers_get_by_nick(
+ Nick, StateData#state.muc_subscribers);
Us -> Us
end,
[jid:make(LJID) || LJID <- Users].
@@ -2020,10 +2035,10 @@ is_nick_change(JID, Nick, StateData) ->
nick_collision(User, Nick, StateData) ->
UserOfNick = case find_jid_by_nick(Nick, StateData) of
false ->
- try maps:get(Nick, StateData#state.subscriber_nicks) of
- [J] -> J
- catch _:{badkey, _} -> false
- end;
+ case muc_subscribers_get_by_nick(Nick, StateData#state.muc_subscribers) of
+ [J] -> J;
+ [] -> false
+ end;
J -> J
end,
(UserOfNick /= false andalso
@@ -2433,6 +2448,11 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
false -> {none, #presence{type = unavailable}}
end,
Affiliation = get_affiliation(LJID, StateData),
+ Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
+ true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
+ false -> ?NS_MUCSUB_NODES_PRESENCE
+ end,
+ Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
UserMap =
case is_room_overcrowded(StateData) orelse
(not (presence_broadcast_allowed(NJID, StateData) orelse
@@ -2440,7 +2460,10 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
true ->
#{LNJID => UserInfo};
false ->
- get_users_and_subscribers(StateData)
+ %% TODO: optimize further
+ UM1 = get_users_and_subscribers_with_node(Node1, StateData),
+ UM2 = get_users_and_subscribers_with_node(Node2, StateData),
+ maps:merge(UM1, UM2)
end,
maps:fold(
fun(LUJID, Info, _) ->
@@ -2465,10 +2488,6 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
Packet = xmpp:set_subtag(
Pres, #muc_user{items = [Item],
status_codes = StatusCodes}),
- Node1 = case is_ra_changed(NJID, IsInitialPresence, StateData, OldStateData) of
- true -> ?NS_MUCSUB_NODES_AFFILIATIONS;
- false -> ?NS_MUCSUB_NODES_PRESENCE
- end,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node1, StateData),
Type = xmpp:get_type(Packet),
@@ -2476,7 +2495,6 @@ send_new_presence(NJID, Reason, IsInitialPresence, StateData, OldStateData) ->
IsOccupant = Info#user.last_presence /= undefined,
if (IsSubscriber and not IsOccupant) and
(IsInitialPresence or (Type == unavailable)) ->
- Node2 = ?NS_MUCSUB_NODES_PARTICIPANTS,
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet, Node2, StateData);
true ->
@@ -2607,11 +2625,13 @@ send_nick_changing(JID, OldNick, StateData,
end;
(_, _, _) ->
ok
- end, ok, get_users_and_subscribers(StateData)).
+ end, ok, get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_PRESENCE, StateData)).
-spec maybe_send_affiliation(jid(), affiliation(), state()) -> ok.
maybe_send_affiliation(JID, Affiliation, StateData) ->
LJID = jid:tolower(JID),
+ %% TODO: there should be a better way to check IsOccupant
Users = get_users_and_subscribers(StateData),
IsOccupant = case LJID of
{LUser, LServer, <<"">>} ->
@@ -2637,7 +2657,8 @@ send_affiliation(JID, Affiliation, StateData) ->
role = none},
Message = #message{id = p1_rand:get_string(),
sub_els = [#muc_user{items = [Item]}]},
- Users = get_users_and_subscribers(StateData),
+ Users = get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
Recipients = case (StateData#state.config)#config.anonymous of
true ->
maps:filter(fun(_, #user{role = moderator}) ->
@@ -3271,6 +3292,13 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
StateData) ->
#user{jid = RealJID, nick = Nick} = maps:get(jid:tolower(UJID), StateData#state.users),
ActorNick = get_actor_nick(MJID, StateData),
+ %% TODO: optimize further
+ UserMap =
+ maps:merge(
+ get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_AFFILIATIONS, StateData),
+ get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_PARTICIPANTS, StateData)),
maps:fold(
fun(LJID, Info, _) ->
IsSelfPresence = jid:tolower(UJID) == LJID,
@@ -3304,7 +3332,7 @@ send_kickban_presence1(MJID, UJID, Reason, Code, Affiliation,
true ->
ok
end
- end, ok, get_users_and_subscribers(StateData)).
+ end, ok, UserMap).
-spec get_actor_nick(undefined | jid(), state()) -> binary().
get_actor_nick(undefined, _StateData) ->
@@ -3720,7 +3748,8 @@ send_config_change_info(New, #state{config = Old} = StateData) ->
id = p1_rand:get_string(),
sub_els = [#muc_user{status_codes = Codes}]},
send_wrapped_multiple(StateData#state.jid,
- get_users_and_subscribers(StateData),
+ get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_CONFIG, StateData),
Message,
?NS_MUCSUB_NODES_CONFIG,
StateData);
@@ -3872,26 +3901,23 @@ set_opts([{Opt, Val} | Opts], StateData) ->
StateData#state{config =
(StateData#state.config)#config{lang = Val}};
subscribers ->
- {Subscribers, Nicks} =
- lists:foldl(
- fun({JID, Nick, Nodes}, {SubAcc, NickAcc}) ->
- BareJID = case JID of
- #jid{} -> jid:remove_resource(JID);
- _ ->
- ?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
- jid:remove_resource(jid:make(JID))
- end,
- LBareJID = jid:tolower(BareJID),
- {maps:put(
- LBareJID,
- #subscriber{jid = BareJID,
- nick = Nick,
- nodes = Nodes},
- SubAcc),
- maps:put(Nick, [LBareJID], NickAcc)}
- end, {#{}, #{}}, Val),
- StateData#state{subscribers = Subscribers,
- subscriber_nicks = Nicks};
+ MUCSubscribers =
+ lists:foldl(
+ fun({JID, Nick, Nodes}, MUCSubs) ->
+ BareJID =
+ case JID of
+ #jid{} -> jid:remove_resource(JID);
+ _ ->
+ ?ERROR_MSG("Invalid subscriber JID in set_opts ~p", [JID]),
+ jid:remove_resource(jid:make(JID))
+ end,
+ muc_subscribers_put(
+ #subscriber{jid = BareJID,
+ nick = Nick,
+ nodes = Nodes},
+ MUCSubs)
+ end, muc_subscribers_new(), Val),
+ StateData#state{muc_subscribers = MUCSubscribers};
affiliations ->
StateData#state{affiliations = maps:from_list(Val)};
subject ->
@@ -3926,12 +3952,12 @@ set_vcard_xupdate(State) ->
-spec make_opts(state()) -> [{atom(), any()}].
make_opts(StateData) ->
Config = StateData#state.config,
- Subscribers = maps:fold(
+ Subscribers = muc_subscribers_fold(
fun(_LJID, Sub, Acc) ->
[{Sub#subscriber.jid,
Sub#subscriber.nick,
Sub#subscriber.nodes}|Acc]
- end, [], StateData#state.subscribers),
+ end, [], StateData#state.muc_subscribers),
[?MAKE_CONFIG_OPT(#config.title), ?MAKE_CONFIG_OPT(#config.description),
?MAKE_CONFIG_OPT(#config.allow_change_subj),
?MAKE_CONFIG_OPT(#config.allow_query_users),
@@ -4013,7 +4039,8 @@ destroy_room(DEl, StateData) ->
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
Info#user.jid, Packet,
?NS_MUCSUB_NODES_CONFIG, StateData)
- end, ok, get_users_and_subscribers(StateData)),
+ end, ok, get_users_and_subscribers_with_node(
+ ?NS_MUCSUB_NODES_CONFIG, StateData)),
forget_room(StateData),
{result, undefined, stop}.
@@ -4248,30 +4275,35 @@ process_iq_mucsub(From,
sub_els = [#muc_subscribe{nick = Nick}]} = Packet,
StateData) ->
LBareJID = jid:tolower(jid:remove_resource(From)),
- try maps:get(LBareJID, StateData#state.subscribers) of
+ try muc_subscribers_get(LBareJID, StateData#state.muc_subscribers) of
#subscriber{nick = Nick1} when Nick1 /= Nick ->
Nodes = get_subscription_nodes(Packet),
- case {nick_collision(From, Nick, StateData),
- mod_muc:can_use_nick(StateData#state.server_host,
- StateData#state.host,
- From, Nick)} of
- {true, _} ->
+ case nick_collision(From, Nick, StateData) of
+ true ->
ErrText = ?T("That nickname is already in use by another occupant"),
{error, xmpp:err_conflict(ErrText, Lang)};
- {_, false} ->
- Err = case Nick of
- <<>> ->
- xmpp:err_jid_malformed(?T("Nickname can't be empty"),
- Lang);
- _ ->
- xmpp:err_conflict(?T("That nickname is registered"
- " by another person"), Lang)
- end,
- {error, Err};
- _ ->
- NewStateData = set_subscriber(From, Nick, Nodes, StateData),
- {result, subscribe_result(Packet), NewStateData}
- end;
+ false ->
+ case mod_muc:can_use_nick(StateData#state.server_host,
+ StateData#state.host,
+ From, Nick) of
+ false ->
+ Err = case Nick of
+ <<>> ->
+ xmpp:err_jid_malformed(
+ ?T("Nickname can't be empty"),
+ Lang);
+ _ ->
+ xmpp:err_conflict(
+ ?T("That nickname is registered"
+ " by another person"), Lang)
+ end,
+ {error, Err};
+ true ->
+ NewStateData =
+ set_subscriber(From, Nick, Nodes, StateData),
+ {result, subscribe_result(Packet), NewStateData}
+ end
+ end;
#subscriber{} ->
Nodes = get_subscription_nodes(Packet),
NewStateData = set_subscriber(From, Nick, Nodes, StateData),
@@ -4298,12 +4330,9 @@ process_iq_mucsub(From, #iq{type = set, sub_els = [#muc_unsubscribe{}]},
#state{room = Room, host = Host, server_host = ServerHost} = StateData) ->
BareJID = jid:remove_resource(From),
LBareJID = jid:tolower(BareJID),
- try maps:get(LBareJID, StateData#state.subscribers) of
- #subscriber{nick = Nick} ->
- Nicks = maps:remove(Nick, StateData#state.subscriber_nicks),
- Subscribers = maps:remove(LBareJID, StateData#state.subscribers),
- NewStateData = StateData#state{subscribers = Subscribers,
- subscriber_nicks = Nicks},
+ try muc_subscribers_remove_exn(LBareJID, StateData#state.muc_subscribers) of
+ {MUCSubscribers, #subscriber{nick = Nick}} ->
+ NewStateData = StateData#state{muc_subscribers = MUCSubscribers},
store_room(NewStateData, [{del_subscription, LBareJID}]),
send_subscriptions_change_notifications(BareJID, Nick, unsubscribe, StateData),
ejabberd_hooks:run(muc_unsubscribed, ServerHost, [ServerHost, Room, Host, BareJID]),
@@ -4326,7 +4355,7 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
true ->
ShowJid = IsModerator orelse
(StateData#state.config)#config.anonymous == false,
- Subs = maps:fold(
+ Subs = muc_subscribers_fold(
fun(_, #subscriber{jid = J, nick = N, nodes = Nodes}, Acc) ->
case ShowJid of
true ->
@@ -4334,7 +4363,7 @@ process_iq_mucsub(From, #iq{type = get, lang = Lang,
_ ->
[#muc_subscription{nick = N, events = Nodes}|Acc]
end
- end, [], StateData#state.subscribers),
+ end, [], StateData#state.muc_subscribers),
{result, #muc_subscriptions{list = Subs}, StateData};
_ ->
Txt = ?T("Moderator privileges required"),
@@ -4347,8 +4376,7 @@ process_iq_mucsub(_From, #iq{type = get, lang = Lang}, _StateData) ->
-spec remove_subscriptions(state()) -> state().
remove_subscriptions(StateData) ->
if not (StateData#state.config)#config.allow_subscription ->
- StateData#state{subscribers = #{},
- subscriber_nicks = #{}};
+ StateData#state{muc_subscribers = muc_subscribers_new()};
true ->
StateData
end.
@@ -4597,7 +4625,7 @@ store_room(StateData, ChangesHints) ->
true ->
ok;
_ ->
- erlang:put(muc_subscribers, StateData#state.subscribers)
+ erlang:put(muc_subscribers, StateData#state.muc_subscribers#muc_subscribers.subscribers)
end,
ShouldStore = case (StateData#state.config)#config.persistent of
true ->
@@ -4611,7 +4639,15 @@ store_room(StateData, ChangesHints) ->
end
end,
if ShouldStore ->
- store_room_no_checks(StateData, ChangesHints);
+ case erlang:function_exported(Mod, store_changes, 4) of
+ true when ChangesHints /= [] ->
+ mod_muc:store_changes(
+ StateData#state.server_host,
+ StateData#state.host, StateData#state.room,
+ ChangesHints);
+ _ ->
+ store_room_no_checks(StateData, ChangesHints)
+ end;
true ->
ok
end.
@@ -4624,37 +4660,52 @@ store_room_no_checks(StateData, ChangesHints) ->
-spec send_subscriptions_change_notifications(jid(), binary(), subscribe|unsubscribe, state()) -> ok.
send_subscriptions_change_notifications(From, Nick, Type, State) ->
- maps:fold(fun(_, #subscriber{nodes = Nodes, jid = JID}, _) ->
- case lists:member(?NS_MUCSUB_NODES_SUBSCRIBERS, Nodes) of
- true ->
- ShowJid = case (State#state.config)#config.anonymous == false orelse
- get_role(JID, State) == moderator orelse
- get_default_role(get_affiliation(JID, State), State) == moderator of
- true -> true;
- _ -> false
- end,
- Payload = case {Type, ShowJid} of
- {subscribe, true} ->
- #muc_subscribe{jid = From, nick = Nick};
- {subscribe, _} ->
- #muc_subscribe{nick = Nick};
- {unsubscribe, true} ->
- #muc_unsubscribe{jid = From, nick = Nick};
- {unsubscribe, _} ->
- #muc_unsubscribe{nick = Nick}
- end,
- Packet = #message{
- sub_els = [#ps_event{
- items = #ps_items{
- node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
- items = [#ps_item{
- id = p1_rand:get_string(),
- sub_els = [Payload]}]}}]},
- ejabberd_router:route(xmpp:set_from_to(Packet, State#state.jid, JID));
- false ->
- ok
- end
- end, ok, State#state.subscribers).
+ {WJ, WN} =
+ maps:fold(
+ fun(_, #subscriber{jid = JID}, {WithJid, WithNick}) ->
+ case (State#state.config)#config.anonymous == false orelse
+ get_role(JID, State) == moderator orelse
+ get_default_role(get_affiliation(JID, State), State) == moderator of
+ true ->
+ {[JID | WithJid], WithNick};
+ _ ->
+ {WithJid, [JID | WithNick]}
+ end
+ end, {[], []},
+ muc_subscribers_get_by_node(?NS_MUCSUB_NODES_SUBSCRIBERS,
+ State#state.muc_subscribers)),
+ if WJ /= [] ->
+ Payload1 = case Type of
+ subscribe -> #muc_subscribe{jid = From, nick = Nick};
+ _ -> #muc_unsubscribe{jid = From, nick = Nick}
+ end,
+ Packet1 = #message{
+ sub_els = [#ps_event{
+ items = #ps_items{
+ node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
+ items = [#ps_item{
+ id = p1_rand:get_string(),
+ sub_els = [Payload1]}]}}]},
+ ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
+ WJ, Packet1, true);
+ true -> ok
+ end,
+ if WN /= [] ->
+ Payload2 = case Type of
+ subscribe -> #muc_subscribe{nick = Nick};
+ _ -> #muc_unsubscribe{nick = Nick}
+ end,
+ Packet2 = #message{
+ sub_els = [#ps_event{
+ items = #ps_items{
+ node = ?NS_MUCSUB_NODES_SUBSCRIBERS,
+ items = [#ps_item{
+ id = p1_rand:get_string(),
+ sub_els = [Payload2]}]}}]},
+ ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
+ WN, Packet2, true);
+ true -> ok
+ end.
-spec send_wrapped(jid(), jid(), stanza(), binary(), state()) -> ok.
send_wrapped(From, To, Packet, Node, State) ->
@@ -4666,7 +4717,7 @@ send_wrapped(From, To, Packet, Node, State) ->
_ -> false
end,
if IsOffline ->
- try maps:get(LBareTo, State#state.subscribers) of
+ try muc_subscribers_get(LBareTo, State#state.muc_subscribers) of
#subscriber{nodes = Nodes, jid = JID} ->
case lists:member(Node, Nodes) of
true ->
@@ -4713,7 +4764,7 @@ send_wrapped(From, To, Packet, Node, State) ->
ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
end.
--spec wrap(jid(), jid(), stanza(), binary(), binary()) -> message().
+-spec wrap(jid(), undefined | jid(), stanza(), binary(), binary()) -> message().
wrap(From, To, Packet, Node, Id) ->
El = xmpp:set_from_to(Packet, From, To),
#message{
@@ -4727,10 +4778,155 @@ wrap(From, To, Packet, Node, Id) ->
-spec send_wrapped_multiple(jid(), users(), stanza(), binary(), state()) -> ok.
send_wrapped_multiple(From, Users, Packet, Node, State) ->
+ {Dir, Wra} =
maps:fold(
- fun(_, #user{jid = To}, _) ->
- send_wrapped(From, To, Packet, Node, State)
- end, ok, Users).
+ fun(_, #user{jid = To, last_presence = LP}, {Direct, Wrapped} = Res) ->
+ IsOffline = LP == undefined,
+ if IsOffline ->
+ LBareTo = jid:tolower(jid:remove_resource(To)),
+ case muc_subscribers_find(LBareTo, State#state.muc_subscribers) of
+ {ok, #subscriber{nodes = Nodes}} ->
+ case lists:member(Node, Nodes) of
+ true ->
+ {Direct, [To | Wrapped]};
+ _ ->
+ %% TODO: check that this branch is never called
+ Res
+ end;
+ _ ->
+ Res
+ end;
+ true ->
+ {[To | Direct], Wrapped}
+ end
+ end, {[],[]}, Users),
+ case Dir of
+ [] -> ok;
+ _ ->
+ case Packet of
+ #presence{type = unavailable} ->
+ case xmpp:get_subtag(Packet, #muc_user{}) of
+ #muc_user{destroy = Destroy,
+ status_codes = Codes} ->
+ case Destroy /= undefined orelse
+ (lists:member(110,Codes) andalso
+ not lists:member(303, Codes)) of
+ true ->
+ ejabberd_router_multicast:route_multicast(
+ From, State#state.server_host, Dir,
+ #presence{id = p1_rand:get_string(),
+ type = unavailable}, false);
+ false ->
+ ok
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ ok
+ end,
+ ejabberd_router_multicast:route_multicast(From, State#state.server_host,
+ Dir, Packet, false)
+ end,
+ case Wra of
+ [] -> ok;
+ _ ->
+ MamEnabled = (State#state.config)#config.mam,
+ Id = case xmpp:get_subtag(Packet, #stanza_id{by = #jid{}}) of
+ #stanza_id{id = Id2} ->
+ Id2;
+ _ ->
+ p1_rand:get_string()
+ end,
+ NewPacket = wrap(From, undefined, Packet, Node, Id),
+ NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
+ ejabberd_router_multicast:route_multicast(State#state.jid, State#state.server_host,
+ Wra, NewPacket2, true)
+ end.
+
+%%%----------------------------------------------------------------------
+%%% #muc_subscribers API
+%%%----------------------------------------------------------------------
+
+-spec muc_subscribers_new() -> #muc_subscribers{}.
+muc_subscribers_new() ->
+ #muc_subscribers{}.
+
+-spec muc_subscribers_get(ljid(), #muc_subscribers{}) -> #subscriber{}.
+muc_subscribers_get({_, _, _} = LJID, MUCSubscribers) ->
+ maps:get(LJID, MUCSubscribers#muc_subscribers.subscribers).
+
+-spec muc_subscribers_find(ljid(), #muc_subscribers{}) ->
+ {ok, #subscriber{}} | error.
+muc_subscribers_find({_, _, _} = LJID, MUCSubscribers) ->
+ maps:find(LJID, MUCSubscribers#muc_subscribers.subscribers).
+
+-spec muc_subscribers_is_key(ljid(), #muc_subscribers{}) -> boolean().
+muc_subscribers_is_key({_, _, _} = LJID, MUCSubscribers) ->
+ maps:is_key(LJID, MUCSubscribers#muc_subscribers.subscribers).
+
+-spec muc_subscribers_size(#muc_subscribers{}) -> integer().
+muc_subscribers_size(MUCSubscribers) ->
+ maps:size(MUCSubscribers#muc_subscribers.subscribers).
+
+-spec muc_subscribers_fold(Fun, Acc, #muc_subscribers{}) -> Acc when
+ Fun :: fun((ljid(), #subscriber{}, Acc) -> Acc).
+muc_subscribers_fold(Fun, Init, MUCSubscribers) ->
+ maps:fold(Fun, Init, MUCSubscribers#muc_subscribers.subscribers).
+
+-spec muc_subscribers_get_by_nick(binary(), #muc_subscribers{}) -> [#subscriber{}].
+muc_subscribers_get_by_nick(Nick, MUCSubscribers) ->
+ maps:get(Nick, MUCSubscribers#muc_subscribers.subscriber_nicks, []).
+
+-spec muc_subscribers_get_by_node(binary(), #muc_subscribers{}) -> subscribers().
+muc_subscribers_get_by_node(Node, MUCSubscribers) ->
+ maps:get(Node, MUCSubscribers#muc_subscribers.subscriber_nodes, #{}).
+
+-spec muc_subscribers_remove_exn(ljid(), #muc_subscribers{}) ->
+ {#muc_subscribers{}, #subscriber{}}.
+muc_subscribers_remove_exn({_, _, _} = LJID, MUCSubscribers) ->
+ #muc_subscribers{subscribers = Subs,
+ subscriber_nicks = SubNicks,
+ subscriber_nodes = SubNodes} = MUCSubscribers,
+ Subscriber = maps:get(LJID, Subs),
+ #subscriber{nick = Nick, nodes = Nodes} = Subscriber,
+ NewSubNicks = maps:remove(Nick, SubNicks),
+ NewSubs = maps:remove(LJID, Subs),
+ NewSubNodes =
+ lists:foldl(
+ fun(Node, Acc) ->
+ NodeSubs = maps:get(Node, Acc, #{}),
+ NodeSubs2 = maps:remove(LJID, NodeSubs),
+ maps:put(Node, NodeSubs2, Acc)
+ end, SubNodes, Nodes),
+ {#muc_subscribers{subscribers = NewSubs,
+ subscriber_nicks = NewSubNicks,
+ subscriber_nodes = NewSubNodes}, Subscriber}.
+
+-spec muc_subscribers_put(#subscriber{}, #muc_subscribers{}) ->
+ #muc_subscribers{}.
+muc_subscribers_put(Subscriber, MUCSubscribers) ->
+ #subscriber{jid = JID,
+ nick = Nick,
+ nodes = Nodes} = Subscriber,
+ #muc_subscribers{subscribers = Subs,
+ subscriber_nicks = SubNicks,
+ subscriber_nodes = SubNodes} = MUCSubscribers,
+ LJID = jid:tolower(JID),
+ NewSubs = maps:put(LJID, Subscriber, Subs),
+ NewSubNicks = maps:put(Nick, [LJID], SubNicks),
+ NewSubNodes =
+ lists:foldl(
+ fun(Node, Acc) ->
+ NodeSubs = maps:get(Node, Acc, #{}),
+ NodeSubs2 = maps:put(LJID, Subscriber, NodeSubs),
+ maps:put(Node, NodeSubs2, Acc)
+ end, SubNodes, Nodes),
+ #muc_subscribers{subscribers = NewSubs,
+ subscriber_nicks = NewSubNicks,
+ subscriber_nodes = NewSubNodes}.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Detect messange stanzas that don't have meaningful content