From 049a6d97f1216b831e7cc3b1ebaf85564082a4d3 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Sun, 20 Nov 2016 18:08:49 +0300 Subject: [PATCH] Fix RSM for conference disco#items --- src/mod_muc.erl | 223 ++++++++++++++++++++++++++----------------- src/mod_muc_room.erl | 3 +- 2 files changed, 138 insertions(+), 88 deletions(-) diff --git a/src/mod_muc.erl b/src/mod_muc.erl index ea8bff5e3..9b475f69d 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -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. +-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)}. -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 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, [])). -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_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. + +-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}) -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 060ac2bcd..5b000548a 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -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,