-include("ejabberd.hrl").
-include("logger.hrl").
-
+-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
-include("mod_muc.hrl").
<<"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}]),
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)).
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),
[]
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),
[{'==', {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() ->
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}) ->