]> granicus.if.org Git - ejabberd/commitdiff
Add option user_mucsub_from_muc_archive to mod_muc
authorPaweł Chmielowski <pchmielowski@process-one.net>
Thu, 28 Mar 2019 16:42:25 +0000 (17:42 +0100)
committerPaweł Chmielowski <pchmielowski@process-one.net>
Thu, 28 Mar 2019 16:42:25 +0000 (17:42 +0100)
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
src/mod_muc.erl
src/mod_muc_admin.erl
src/mod_muc_room.erl

index e066f6d0e9709c447f46ba6ef0f80ec868e16566..8d672a7b16f51c2b9dd2922b60729c66e06c2244 100644 (file)
 -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)},
index 366af53d6a405a23797cdcdace05f66a66a2e877..c2701fa2b6673007f46dd1e60f090ba751dddc87 100644 (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),
index d7122afbee8bc35579b2e0579a4b5d3464040fdd..3672c2b9cdeaffe4ce599c21f1a401daa06bbb07 100644 (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,
index 1627b8866a6b7528d3295d1f22f8f6db81fe7d24..cbc967da42ab8b75fb9389e954856a32b58dfec4 100644 (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{