Add option user_mucsub_from_muc_archive to mod_muc

This option disable storing separate mucsub message for each individual
subscriber but instead when user fetches archive virtual mucsub messages
are generated from muc archives.
This commit is contained in:
Paweł Chmielowski 2019-03-28 17:42:25 +01:00
parent 063869603a
commit 8e05fd1d24
4 changed files with 150 additions and 18 deletions

View File

@ -76,8 +76,12 @@
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
-callback is_empty_for_user(binary(), binary()) -> boolean().
-callback is_empty_for_room(binary(), binary(), binary()) -> boolean().
-callback select_with_mucsub(binary(), jid(), jid(), mam_query:result(),
#rsm_set{} | undefined) ->
{[{binary(), non_neg_integer(), xmlel()}], boolean(), count()} |
{error, db_failure}.
-optional_callbacks([use_cache/1, cache_nodes/1]).
-optional_callbacks([use_cache/1, cache_nodes/1, select_with_mucsub/5]).
%%%===================================================================
%%% API
@ -886,16 +890,20 @@ may_enter_room(From, MUCState) ->
store_msg(Pkt, LUser, LServer, Peer, Dir) ->
case get_prefs(LUser, LServer) of
{ok, Prefs} ->
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt} of
{true, #message{meta = #{sm_copy := true}}} ->
UseMucArchive = gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive),
StoredInMucMam = UseMucArchive andalso xmpp:get_meta(Pkt, in_muc_mam, false),
case {should_archive_peer(LUser, LServer, Prefs, Peer), Pkt, StoredInMucMam} of
{true, #message{meta = #{sm_copy := true}}, _} ->
ok; % Already stored.
{true, _} ->
{true, _, true} ->
ok; % Stored in muc archive.
{true, _, _} ->
case ejabberd_hooks:run_fold(store_mam_message, LServer, Pkt,
[LUser, LServer, Peer, <<"">>, chat, Dir]) of
#message{} -> ok;
_ -> pass
end;
{false, _} ->
{false, _, _} ->
pass
end;
{error, _} ->
@ -1073,10 +1081,118 @@ select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
true ->
{[], true, 0};
false ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
case {MsgType, gen_mod:get_module_opt(LServer, ?MODULE, user_mucsub_from_muc_archive)} of
{chat, true} ->
select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM);
_ ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
end
end.
select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM) ->
MucHosts = mod_muc_admin:find_hosts(LServer),
Mod = gen_mod:db_mod(LServer, ?MODULE),
case proplists:get_value(with, Query) of
#jid{lserver = WithLServer} = MucJid ->
case lists:member(WithLServer, MucHosts) of
true ->
select(LServer, JidRequestor, MucJid, Query, RSM,
{groupchat, member, #state{config = #config{mam = true}}});
_ ->
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, chat)
end;
_ ->
case erlang:function_exported(Mod, select_with_mucsub, 5) of
true ->
Mod:select_with_mucsub(LServer, JidRequestor, JidArchive, Query, RSM);
false ->
case Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, chat) of
{error, _} = Err ->
Err;
{Entries, All, Count} ->
{Dir, Max} = case RSM of
#rsm_set{max = M, before = V} when is_binary(V) ->
{desc, M};
#rsm_set{max = M} ->
{asc, M};
_ ->
{asc, undefined}
end,
SubRooms = case mod_muc_admin:find_hosts(LServer) of
[First|_] ->
mod_muc:get_subscribed_rooms(First, JidRequestor);
_ ->
[]
end,
SubRoomJids = [Jid || #muc_subscription{jid = Jid} <- SubRooms],
{E2, A2, C2} = lists:foldl(
fun(MucJid, {E0, A0, C0}) ->
case select(LServer, JidRequestor, MucJid, Query, RSM,
{groupchat, member, #state{config = #config{mam = true}}}) of
{error, _} ->
{E0, A0, C0};
{E, A, C} ->
{lists:keymerge(2, E0, wrap_as_mucsub(E, JidRequestor)),
A0 andalso A, C0 + C}
end
end, {Entries, All, Count}, SubRoomJids),
case {Dir, Max} of
{_, undefined} ->
{E2, A2, C2};
{desc, _} ->
Start = case length(E2) of
Len when Len < Max -> 1;
Len -> Len - Max + 1
end,
Sub = lists:sublist(E2, Start, Max),
{Sub, if Sub == E2 -> A2; true -> false end, C2};
_ ->
Sub = lists:sublist(E2, 1, Max),
{Sub, if Sub == E2 -> A2; true -> false end, C2}
end
end
end
end.
wrap_as_mucsub(Messages, #jid{lserver = LServer} = Requester) ->
ReqBare = jid:remove_resource(Requester),
ReqServer = jid:make(<<>>, LServer, <<>>),
[{T1, T2, wrap_as_mucsub(M, ReqBare, ReqServer)} || {T1, T2, M} <- Messages].
wrap_as_mucsub(Message, Requester, ReqServer) ->
case Message of
#forwarded{delay = #delay{stamp = Stamp, desc = Desc},
sub_els = [#message{from = From, sub_els = SubEls} = Msg]} ->
{L1, SubEls2} = case lists:keytake(mam_archived, 1, xmpp:decode(SubEls)) of
{value, Arch, Rest} ->
{[Arch#mam_archived{by = Requester}], Rest};
_ ->
{[], SubEls}
end,
{Sid, L2, SubEls3} = case lists:keytake(stanza_id, 1, SubEls2) of
{value, #stanza_id{id = Sid0} = SID, Rest2} ->
{Sid0, [SID#stanza_id{by = Requester} | L1], Rest2};
_ ->
{p1_rand:get_string(), L1, SubEls2}
end,
Msg2 = Msg#message{to = Requester, sub_els = SubEls3},
#forwarded{delay = #delay{stamp = Stamp, desc = Desc, from = ReqServer},
sub_els = [
#message{from = jid:remove_resource(From), to = Requester,
id = Sid,
sub_els = [#ps_event{
items = #ps_items{
node = ?NS_MUCSUB_NODES_MESSAGES,
items = [#ps_item{
id = Sid,
sub_els = [Msg2]
}]}} | L2]}]};
_ ->
Message
end.
msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
peer = Peer, id = ID},
MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) ->
@ -1265,6 +1381,8 @@ mod_opt_type(request_activates_archiving) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(clear_archive_on_room_destroy) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(user_mucsub_from_muc_archive) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(access_preferences) ->
fun acl:access_rules_validator/1.
@ -1275,6 +1393,7 @@ mod_options(Host) ->
{compress_xml, false},
{clear_archive_on_room_destroy, true},
{access_preferences, all},
{user_mucsub_from_muc_archive, false},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},

View File

@ -65,7 +65,8 @@
iq_set_register_info/5,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4]).
can_use_nick/4,
get_subscribed_rooms/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@ -727,6 +728,11 @@ get_room_disco_item({Name, Host, Pid}, Query) ->
{error, notfound}
end.
-spec get_subscribed_rooms(binary(), jid()) -> [#muc_subscription{}].
get_subscribed_rooms(Host, User) ->
ServerHost = ejabberd_router:host_of_route(Host),
get_subscribed_rooms(ServerHost, Host, User).
get_subscribed_rooms(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),

View File

@ -28,7 +28,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, depends/2,
-export([start/2, stop/1, reload/3, depends/2,
muc_online_rooms/1, muc_online_rooms_by_regex/2,
muc_register_nick/3, muc_unregister_nick/2,
create_room_with_opts/4, create_room/3, destroy_room/2,
@ -41,7 +41,7 @@
set_room_affiliation/4, get_room_affiliations/2, get_room_affiliation/3,
web_menu_main/2, web_page_main/2, web_menu_host/3,
subscribe_room/4, unsubscribe_room/2, get_subscribers/2,
web_page_host/3, mod_options/1, get_commands_spec/0]).
web_page_host/3, mod_options/1, get_commands_spec/0, find_hosts/1]).
-include("logger.hrl").
-include("xmpp.hrl").
@ -100,18 +100,18 @@ get_commands_spec() ->
desc = "List existing rooms ('global' to get all vhosts) by regex",
policy = admin,
module = ?MODULE, function = muc_online_rooms_by_regex,
args_desc = ["Server domain where the MUC service is, or 'global' for all",
args_desc = ["Server domain where the MUC service is, or 'global' for all",
"Regex pattern for room name"],
args_example = ["example.com", "^prefix"],
result_desc = "List of rooms with summary",
result_example = [{"room1@muc.example.com", "true", 10},
result_example = [{"room1@muc.example.com", "true", 10},
{"room2@muc.example.com", "false", 10}],
args = [{host, binary}, {regex, binary}],
result = {rooms, {list, {room, {tuple,
[{jid, string},
{public, string},
{participants, integer}
]}}}}},
]}}}}},
#ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick to a User JID in the MUC service of a server",
module = ?MODULE, function = muc_register_nick,

