Fix RSM for conference disco#items

This commit is contained in:
Evgeniy Khramtsov 2016-11-20 18:08:49 +03:00
parent 13c6039700
commit 049a6d97f1
2 changed files with 138 additions and 88 deletions

View 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.
-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}) ->

View 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,