Implement database backend interface for MUC, BOSH and auth_anonyous

This commit is contained in:
Evgeniy Khramtsov 2017-01-13 12:03:39 +03:00
parent 31491ebe16
commit 0baaad30b1
22 changed files with 794 additions and 533 deletions

View File

@ -0,0 +1,6 @@
-type local_hint() :: undefined | integer() | {apply, atom(), atom()}.
-record(route, {domain :: binary(),
server_host :: binary(),
pid :: undefined | pid(),
local_hint :: local_hint()}).

View File

@ -23,11 +23,15 @@
{'_', binary()},
opts = [] :: list() | '_'}).
-record(muc_online_room,
{name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1' |
{'_', binary()} | '_',
pid = self() :: pid() | '$2' | '_' | '$1'}).
-record(muc_registered,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
nick = <<"">> :: binary()}).
-record(muc_online_room,
{name_host :: {binary(), binary()} | '$1' | {'_', binary()} | '_',
pid :: pid() | '$2' | '_' | '$1'}).
-record(muc_online_users, {us :: {binary(), binary()},
resource :: binary() | '_',
room :: binary() | '_' | '$1',
host :: binary() | '_' | '$2'}).

View File

@ -120,10 +120,3 @@
room_shaper = none :: shaper:shaper(),
room_queue = queue:new() :: ?TQUEUE
}).
-record(muc_online_users, {us = {<<>>, <<>>} :: {binary(), binary()},
resource = <<>> :: binary() | '_',
room = <<>> :: binary() | '_' | '$1',
host = <<>> :: binary() | '_' | '$2'}).
-type muc_online_users() :: #muc_online_users{}.

View File

@ -343,7 +343,7 @@ send_service_message_all_mucs(Subject, AnnouncementText) ->
fun(ServerHost) ->
MUCHost = gen_mod:get_module_opt_host(
ServerHost, mod_muc, <<"conference.@HOST@">>),
mod_muc:broadcast_service_message(MUCHost, Message)
mod_muc:broadcast_service_message(ServerHost, MUCHost, Message)
end,
?MYHOSTS).

View File