View File

@ -4397,9 +4397,17 @@ send_wrapped(From, To, Packet, Node, State) ->
#subscriber{nodes = Nodes, jid = JID} ->
case lists:member(Node, Nodes) of
true ->
NewPacket = wrap(From, JID, Packet, Node),
MamEnabled = (State#state.config)#config.mam,
Id = case xmpp:get_subtag(Packet, #stanza_id{}) of
#stanza_id{id = Id2} ->
Id2;
_ ->
p1_rand:get_string()
end,
NewPacket = wrap(From, JID, Packet, Node, Id),
NewPacket2 = xmpp:put_meta(NewPacket, in_muc_mam, MamEnabled),
ejabberd_router:route(
xmpp:set_from_to(NewPacket, State#state.jid, JID));
xmpp:set_from_to(NewPacket2, State#state.jid, JID));
false ->
ok
end
@ -4432,10 +4440,9 @@ send_wrapped(From, To, Packet, Node, State) ->
ejabberd_router:route(xmpp:set_from_to(Packet, From, To))
end.
-spec wrap(jid(), jid(), stanza(), binary()) -> message().
wrap(From, To, Packet, Node) ->
-spec wrap(jid(), jid(), stanza(), binary(), binary()) -> message().
wrap(From, To, Packet, Node, Id) ->
El = xmpp:set_from_to(Packet, From, To),
Id = p1_rand:get_string(),
#message{
id = Id,
sub_els = [#ps_event{