]> granicus.if.org Git - ejabberd/commitdiff
Fix RSM for conference disco#items
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 20 Nov 2016 15:08:49 +0000 (18:08 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 20 Nov 2016 15:08:49 +0000 (18:08 +0300)
src/mod_muc.erl
src/mod_muc_room.erl

index ea8bff5e310a653cebbc719e22ef7a0ad1c94744..9b475f69da93f6c90c0032e0a6f05198ee562140 100644 (file)
@@ -62,7 +62,7 @@
 
 -include("ejabberd.hrl").
 -include("logger.hrl").
-
+-include_lib("stdlib/include/ms_transform.hrl").
 -include("xmpp.hrl").
 -include("mod_muc.hrl").
 
@@ -175,8 +175,10 @@ init([Host, Opts]) ->
                                  <<"conference.@HOST@">>),
     Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
     Mod:init(Host, [{host, MyHost}|Opts]),
+    update_tables(),
     mnesia:create_table(muc_online_room,
                        [{ram_copies, [node()]},
+                        {type, ordered_set},
                         {attributes, record_info(fields, muc_online_room)}]),
     mnesia:add_table_copy(muc_online_room, node(), ram_copies),
     catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
@@ -497,8 +499,12 @@ process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
                           ServerHost, ?MODULE, max_rooms_discoitems,
                           fun(I) when is_integer(I), I>=0 -> I end,
                           100),
