From 8e05fd1d244c59f2f34b524aad692757b4bbf746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chmielowski?= Date: Thu, 28 Mar 2019 17:42:25 +0100 Subject: 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. --- src/mod_mam.erl | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 7 deletions(-) (limited to 'src/mod_mam.erl') diff --git a/src/mod_mam.erl b/src/mod_mam.erl index e066f6d0e..8d672a7b1 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -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)}, -- cgit v1.2.3