@ -52,17 +52,7 @@
-include("logger.hrl").
-include("jid.hrl").
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode
ejabberd_mnesia:create(?MODULE, anonymous, [{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, anonymous)}]),
%% The hooks are needed to add / remove users from the anonymous tables
ejabberd_hooks:add(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:add(sm_remove_connection_hook, Host,
@ -119,56 +109,33 @@ allow_multiple_connections(Host) ->
fun(V) when is_boolean(V) -> V end,
false).
%% Check if user exist in the anonymus database
anonymous_user_exist(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({anonymous, US}) of
[] ->
false;
[_H|_T] ->
true
end.
%% Remove connection from Mnesia tables
remove_connection(SID, LUser, LServer) ->
US = {LUser, LServer},
F = fun () -> mnesia:delete_object({anonymous, US, SID})
end,
mnesia:transaction(F).
lists:any(
fun({_LResource, Info}) ->
proplists:get_value(auth_module, Info) == ?MODULE
end, ejabberd_sm:get_user_info(User, Server)).
%% Register connection
-spec register_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
register_connection(SID,
register_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = proplists:get_value(auth_module, Info, undefined),
case AuthModule == (?MODULE) of
true ->
ejabberd_hooks:run(register_user, LServer,
[LUser, LServer]),
US = {LUser, LServer},
mnesia:sync_dirty(fun () ->
mnesia:write(#anonymous{us = US,
sid = SID})
end);
false -> ok
case proplists:get_value(auth_module, Info) of
?MODULE ->
ejabberd_hooks:run(register_user, LServer, [LUser, LServer]);
false ->
ok
end.
%% Remove an anonymous user from the anonymous users table
-spec unregister_connection(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> any().
unregister_connection(SID,
#jid{luser = LUser, lserver = LServer}, _) ->
purge_hook(anonymous_user_exist(LUser, LServer), LUser,
LServer),
remove_connection(SID, LUser, LServer).
%% Launch the hook to purge user data only for anonymous users
purge_hook(false, _LUser, _LServer) ->
ok;
purge_hook(true, LUser, LServer) ->
ejabberd_hooks:run(anonymous_purge_hook, LServer,
[LUser, LServer]).
unregister_connection(_SID,
#jid{luser = LUser, lserver = LServer}, Info) ->
case proplists:get_value(auth_module, Info) of
?MODULE ->
ejabberd_hooks:run(remove_user, LServer, [LUser, LServer]);
_ ->
ok
end.
%% ---------------------------------
%% Specific anonymous auth functions
@ -258,8 +225,6 @@ get_password_s(User, Server) ->
Password
end.
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
is_user_exists(User, Server) ->
anonymous_user_exist(User, Server).

View File

@ -64,6 +64,7 @@
user_resources/2,
kick_user/2,
get_session_pid/3,
get_user_info/2,
get_user_info/3,
get_user_ip/3,
get_max_user_sessions/2,
@ -215,6 +216,17 @@ get_user_ip(User, Server, Resource) ->
proplists:get_value(ip, Session#session.info)
end.
-spec get_user_info(binary(), binary()) -> [{binary(), info()}].
get_user_info(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
[{LResource, [{node, node(Pid)}|Info]}
|| #session{usr = {_, _, LResource},
info = Info,
sid = {_, Pid}} <- clean_session_list(Ss)].
-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
get_user_info(User, Server, Resource) ->
@ -228,9 +240,7 @@ get_user_info(User, Server, Resource) ->
Ss ->
Session = lists:max(Ss),
Node = node(element(2, Session#session.sid)),
Conn = proplists:get_value(conn, Session#session.info),
IP = proplists:get_value(ip, Session#session.info),
[{node, Node}, {conn, Conn}, {ip, IP}]
[{node, Node}|Session#session.info]
end.
-spec set_presence(sid(), binary(), binary(), binary(),

View File

@ -31,12 +31,13 @@
-export([start/0, start_module/2, start_module/3,
stop_module/2, stop_module_keep_config/2, get_opt/3,
get_opt/4, get_opt_host/3, db_type/2, db_type/3,
get_opt/4, get_opt_host/3, opt_type/1,
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2,
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
opt_type/1, db_mod/2, db_mod/3]).
db_mod/2, db_mod/3, ram_db_mod/2, ram_db_mod/3,
db_type/2, db_type/3, ram_db_type/2, ram_db_type/3]).
%%-export([behaviour_info/1]).
@ -424,6 +425,43 @@ db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
db_mod(Host, Opts, Module) when is_list(Opts) ->
db_mod(db_type(Host, Opts, Module), Module).
-spec ram_db_type(binary() | global, module()) -> db_type();
(opts(), module()) -> db_type().
ram_db_type(Opts, Module) when is_list(Opts) ->
ram_db_type(global, Opts, Module);
ram_db_type(Host, Module) when is_atom(Module) ->
case catch Module:mod_opt_type(ram_db_type) of
F when is_function(F) ->
case get_module_opt(Host, Module, ram_db_type, F) of
undefined -> ejabberd_config:default_ram_db(Host, Module);
Type -> Type
end;
_ ->
undefined
end.
-spec ram_db_type(binary(), opts(), module()) -> db_type().
ram_db_type(Host, Opts, Module) ->
case catch Module:mod_opt_type(ram_db_type) of
F when is_function(F) ->
case get_opt(ram_db_type, Opts, F) of
undefined -> ejabberd_config:default_ram_db(Host, Module);
Type -> Type
end;
_ ->
undefined
end.
-spec ram_db_mod(binary() | global | db_type(), module()) -> module().
ram_db_mod(Type, Module) when is_atom(Type), Type /= global ->
list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
ram_db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
ram_db_mod(ram_db_type(Host, Module), Module).
-spec ram_db_mod(binary() | global, opts(), module()) -> module().
ram_db_mod(Host, Opts, Module) when is_list(Opts) ->
ram_db_mod(ram_db_type(Host, Opts, Module), Module).
-spec loaded_modules(binary()) -> [atom()].
loaded_modules(Host) ->

View File

@ -30,31 +30,25 @@
%%-define(ejabberd_debug, true).
-behaviour(gen_server).
-behaviour(gen_mod).
-export([start_link/0]).
-export([start/2, stop/1, process/2, open_session/2,
close_session/1, find_session/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
depends/2, mod_opt_type/1]).
-export([depends/2, mod_opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("jlib.hrl").
-include("ejabberd_http.hrl").
-include("bosh.hrl").
-record(bosh, {sid = <<"">> :: binary() | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
pid = self() :: pid() | '$1'}).
-record(state, {}).
-callback init() -> any().
-callback open_session(binary(), pid()) -> any().
-callback close_session(binary()) -> any().
-callback find_session(binary()) -> {ok, pid()} | error.
%%%----------------------------------------------------------------------
%%% API
@ -114,137 +108,35 @@ get_human_html_xmlel() ->
"client that supports it.">>}]}]}]}.
open_session(SID, Pid) ->
Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid},
lists:foreach(
fun(Node) when Node == node() ->
gen_server:call(?MODULE, {write, Session});
(Node) ->
cluster_send({?MODULE, Node}, {write, Session})
end, ejabberd_cluster:get_nodes()).
Mod = gen_mod:ram_db_mod(global, ?MODULE),
Mod:open_session(SID, Pid).
close_session(SID) ->
case mnesia:dirty_read(bosh, SID) of
[Session] ->
lists:foreach(
fun(Node) when Node == node() ->
gen_server:call(?MODULE, {delete, Session});
(Node) ->
cluster_send({?MODULE, Node}, {delete, Session})
end, ejabberd_cluster:get_nodes());
[] ->
ok
end.
write_session(#bosh{pid = Pid1, sid = SID, timestamp = T1} = S1) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid2, timestamp = T2} = S2] ->
if Pid1 == Pid2 ->
mnesia:dirty_write(S1);
T1 < T2 ->
cluster_send(Pid2, replaced),
mnesia:dirty_write(S1);
true ->
cluster_send(Pid1, replaced),
mnesia:dirty_write(S2)
end;
[] ->
mnesia:dirty_write(S1)
end.
delete_session(#bosh{sid = SID, pid = Pid1}) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid2}] ->
if Pid1 == Pid2 ->
mnesia:dirty_delete(bosh, SID);
true ->
ok
end;
[] ->
ok
end.
Mod = gen_mod:ram_db_mod(global, ?MODULE),
Mod:close_session(SID).
find_session(SID) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid}] ->
{ok, Pid};
[] ->
error
end.
Mod = gen_mod:ram_db_mod(global, ?MODULE),
Mod:find_session(SID).
start(Host, Opts) ->
setup_database(),
start_jiffy(Opts),
TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME),
TmpSupSpec = {TmpSup,
{ejabberd_tmp_sup, start_link, [TmpSup, ejabberd_bosh]},
permanent, infinity, supervisor, [ejabberd_tmp_sup]},
ProcSpec = {?MODULE,
{?MODULE, start_link, []},
transient, 2000, worker, [?MODULE]},
case supervisor:start_child(ejabberd_sup, ProcSpec) of
{ok, _} ->
supervisor:start_child(ejabberd_sup, TmpSupSpec);
{error, {already_started, _}} ->
supervisor:start_child(ejabberd_sup, TmpSupSpec);
Err ->
Err
end.
supervisor:start_child(ejabberd_sup, TmpSupSpec),
Mod = gen_mod:ram_db_mod(global, ?MODULE),
Mod:init().
stop(Host) ->
TmpSup = gen_mod:get_module_proc(Host, ?PROCNAME),
supervisor:terminate_child(ejabberd_sup, TmpSup),
supervisor:delete_child(ejabberd_sup, TmpSup).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
{ok, #state{}}.
handle_call({write, Session}, _From, State) ->
Res = write_session(Session),
{reply, Res, State};
handle_call({delete, Session}, _From, State) ->
Res = delete_session(Session),
{reply, Res, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({write, Session}, State) ->
write_session(Session),
{noreply, State};
handle_info({delete, Session}, State) ->
delete_session(Session),
{noreply, State};
handle_info(_Info, State) ->
?ERROR_MSG("got unexpected info: ~p", [_Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
setup_database() ->
case catch mnesia:table_info(bosh, attributes) of
[sid, pid] ->
mnesia:delete_table(bosh);
_ ->
ok
end,
ejabberd_mnesia:create(?MODULE, bosh,
[{ram_copies, [node()]}, {local_content, true},
{attributes, record_info(fields, bosh)}]),
mnesia:add_table_copy(bosh, node(), ram_copies).
start_jiffy(Opts) ->
case gen_mod:get_opt(json, Opts,
fun(false) -> false;
@ -272,9 +164,6 @@ get_type(Hdrs) ->
xml
end.
cluster_send(NodePid, Msg) ->
erlang:send(NodePid, Msg, [noconnect, nosuspend]).
depends(_Host, _Opts) ->
[].
@ -292,5 +181,7 @@ mod_opt_type(max_pause) ->
fun (I) when is_integer(I), I > 0 -> I end;
mod_opt_type(prebind) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(ram_db_type) ->
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) ->
[json, max_concat, max_inactivity, max_pause, prebind].
[json, max_concat, max_inactivity, max_pause, prebind, ram_db_type].

163
src/mod_bosh_mnesia.erl Normal file
View File

@ -0,0 +1,163 @@
%%%-------------------------------------------------------------------
%%% Created : 12 Jan 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(mod_bosh_mnesia).
-behaviour(gen_server).
-behaviour(mod_bosh).
%% mod_bosh API
-export([init/0, open_session/2, close_session/1, find_session/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("logger.hrl").
-record(bosh, {sid = <<"">> :: binary() | '_',
timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_',
pid = self() :: pid() | '$1'}).
-record(state, {}).
%%%===================================================================
%%% API
%%%===================================================================
init() ->
case gen_server:start_link({local, ?MODULE}, ?MODULE, [], []) of
{ok, _Pid} ->
ok;
Err ->
Err
end.
open_session(SID, Pid) ->
Session = #bosh{sid = SID, timestamp = p1_time_compat:timestamp(), pid = Pid},
lists:foreach(
fun(Node) when Node == node() ->
gen_server:call(?MODULE, {write, Session});
(Node) ->
cluster_send({?MODULE, Node}, {write, Session})
end, ejabberd_cluster:get_nodes()).
close_session(SID) ->
case mnesia:dirty_read(bosh, SID) of
[Session] ->
lists:foreach(
fun(Node) when Node == node() ->
gen_server:call(?MODULE, {delete, Session});
(Node) ->
cluster_send({?MODULE, Node}, {delete, Session})
end, ejabberd_cluster:get_nodes());
[] ->
ok
end.
find_session(SID) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid}] ->
{ok, Pid};
[] ->
error
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
setup_database(),
{ok, #state{}}.
handle_call({write, Session}, _From, State) ->
Res = write_session(Session),
{reply, Res, State};
handle_call({delete, Session}, _From, State) ->
Res = delete_session(Session),
{reply, Res, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({write, Session}, State) ->
write_session(Session),
{noreply, State};
handle_info({delete, Session}, State) ->
delete_session(Session),
{noreply, State};
handle_info(_Info, State) ->
?ERROR_MSG("got unexpected info: ~p", [_Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
write_session(#bosh{pid = Pid1, sid = SID, timestamp = T1} = S1) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid2, timestamp = T2} = S2] ->
if Pid1 == Pid2 ->
mnesia:dirty_write(S1);
T1 < T2 ->
cluster_send(Pid2, replaced),
mnesia:dirty_write(S1);
true ->
cluster_send(Pid1, replaced),
mnesia:dirty_write(S2)
end;
[] ->
mnesia:dirty_write(S1)
end.
delete_session(#bosh{sid = SID, pid = Pid1}) ->
case mnesia:dirty_read(bosh, SID) of
[#bosh{pid = Pid2}] ->
if Pid1 == Pid2 ->
mnesia:dirty_delete(bosh, SID);
true ->
ok
end;
[] ->
ok
end.
cluster_send(NodePid, Msg) ->
erlang:send(NodePid, Msg, [noconnect, nosuspend]).
setup_database() ->
case catch mnesia:table_info(bosh, attributes) of
[sid, pid] ->
mnesia:delete_table(bosh);
_ ->
ok
end,
ejabberd_mnesia:create(?MODULE, bosh,
[{ram_copies, [node()]}, {local_content, true},
{attributes, record_info(fields, bosh)}]),
mnesia:add_table_copy(bosh, node(), ram_copies).

View File

@ -139,8 +139,6 @@ start(ServerHost, Opts) ->
true) of
true ->
ejabberd_hooks:add(remove_user, ServerHost, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE,
remove_user, 50);
false ->
ok
@ -162,8 +160,6 @@ stop(ServerHost) ->
true) of
true ->
ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE,
remove_user, 50);
false ->
ok

View File

@ -104,8 +104,6 @@ start(Host, Opts) ->
get_room_config, 50),
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
set_room_option, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
case gen_mod:get_opt(assume_mam_usage, Opts,
fun(B) when is_boolean(B) -> B end, false) of
true ->
@ -154,8 +152,6 @@ stop(Host) ->
get_room_config, 50),
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
set_room_option, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
case gen_mod:get_module_opt(Host, ?MODULE, assume_mam_usage,
fun(B) when is_boolean(B) -> B end, false) of
true ->

View File

@ -49,12 +49,20 @@
process_register/1,
process_muc_unique/1,
process_mucsub/1,
broadcast_service_message/2,
broadcast_service_message/3,
export/1,
import_info/0,
import/5,
import_start/2,
opts_to_binary/1,
find_online_room/2,
register_online_room/3,
get_online_rooms/1,
count_online_rooms/1,
register_online_user/4,
unregister_online_user/4,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4]).
-export([init/1, handle_call/3, handle_cast/2,
@ -63,7 +71,6 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
-include("mod_muc.hrl").
@ -88,6 +95,17 @@
-callback get_rooms(binary(), binary()) -> [#muc_room{}].
-callback get_nick(binary(), binary(), jid()) -> binary() | error.
-callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}.
-callback register_online_room(binary(), binary(), pid()) -> any().
-callback unregister_online_room(binary(), binary(), pid()) -> any().
-callback find_online_room(binary(), binary()) -> {ok, pid()} | error.
-callback get_online_rooms(binary(), undefined | rsm_set()) -> [{binary(), binary(), pid()}].
-callback count_online_rooms(binary()) -> non_neg_integer().
-callback rsm_supported() -> boolean().
-callback register_online_user(ljid(), binary(), binary()) -> any().
-callback unregister_online_user(ljid(), binary(), binary()) -> any().
-callback count_online_rooms_by_user(binary(), binary()) -> non_neg_integer().
-callback get_online_rooms_by_user(binary(), binary()) -> [{binary(), binary()}].
-callback handle_event(term()) -> any().
%%====================================================================
%% API
@ -114,16 +132,17 @@ depends(_Host, _Opts) ->
[{mod_mam, soft}].
shutdown_rooms(Host) ->
RMod = gen_mod:ram_db_mod(Host, ?MODULE),
MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
<<"conference.@HOST@">>),
Rooms = mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1',
pid = '$2'},
[{'==', {element, 2, '$1'}, MyHost},
{'==', {node, '$2'}, node()}],
['$2']}]),
[Pid ! shutdown || Pid <- Rooms],
Rooms.
Rooms = RMod:get_online_rooms(MyHost, undefined),
lists:filter(
fun({_, _, Pid}) when node(Pid) == node() ->
Pid ! shutdown,
true;
(_) ->
false
end, Rooms).
%% This function is called by a room in three situations:
%% A) The owner of the room destroyed it
@ -165,6 +184,48 @@ can_use_nick(ServerHost, Host, JID, Nick) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:can_use_nick(LServer, Host, JID, Nick).
-spec find_online_room(binary(), binary()) -> {ok, pid()} | error.
find_online_room(Room, Host) ->
ServerHost = ejabberd_router:host_of_route(Host),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:find_online_room(Room, Host).
-spec register_online_room(binary(), binary(), pid()) -> any().
register_online_room(Room, Host, Pid) ->
ServerHost = ejabberd_router:host_of_route(Host),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(Room, Host, Pid).
-spec get_online_rooms(binary()) -> [{binary(), binary(), pid()}].
get_online_rooms(Host) ->
ServerHost = ejabberd_router:host_of_route(Host),
get_online_rooms(ServerHost, Host).
-spec count_online_rooms(binary()) -> non_neg_integer().
count_online_rooms(Host) ->
ServerHost = ejabberd_router:host_of_route(Host),
count_online_rooms(ServerHost, Host).
-spec register_online_user(binary(), ljid(), binary(), binary()) -> any().
register_online_user(ServerHost, LJID, Name, Host) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_user(LJID, Name, Host).
-spec unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
unregister_online_user(ServerHost, LJID, Name, Host) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:unregister_online_user(LJID, Name, Host).
-spec count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
count_online_rooms_by_user(ServerHost, LUser, LServer) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:count_online_rooms_by_user(LUser, LServer).
-spec get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
get_online_rooms_by_user(ServerHost, LUser, LServer) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:get_online_rooms_by_user(LUser, LServer).
%%====================================================================
%% gen_server callbacks
%%====================================================================
@ -175,16 +236,9 @@ init([Host, Opts]) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
Mod:init(Host, [{host, MyHost}|Opts]),
update_tables(),
ejabberd_mnesia:create(?MODULE, 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}]),
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system),
RMod:init(Host, [{host, MyHost}|Opts]),
Access = gen_mod:get_opt(access, Opts,
fun acl:access_rules_validator/1, all),
AccessCreate = gen_mod:get_opt(access_create, Opts,
@ -298,7 +352,8 @@ handle_call({create, Room, From, Nick, Opts}, _From,
Room, HistorySize,
RoomShaper, From,
Nick, NewOpts),
register_room(Host, Room, Pid),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(Room, Host, Pid),
{reply, ok, State}.
handle_cast(_Msg, State) -> {noreply, State}.
@ -317,18 +372,15 @@ handle_info({route, From, To, Packet},
ok
end,
{noreply, State};
handle_info({room_destroyed, RoomHost, Pid}, State) ->
F = fun () ->
mnesia:delete_object(#muc_online_room{name_host =
RoomHost,
pid = Pid})
end,
mnesia:transaction(F),
handle_info({room_destroyed, {Room, Host}, Pid}, State) ->
ServerHost = State#state.server_host,
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:unregister_online_room(Room, Host, Pid),
{noreply, State};
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
clean_table_from_bad_node(Node),
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
handle_info(Event, #state{server_host = LServer} = State) ->
RMod = gen_mod:ram_db_mod(LServer, ?MODULE),
RMod:handle_event(Event),
{noreply, State}.
terminate(_Reason, #state{host = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
@ -374,7 +426,7 @@ do_route1(Host, ServerHost, Access, _HistorySize, _RoomShaper,
case acl:match_rule(ServerHost, AccessAdmin, From) of
allow ->
Msg = xmpp:get_text(Body),
broadcast_service_message(Host, Msg);
broadcast_service_message(ServerHost, Host, Msg);
deny ->
ErrText = <<"Only service administrators are allowed "
"to send service messages">>,
@ -390,8 +442,9 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts) ->
{_AccessRoute, AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
{Room, _, Nick} = jid:tolower(To),
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
case RMod:find_online_room(Room, Host) of
error ->
case is_create_request(Packet) of
true ->
case check_user_can_create_room(
@ -402,7 +455,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
Host, ServerHost, Access,
Room, HistorySize,
RoomShaper, From, Nick, DefRoomOpts),
register_room(Host, Room, Pid),
RMod:register_online_room(Room, Host, Pid),
mod_muc_room:route(Pid, From, Nick, Packet),
ok;
false ->
@ -417,8 +470,7 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
Err = xmpp:err_item_not_found(ErrText, Lang),
ejabberd_router:route_error(To, From, Packet, Err)
end;
[R] ->
Pid = R#muc_online_room.pid,
{ok, Pid} ->
?DEBUG("MUC: send to process ~p~n", [Pid]),
mod_muc_room:route(Pid, From, Nick, Packet),
ok
@ -462,15 +514,20 @@ process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
process_disco_info(#iq{type = get, to = To, lang = Lang,
sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
[ServerHost, ?MODULE, <<"">>, Lang]),
MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1];
false -> []
end,
RSMFeatures = case RMod:rsm_supported() of
true -> [?NS_RSM];
false -> []
end,
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
?NS_REGISTER, ?NS_MUC, ?NS_RSM,
?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE | MAMFeatures],
?NS_REGISTER, ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE
| RSMFeatures ++ MAMFeatures],
Identity = #identity{category = <<"conference">>,
type = <<"text">>,
name = translate:translate(Lang, <<"Chatrooms">>)},
@ -497,7 +554,8 @@ 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),
case iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM) of
case iq_disco_items(ServerHost, Host, From, Lang,
MaxRoomsDiscoItems, Node, RSM) of
{error, Err} ->
xmpp:make_error(IQ, Err);
{result, Result} ->
@ -564,20 +622,22 @@ get_rooms(ServerHost, Host) ->
load_permanent_rooms(Host, ServerHost, Access,
HistorySize, RoomShaper) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
lists:foreach(
fun(R) ->
{Room, Host} = R#muc_room.name_host,
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] ->
{ok, Pid} = mod_muc_room:start(Host,
ServerHost, Access, Room,
HistorySize, RoomShaper,
R#muc_room.opts),
register_room(Host, Room, Pid);
_ -> ok
end
end,
get_rooms(ServerHost, Host)).
{Room, Host} = R#muc_room.name_host,
case RMod:find_online_room(Room, Host) of
error ->
{ok, Pid} = mod_muc_room:start(Host,
ServerHost, Access, Room,
HistorySize, RoomShaper,
R#muc_room.opts),
RMod:register_online_room(Room, Host, Pid);
{ok, _} ->
ok
end
end,
get_rooms(ServerHost, Host)).
start_new_room(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, From,
@ -594,19 +654,12 @@ start_new_room(Host, ServerHost, Access, Room,
HistorySize, RoomShaper, Opts)
end.
register_room(Host, Room, Pid) ->
F = fun() ->
mnesia:write(#muc_online_room{name_host = {Room, Host},
pid = Pid})
end,
mnesia:transaction(F).
-spec iq_disco_items(binary(), jid(), binary(), integer(), binary(),
-spec iq_disco_items(binary(), binary(), jid(), binary(), integer(), binary(),
rsm_set() | undefined) ->
{result, disco_items()} | {error, stanza_error()}.
iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> ->
Count = get_vh_rooms_count(Host),
Count = count_online_rooms(ServerHost, Host),
Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems ->
{get_disco_item, only_non_empty, From, Lang};
Node == <<"nonemptyrooms">> ->
@ -616,7 +669,13 @@ iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
true ->
{get_disco_item, all, From, Lang}
end,
Items = get_vh_rooms(Host, Query, RSM),
Items = lists:flatmap(
fun(R) ->
case get_room_disco_item(R, Query) of
{ok, Item} -> [Item];
{error, _} -> []
end
end, get_online_rooms(ServerHost, Host, RSM)),
ResRSM = case Items of
[_|_] when RSM /= undefined ->
#disco_item{jid = #jid{luser = First}} = hd(Items),
@ -630,89 +689,30 @@ iq_disco_items(Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
undefined
end,
{result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
iq_disco_items(_Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
iq_disco_items(_ServerHost, _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}, 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}, Host, Query, 0, Max, []);
get_vh_rooms(Host, Query,
#rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
get_vh_rooms(last, {<<"">>, Host}, Host, Query, 0, Max, []);
get_vh_rooms(Host, Query, #rsm_set{max = Max}) ->
lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, Max, []));
get_vh_rooms(Host, Query, undefined) ->
lists:reverse(get_vh_rooms(first, {<<"">>, Host}, Host, Query, 0, undefined, [])).
-spec get_vh_rooms(prev | next | last | first,
{binary(), binary()}, binary(), term(),
non_neg_integer(), non_neg_integer() | undefined,
[disco_item()]) -> [disco_item()].
get_vh_rooms(_Action, _Key, _Host, _Query, Count, Max, Items) when Count >= Max ->
Items;
get_vh_rooms(Action, Key, Host, 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, Host, Query,
Count + 1, Max, [Item|Items]);
{error, _} ->
get_vh_rooms(NewAction, NewKey, Host, Query,
Count, Max, Items)
end;
NewKey ->
get_vh_rooms(NewAction, NewKey, Host, 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;
_ ->
-spec get_room_disco_item({binary(), binary(), pid()},
term()) -> {ok, disco_item()} |
{error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) ->
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.
get_subscribed_rooms(_ServerHost, Host, From) ->
Rooms = get_vh_rooms(Host),
get_subscribed_rooms(ServerHost, Host, From) ->
Rooms = get_online_rooms(ServerHost, Host),
BareFrom = jid:remove_resource(From),
lists:flatmap(
fun(#muc_online_room{name_host = {Name, _}, pid = Pid}) ->
fun({Name, _, Pid}) ->
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
true -> [jid:make(Name, Host)];
false -> []
@ -793,72 +793,28 @@ process_iq_register_set(ServerHost, Host, From,
{error, xmpp:err_not_acceptable(ErrText, Lang)}
end.
broadcast_service_message(Host, Msg) ->
-spec broadcast_service_message(binary(), binary(), message()) -> ok.
broadcast_service_message(ServerHost, Host, Msg) ->
lists:foreach(
fun(#muc_online_room{pid = Pid}) ->
gen_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, get_vh_rooms(Host)).
fun({_, _, Pid}) ->
gen_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, get_online_rooms(ServerHost, Host)).
-spec get_online_rooms(binary(), binary()) -> [{binary(), binary(), pid()}].
get_online_rooms(ServerHost, Host) ->
get_online_rooms(ServerHost, Host, undefined).
get_vh_rooms(Host) ->
mnesia:dirty_select(muc_online_room,
[{#muc_online_room{name_host = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Host}],
['$_']}]).
-spec get_online_rooms(binary(), binary(), undefined | rsm_set()) ->
[{binary(), binary(), pid()}].
get_online_rooms(ServerHost, Host, RSM) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:get_online_rooms(Host, RSM).
-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() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
clean_table_from_bad_node(Node, Host) ->
F = fun() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1',
name_host = {'_', Host},
_ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
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.
-spec count_online_rooms(binary(), binary()) -> non_neg_integer().
count_online_rooms(ServerHost, Host) ->
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:count_online_rooms(Host).
opts_to_binary(Opts) ->
lists:map(
@ -922,6 +878,7 @@ mod_opt_type(access_create) ->
mod_opt_type(access_persistent) ->
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_room_options) ->
fun (L) when is_list(L) -> L end;
mod_opt_type(history_size) ->
@ -963,7 +920,7 @@ mod_opt_type(user_presence_shaper) ->
fun (A) when is_atom(A) -> A end;
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
db_type, default_room_options, history_size, host,
db_type, ram_db_type, default_room_options, history_size, host,
max_room_desc, max_room_id, max_room_name,
max_rooms_discoitems, max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,

View File

@ -28,7 +28,6 @@
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_muc_room.hrl").
-include("mod_muc.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_commands.hrl").
@ -207,22 +206,12 @@ get_commands_spec() ->
%%%
muc_online_rooms(ServerHost) ->
MUCHost = find_host(ServerHost),
Rooms = ets:tab2list(muc_online_room),
lists:foldl(
fun(Room, Results) ->
{Roomname, Host} = Room#muc_online_room.name_host,
case MUCHost of
global ->
[<<Roomname/binary, "@", Host/binary>> | Results];
Host ->
[<<Roomname/binary, "@", Host/binary>> | Results];
_ ->
Results
end
end,
[],
Rooms).
Hosts = find_hosts(ServerHost),
lists:flatmap(
fun(Host) ->
[{<<Name/binary, "@", Host/binary>>}
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)]
end, Hosts).
muc_unregister_nick(Nick) ->
F2 = fun(N) ->
@ -237,14 +226,18 @@ muc_unregister_nick(Nick) ->
end.
get_user_rooms(LUser, LServer) ->
US = {LUser, LServer},
case catch ets:select(muc_online_users,
[{#muc_online_users{us = US, room='$1', host='$2', _ = '_'}, [], [{{'$1', '$2'}}]}])
of
Res when is_list(Res) ->
[<<R/binary, "@", H/binary>> || {R, H} <- Res];
_ -> []
end.
lists:flatmap(
fun(ServerHost) ->
case gen_mod:is_loaded(ServerHost, mod_muc) of
true ->
Rooms = mod_muc:get_online_rooms_by_user(
ServerHost, LUser, LServer),
[<<Name/binary, "@", Host/binary>>
|| {Name, Host} <- Rooms];
false ->
[]
end
end, ?MYHOSTS).
%%----------------------------
%% Ad-hoc commands
@ -274,10 +267,14 @@ web_menu_host(Acc, _Host, Lang) ->
])).
web_page_main(_, #request{path=[<<"muc">>], lang = Lang} = _Request) ->
OnlineRoomsNumber = lists:foldl(
fun(Host, Acc) ->
Acc ++ mod_muc:count_online_rooms(Host)
end, 0, find_hosts(global)),
Res = [?XCT(<<"h1">>, <<"Multi-User Chat">>),
?XCT(<<"h3">>, <<"Statistics">>),
?XAE(<<"table">>, [],
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, ets:info(muc_online_room, size)),
[?XE(<<"tbody">>, [?TDTD(<<"Total rooms">>, OnlineRoomsNumber),
?TDTD(<<"Permanent rooms">>, mnesia:table_info(muc_room, size)),
?TDTD(<<"Registered nicknames">>, mnesia:table_info(muc_registered, size))
])
@ -456,8 +453,8 @@ create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
RoomShaper = gen_mod:get_module_opt(ServerHost, mod_muc, room_shaper, fun(X) -> X end, none),
%% If the room does not exist yet in the muc_online_room
case mnesia:dirty_read(muc_online_room, {Name, Host}) of
[] ->
case mod_muc:find_online_room(Name, Host) of
error ->
%% Start the room
{ok, Pid} = mod_muc_room:start(
Host,
@ -467,19 +464,12 @@ create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
HistorySize,
RoomShaper,
RoomOpts),
{atomic, ok} = register_room(Host, Name, Pid),
mod_muc:register_online_room(Host, Name, Pid),
ok;
_ ->
{ok, _} ->
error
end.
register_room(Host, Name, Pid) ->
F = fun() ->
mnesia:write(#muc_online_room{name_host = {Name, Host},
pid = Pid})
end,
mnesia:transaction(F).
%% Create the room only in the database.
%% It is required to restart the MUC service for the room to appear.
muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
@ -492,12 +482,11 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
%% If the room has participants, they are not notified that the room was destroyed;
%% they will notice when they try to chat and receive an error that the room doesn't exist.
destroy_room(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[R] ->
Pid = R#muc_online_room.pid,
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
gen_fsm:send_all_state_event(Pid, destroy),
ok;
[] ->
error ->
error
end.
@ -602,19 +591,12 @@ muc_unused2(Action, ServerHost, Host, Last_allowed) ->
%%---------------
%% Get info
get_rooms(Host) ->
Get_room_names = fun(Room_reg, Names) ->
Pid = Room_reg#muc_online_room.pid,
case {Host, Room_reg#muc_online_room.name_host} of
{Host, {Name1, Host}} ->
[{Name1, Host, Pid} | Names];
{global, {Name1, Host1}} ->
[{Name1, Host1, Pid} | Names];
_ ->
Names
end
end,
ets:foldr(Get_room_names, [], muc_online_room).
get_rooms(ServerHost) ->
Hosts = find_hosts(ServerHost),
lists:flatmap(
fun(Host) ->
mod_muc:get_online_rooms(Host)
end, Hosts).
get_room_config(Room_pid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
@ -813,11 +795,11 @@ format_room_option(OptionString, ValueString) ->
%% @doc Get the Pid of an existing MUC room, or 'room_not_found'.
get_room_pid(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[] ->
case mod_muc:find_online_room(Name, Service) of
error ->
room_not_found;
[Room] ->
Room#muc_online_room.pid
{ok, Pid} ->
Pid
end.
%% It is required to put explicitely all the options because
@ -884,10 +866,9 @@ get_options(Config) ->
%% [{JID::string(), Domain::string(), Role::string(), Reason::string()}]
%% @doc Get the affiliations of the room Name@Service.
get_room_affiliations(Name, Service) ->
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[R] ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID of the online room, then request its state
Pid = R#muc_online_room.pid,
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
Affiliations = ?DICT:to_list(StateData#state.affiliations),
lists:map(
@ -896,7 +877,7 @@ get_room_affiliations(Name, Service) ->
({{Uname, Domain, _Res}, Aff}) when is_atom(Aff)->
{Uname, Domain, Aff, <<>>}
end, Affiliations);
[] ->
error ->
throw({error, "The room does not exist."})
end.
@ -914,14 +895,13 @@ get_room_affiliations(Name, Service) ->
%% In any other case the action will be to create the affiliation.
set_room_affiliation(Name, Service, JID, AffiliationString) ->
Affiliation = jlib:binary_to_atom(AffiliationString),
case mnesia:dirty_read(muc_online_room, {Name, Service}) of
[R] ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID for the online room so we can get the state of the room
Pid = R#muc_online_room.pid,
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:from_string(JID), affiliation, Affiliation, <<"">>}, <<"">>}),
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok;
[] ->
error ->
error
end.
@ -1045,4 +1025,28 @@ find_host(ServerHost) when is_list(ServerHost) ->
find_host(ServerHost) ->
gen_mod:get_module_opt_host(ServerHost, mod_muc, <<"conference.@HOST@">>).
find_hosts(Global) when Global == global;
Global == "global";
Global == <<"global">> ->
lists:flatmap(
fun(ServerHost) ->
case gen_mod:is_loaded(ServerHost, mod_muc) of
true ->
[gen_mod:get_module_opt_host(
ServerHost, mod_muc, <<"conference.@HOST@">>)];
false ->
[]
end
end, ?MYHOSTS);
find_hosts(ServerHost) when is_list(ServerHost) ->
find_hosts(list_to_binary(ServerHost));
find_hosts(ServerHost) ->
case gen_mod:is_loaded(ServerHost, mod_muc) of
true ->
[gen_mod:get_module_opt_host(
ServerHost, mod_muc, <<"conference.@HOST@">>)];
false ->
[]
end.
mod_opt_type(_) -> [].

View File

@ -47,7 +47,6 @@
-include("logger.hrl").
-include("xmpp.hrl").
-include("mod_muc.hrl").
-include("mod_muc_room.hrl").
-define(T(Text), translate:translate(Lang, Text)).
@ -1169,13 +1168,11 @@ get_room_occupants(RoomJIDString) ->
-spec get_room_state(binary(), binary()) -> mod_muc_room:state().
get_room_state(RoomName, MucService) ->
case mnesia:dirty_read(muc_online_room,
{RoomName, MucService})
of
[R] ->
RoomPid = R#muc_online_room.pid,
get_room_state(RoomPid);
[] -> #state{}
case mod_muc:find_online_room(RoomName, MucService) of
{ok, RoomPid} ->
get_room_state(RoomPid);
error ->
#state{}
end.
-spec get_room_state(pid()) -> mod_muc_room:state().

View File

@ -14,28 +14,55 @@
%% API
-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
-export([register_online_room/3, unregister_online_room/3, find_online_room/2,
get_online_rooms/2, count_online_rooms/1, rsm_supported/0,
register_online_user/3, unregister_online_user/3,
count_online_rooms_by_user/2, get_online_rooms_by_user/2,
handle_event/1]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
-include("jid.hrl").
-include("mod_muc.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init(_Host, Opts) ->
init(Host, Opts) ->
MyHost = proplists:get_value(host, Opts),
ejabberd_mnesia:create(?MODULE, muc_room,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_room)}]),
ejabberd_mnesia:create(?MODULE, muc_registered,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_registered)}]),
update_tables(MyHost),
mnesia:add_table_index(muc_registered, nick).
case gen_mod:db_mod(Host, Opts, mod_muc) of
?MODULE ->
ejabberd_mnesia:create(?MODULE, muc_room,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_room)}]),
ejabberd_mnesia:create(?MODULE, muc_registered,
[{disc_copies, [node()]},
{attributes,
record_info(fields, muc_registered)}]),
update_tables(MyHost),
mnesia:add_table_index(muc_registered, nick);
_ ->
ok
end,
case gen_mod:ram_db_mod(Host, Opts, mod_muc) of
?MODULE ->
update_muc_online_table(),
ejabberd_mnesia:create(?MODULE, 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}]),
clean_table_from_bad_node(node(), MyHost),
mnesia:subscribe(system);
_ ->
ok
end.
store_room(_LServer, Host, Name, Opts) ->
F = fun () ->
@ -132,6 +159,128 @@ get_affiliations(_ServerHost, _Room, _Host) ->
search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
{error, not_implemented}.
register_online_room(Room, Host, Pid) ->
F = fun() ->
mnesia:write(
#muc_online_room{name_host = {Room, Host}, pid = Pid})
end,
mnesia:transaction(F).
unregister_online_room(Room, Host, Pid) ->
F = fun () ->
mnesia:delete_object(
#muc_online_room{name_host = {Room, Host}, pid = Pid})
end,
mnesia:transaction(F).
find_online_room(Room, Host) ->
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
[] -> error;
[#muc_online_room{pid = Pid}] -> {ok, Pid}
end.
count_online_rooms(Host) ->
ets:select_count(
muc_online_room,
ets:fun2ms(
fun(#muc_online_room{name_host = {_, H}}) ->
H == Host
end)).
get_online_rooms(Host,
#rsm_set{max = Max, 'after' = After, before = undefined})
when is_binary(After), After /= <<"">> ->
lists:reverse(get_online_rooms(next, {After, Host}, Host, 0, Max, []));
get_online_rooms(Host,
#rsm_set{max = Max, 'after' = undefined, before = Before})
when is_binary(Before), Before /= <<"">> ->
get_online_rooms(prev, {Before, Host}, Host, 0, Max, []);
get_online_rooms(Host,
#rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
get_online_rooms(last, {<<"">>, Host}, Host, 0, Max, []);
get_online_rooms(Host, #rsm_set{max = Max}) ->
lists:reverse(get_online_rooms(first, {<<"">>, Host}, Host, 0, Max, []));
get_online_rooms(Host, undefined) ->
mnesia:dirty_select(
muc_online_room,
ets:fun2ms(
fun(#muc_online_room{name_host = {Name, H}, pid = Pid})
when H == Host -> {Name, Host, Pid}
end)).
-spec get_online_rooms(prev | next | last | first,
{binary(), binary()}, binary(),
non_neg_integer(), non_neg_integer() | undefined,
[{binary(), binary(), pid()}]) ->
[{binary(), binary(), pid()}].
get_online_rooms(_Action, _Key, _Host, Count, Max, Items) when Count >= Max ->
Items;
get_online_rooms(Action, Key, Host, 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;
{Room, Host} = NewKey ->
case find_online_room(Room, Host) of
{ok, Pid} ->
get_online_rooms(NewAction, NewKey, Host,
Count + 1, Max, [{Room, Host, Pid}|Items]);
{error, _} ->
get_online_rooms(NewAction, NewKey, Host,
Count, Max, Items)
end;
NewKey ->
get_online_rooms(NewAction, NewKey, Host, Count, Max, Items)
catch _:{aborted, {badarg, _}} ->
Items
end.
handle_event({mnesia_system_event, {mnesia_down, Node}}) ->
clean_table_from_bad_node(Node);
handle_event(_) ->
ok.
rsm_supported() ->
true.
register_online_user({U, S, R}, Room, Host) ->
ets:insert(muc_online_users,
#muc_online_users{us = {U, S}, resource = R,
room = Room, host = Host}).
unregister_online_user({U, S, R}, Room, Host) ->
ets:delete_object(muc_online_users,
#muc_online_users{us = {U, S}, resource = R,
room = Room, host = Host}).
count_online_rooms_by_user(U, S) ->
ets:select_count(
muc_online_users,
ets:fun2ms(
fun(#muc_online_users{us = {U1, S1}}) ->
U == U1 andalso S == S1
end)).
get_online_rooms_by_user(U, S) ->
ets:select(
muc_online_users,
ets:fun2ms(
fun(#muc_online_users{us = {U1, S1}, room = Room, host = Host})
when U == U1 andalso S == S1 -> {Room, Host}
end)).
import(_LServer, <<"muc_room">>,
[Name, RoomHost, SOpts, _TimeStamp]) ->
Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
@ -148,6 +297,34 @@ import(_LServer, <<"muc_registered">>,
%%%===================================================================
%%% Internal functions
%%%===================================================================
clean_table_from_bad_node(Node) ->
F = fun() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1', _ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
clean_table_from_bad_node(Node, Host) ->
F = fun() ->
Es = mnesia:select(
muc_online_room,
[{#muc_online_room{pid = '$1',
name_host = {'_', Host},
_ = '_'},
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
mnesia:delete_object(E)
end, Es)
end,
mnesia:async_dirty(F).
update_tables(Host) ->
update_muc_room_table(Host),
update_muc_registered_table(Host).
@ -188,3 +365,20 @@ update_muc_registered_table(_Host) ->
?INFO_MSG("Recreating muc_registered table", []),
mnesia:transform_table(muc_registered, ignore, Fields)
end.
update_muc_online_table() ->
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, erlang:get_stacktrace()}])
end.

View File

@ -14,6 +14,11 @@
%% API
-export([init/2, import/3, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]).
-export([register_online_room/3, unregister_online_room/3, find_online_room/2,
get_online_rooms/2, count_online_rooms/1, rsm_supported/0,
register_online_user/3, unregister_online_user/3,
count_online_rooms_by_user/2, get_online_rooms_by_user/2,
handle_event/1]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
@ -120,6 +125,39 @@ get_affiliations(_ServerHost, _Room, _Host) ->
search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
{error, not_implemented}.
register_online_room(_, _, _) ->
erlang:error(not_implemented).
unregister_online_room(_, _, _) ->
erlang:error(not_implemented).
find_online_room(_, _) ->
erlang:error(not_implemented).
count_online_rooms(_) ->
erlang:error(not_implemented).
get_online_rooms(_, _) ->
erlang:error(not_implemented).
handle_event(_) ->
ok.
rsm_supported() ->
false.
register_online_user(_, _, _) ->
erlang:error(not_implemented).
unregister_online_user(_, _, _) ->
erlang:error(not_implemented).
count_online_rooms_by_user(_, _) ->
erlang:error(not_implemented).
get_online_rooms_by_user(_, _) ->
erlang:error(not_implemented).
import(_LServer, <<"muc_room">>,
[Name, RoomHost, SOpts, _TimeStamp]) ->
Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),

View File

@ -1798,7 +1798,7 @@ add_new_user(From, Nick, Packet, StateData) ->
Affiliation = get_affiliation(From, StateData),
ServiceAffiliation = get_service_affiliation(From,
StateData),
NConferences = tab_count_user(From),
NConferences = tab_count_user(From, StateData),
MaxConferences =
gen_mod:get_module_opt(StateData#state.server_host,
mod_muc, max_user_conferences,
@ -3968,38 +3968,25 @@ add_to_log(Type, Data, StateData) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Users number checking
-spec tab_add_online_user(jid(), state()) -> ok.
-spec tab_add_online_user(jid(), state()) -> any().
tab_add_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:insert(muc_online_users,
#muc_online_users{us = US, resource = LResource,
room = Room, host = Host}),
ok.
ServerHost = StateData#state.server_host,
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_remove_online_user(jid(), state()) -> ok.
-spec tab_remove_online_user(jid(), state()) -> any().
tab_remove_online_user(JID, StateData) ->
{LUser, LServer, LResource} = jid:tolower(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:delete_object(muc_online_users,
#muc_online_users{us = US, resource = LResource,
room = Room, host = Host}),
ok.
ServerHost = StateData#state.server_host,
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_count_user(jid()) -> non_neg_integer().
tab_count_user(JID) ->
-spec tab_count_user(jid(), state()) -> non_neg_integer().
tab_count_user(JID, StateData) ->
ServerHost = StateData#state.server_host,
{LUser, LServer, _} = jid:tolower(JID),
US = {LUser, LServer},
case catch ets:select(muc_online_users,
[{#muc_online_users{us = US, _ = '_'}, [], [[]]}])
of
Res when is_list(Res) -> length(Res);
_ -> 0
end.
mod_muc:count_online_rooms_by_user(ServerHost, LUser, LServer).
-spec element_size(stanza()) -> non_neg_integer().
element_size(El) ->

View File

@ -17,6 +17,11 @@
-export([init/2, store_room/4, restore_room/3, forget_room/3,
can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4,
import/3, export/1]).
-export([register_online_room/3, unregister_online_room/3, find_online_room/2,
get_online_rooms/2, count_online_rooms/1, rsm_supported/0,
register_online_user/3, unregister_online_user/3,
count_online_rooms_by_user/2, get_online_rooms_by_user/2,
handle_event/1]).
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
get_affiliations/3, search_affiliation/4]).
@ -145,6 +150,39 @@ get_affiliations(_ServerHost, _Room, _Host) ->
search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
{error, not_implemented}.
register_online_room(_, _, _) ->
erlang:error(not_implemented).
unregister_online_room(_, _, _) ->
erlang:error(not_implemented).
find_online_room(_, _) ->
erlang:error(not_implemented).
count_online_rooms(_) ->
erlang:error(not_implemented).
get_online_rooms(_, _) ->
erlang:error(not_implemented).
handle_event(_) ->
ok.
rsm_supported() ->
false.
register_online_user(_, _, _) ->
erlang:error(not_implemented).
unregister_online_user(_, _, _) ->
erlang:error(not_implemented).
count_online_rooms_by_user(_, _) ->
erlang:error(not_implemented).
get_online_rooms_by_user(_, _) ->
erlang:error(not_implemented).
export(_Server) ->
[{muc_room,
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->

View File

@ -148,8 +148,6 @@ init([Host, Opts]) ->
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
ejabberd_hooks:add(remove_user, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:add(disco_sm_features, Host,
?MODULE, get_sm_features, 50),
ejabberd_hooks:add(disco_local_features, Host,
@ -208,8 +206,6 @@ terminate(_Reason, State) ->
ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE, c2s_self_presence, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, get_sm_identity, 50),

View File

@ -303,8 +303,6 @@ init([ServerHost, Opts]) ->
?MODULE, out_subscription, 50),
ejabberd_hooks:add(remove_user, ServerHost,
?MODULE, remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, ServerHost,
?MODULE, remove_user, 50),
ejabberd_hooks:add(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
@ -912,8 +910,6 @@ terminate(_Reason,
?MODULE, out_subscription, 50),
ejabberd_hooks:delete(remove_user, ServerHost,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, ServerHost,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),

View File

@ -100,8 +100,6 @@ start(Host, Opts) ->
get_jid_info, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
c2s_self_presence, 50),
ejabberd_hooks:add(c2s_post_auth_features, Host,
@ -128,8 +126,6 @@ stop(Host) ->
?MODULE, get_jid_info, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_self_presence, Host, ?MODULE,
c2s_self_presence, 50),
ejabberd_hooks:delete(c2s_post_auth_features,

View File

@ -98,8 +98,6 @@ start(Host, Opts) ->
unset_presence, 50),
ejabberd_hooks:add(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50).
@ -126,8 +124,6 @@ stop(Host) ->
?MODULE, unset_presence, 50),
ejabberd_hooks:delete(register_user, Host, ?MODULE,
register_user, 50),
ejabberd_hooks:delete(anonymous_purge_hook, Host,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user,
50).