-    Items = iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM),
-    xmpp:make_iq_result(IQ, #disco_items{node = Node, items = Items});
+    case iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) of
+       {error, Err} ->
+           xmpp:make_error(IQ, Err);
+       {result, Result} ->
+           xmpp:make_iq_result(IQ, Result)
+    end;
 process_disco_items(#iq{lang = Lang} = IQ) ->
     Txt = <<"No module is handling this query">>,
     xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
@@ -597,76 +603,112 @@ register_room(Host, Room, Pid) ->
     end,
     mnesia:transaction(F).
 
-iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<"">>, undefined) ->
-    Rooms = get_vh_rooms(Host),
-    case erlang:length(Rooms) < MaxRoomsDiscoItems of
-       true ->
-           iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
-       false ->
-           iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, <<"nonemptyrooms">>, undefined)
-    end;
-iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"nonemptyrooms">>, undefined) ->
-    Empty = #disco_item{jid = jid:make(<<"conference.localhost">>),
-                       node = <<"emptyrooms">>,
-                       name = translate:translate(Lang, <<"Empty Rooms">>)},
-    Query = {get_disco_item, only_non_empty, From, Lang},
-    [Empty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
-iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, <<"emptyrooms">>, undefined) ->
-    iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
-iq_disco_items(Host, From, Lang, _MaxRoomsDiscoItems, _DiscoNode, Rsm) ->
-    {Rooms, RsmO} = get_vh_rooms(Host, Rsm),
-    RsmOut = jlib:rsm_encode(RsmO),
-    iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
-
-iq_disco_items_list(Host, Rooms, Query) ->
-    lists:zf(
-      fun(#muc_online_room{name_host = {Name, _Host}, pid = Pid}) ->
-             case catch gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
-                 {item, Desc} ->
-                     flush(),
-                     {true, #disco_item{jid = jid:make(Name, Host),
-                                        name = Desc}};
-                 _ ->
-                     false
-             end
-      end, Rooms).
+-spec iq_disco_items(binary(), jid(), binary(), integer(), binary(),
+                    rsm_set() | undefined) ->
+                           {result, disco_items()} | {error, stanza_error()}.
+iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
+  when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> ->
+    Count = get_vh_rooms_count(Host),
+    Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems ->
+                   {get_disco_item, only_non_empty, From, Lang};
+              Node == <<"nonemptyrooms">> ->
+                   {get_disco_item, only_non_empty, From, Lang};
+              Node == <<"emptyrooms">> ->
+                   {get_disco_item, 0, From, Lang};
+              true ->
+                   {get_disco_item, all, From, Lang}
+           end,
+    Items = get_vh_rooms(Host, Query, RSM),
+    ResRSM = case Items of
+                [_|_] when RSM /= undefined ->
+                    #disco_item{jid = #jid{luser = First}} = hd(Items),
+                    #disco_item{jid = #jid{luser = Last}} = lists:last(Items),
+                    #rsm_set{first = #rsm_first{data = First},
+                             last = Last,
+                             count = Count};
+                [] when RSM /= undefined ->
+                    #rsm_set{count = Count};
+                _ ->
+                    undefined
+            end,
+    {result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
+iq_disco_items(_Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
+    {error, xmpp:err_item_not_found(<<"Node not found">>, Lang)}.
+
+-spec get_vh_rooms(binary, term(), rsm_set() | undefined) -> [disco_item()].
+get_vh_rooms(Host, Query,
+            #rsm_set{max = Max, 'after' = After, before = undefined})
+  when is_binary(After), After /= <<"">> ->
+    lists:reverse(get_vh_rooms(next, {After, Host}, Query, 0, Max, []));
+get_vh_rooms(Host, Query,
+            #rsm_set{max = Max, 'after' = undefined, before = Before})
+  when is_binary(Before), Before /= <<"">> ->
+    get_vh_rooms(prev, {Before, Host}, Query, 0, Max, []);
+get_vh_rooms(Host, Query,
+            #rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
+    get_vh_rooms(last, {<<"">>, Host}, Query, 0, Max, []);
+get_vh_rooms(Host, Query, #rsm_set{max = Max}) ->
+    lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Query, 0, Max, []));
+get_vh_rooms(Host, Query, undefined) ->
+    lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Query, 0, undefined, [])).
+
+-spec get_vh_rooms(prev | next | last | first,
+                  {binary(), binary()}, term(),
+                  non_neg_integer(), non_neg_integer() | undefined,
+                  [disco_item()]) -> [disco_item()].
+get_vh_rooms(_Action, _Key, _Query, Count, Max, Items) when Count >= Max ->
+    Items;
+get_vh_rooms(Action, {_, Host} = Key, Query, Count, Max, Items) ->
+    Call = fun() ->
+                  case Action of
+                      prev -> mnesia:dirty_prev(muc_online_room, Key);
+                      next -> mnesia:dirty_next(muc_online_room, Key);
+                      last -> mnesia:dirty_last(muc_online_room);
+                      first -> mnesia:dirty_first(muc_online_room)
+                  end
+          end,
+    NewAction = case Action of
+                   last -> prev;
+                   first -> next;
+                   _ -> Action
+               end,
+    try Call() of
+       '$end_of_table' ->
+           Items;
+       {_, Host} = NewKey ->
+           case get_room_disco_item(NewKey, Query) of
+               {ok, Item} ->
+                   get_vh_rooms(NewAction, NewKey, Query,
+                                Count + 1, Max, [Item|Items]);
+               {error, _} ->
+                   get_vh_rooms(NewAction, NewKey, Query,
+                                Count, Max, Items)
+           end;
+       NewKey ->
+           get_vh_rooms(NewAction, NewKey, Query, Count, Max, Items)
+    catch _:{aborted, {badarg, _}} ->
+           Items
+    end.
 
-get_vh_rooms(_, _) ->
-    todo.
-    %% AllRooms = lists:sort(get_vh_rooms(Host)),
-    %% Count = erlang:length(AllRooms),
-    %% Guard = case Direction of
-    %%                 _ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
-    %%                 aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
-    %%                 before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
-    %%                 _ -> [{'==', {element, 2, '$1'}, Host}]
-    %%             end,
-    %% L = lists:sort(
-    %%           mnesia:dirty_select(muc_online_room,
-    %%                               [{#muc_online_room{name_host = '$1', _ = '_'},
-    %%                                 Guard,
-    %%                                 ['$_']}])),
-    %% L2 = if
-    %%              Index == undefined andalso Direction == before ->
-    %%                  lists:reverse(lists:sublist(lists:reverse(L), 1, M));
-    %%              Index == undefined ->
-    %%                  lists:sublist(L, 1, M);
-    %%              Index > Count  orelse Index < 0 ->
-    %%                  [];
-    %%              true ->
-    %%                  lists:sublist(L, Index+1, M)
-    %%          end,
-    %% if L2 == [] -> {L2, #rsm_out{count = Count}};
-    %%    true ->
-    %%            H = hd(L2),
-    %%            NewIndex = get_room_pos(H, AllRooms),
-    %%            T = lists:last(L2),
-    %%            {F, _} = H#muc_online_room.name_host,
-    %%            {Last, _} = T#muc_online_room.name_host,
-    %%            {L2,
-    %%             #rsm_out{first = F, last = Last, count = Count,
-    %%                      index = NewIndex}}
-    %% end.
+-spec get_room_disco_item({binary(), binary()}, term()) -> {ok, disco_item()} |
+                                                          {error, timeout | notfound}.
+get_room_disco_item({Name, Host}, Query) ->
+    case mnesia:dirty_read(muc_online_room, {Name, Host}) of
+       [#muc_online_room{pid = Pid}|_] ->
+           RoomJID = jid:make(Name, Host),
+           try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
+               {item, Desc} ->
+                   {ok, #disco_item{jid = RoomJID, name = Desc}};
+               false ->
+                   {error, notfound}
+           catch _:{timeout, _} ->
+                   {error, timeout};
+                 _:{noproc, _} ->
+                   {error, notfound}
+           end;
+       _ ->
+           {error, notfound}
+    end.
 
 get_subscribed_rooms(_ServerHost, Host, From) ->
     Rooms = get_vh_rooms(Host),
@@ -681,21 +723,6 @@ get_subscribed_rooms(_ServerHost, Host, From) ->
              []
       end, Rooms).
 
-%% @doc Return the position of desired room in the list of rooms.
-%% The room must exist in the list. The count starts in 0.
-%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
-get_room_pos(Desired, Rooms) ->
-    get_room_pos(Desired, Rooms, 0).
-
-get_room_pos(Desired, [HeadRoom | _], HeadPosition)
-    when Desired#muc_online_room.name_host ==
-          HeadRoom#muc_online_room.name_host ->
-    HeadPosition;
-get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
-    get_room_pos(Desired, Rooms, HeadPosition + 1).
-
-flush() -> receive _ -> flush() after 0 -> ok end.
-
 get_nick(ServerHost, Host, From) ->
     LServer = jid:nameprep(ServerHost),
     Mod = gen_mod:db_mod(LServer, ?MODULE),
@@ -782,6 +809,13 @@ get_vh_rooms(Host) ->
                          [{'==', {element, 2, '$1'}, Host}],
                          ['$_']}]).
 
+-spec get_vh_rooms_count(binary()) -> non_neg_integer().
+get_vh_rooms_count(Host) ->
+    ets:select_count(muc_online_room,
+                    ets:fun2ms(
+                      fun(#muc_online_room{name_host = {_, H}}) ->
+                              H == Host
+                      end)).
 
 clean_table_from_bad_node(Node) ->
     F = fun() ->
@@ -811,6 +845,23 @@ clean_table_from_bad_node(Node, Host) ->
         end,
     mnesia:async_dirty(F).
 
+update_tables() ->
+    try
+       case mnesia:table_info(muc_online_room, type) of
+           ordered_set -> ok;
+           _ ->
+               case mnesia:delete_table(muc_online_room) of
+                   {atomic, ok} -> ok;
+                   Err -> erlang:error(Err)
+               end
+       end
+    catch _:{aborted, {no_exists, muc_online_room}} -> ok;
+         _:{aborted, {no_exists, muc_online_room, type}} -> ok;
+         E:R ->
+           ?ERROR_MSG("failed to update mnesia table '~s': ~p",
+                      [muc_online_room, {E, R}])
+    end.
+
 opts_to_binary(Opts) ->
     lists:map(
       fun({title, Title}) ->
index 060ac2bcddeee562fbc4fbbe92fdb7f188e4fa07..5b000548a131ab6fc22a1ed7c103548a7ecc1679 100644 (file)
@@ -504,8 +504,7 @@ handle_event(_Event, StateName, StateData) ->
     {next_state, StateName, StateData}.
 
 handle_sync_event({get_disco_item, Filter, JID, Lang}, _From, StateName, StateData) ->
-    Len = ?DICT:fold(fun(_, _, Acc) -> Acc + 1 end, 0,
-                    StateData#state.users),
+    Len = ?DICT:size(StateData#state.users),
     Reply = case (Filter == all) or (Filter == Len) or ((Filter /= 0) and (Len /= 0)) of
        true ->
            get_roomdesc_reply(JID, StateData,