Merge remote-tracking branch 'processone/pr/2763'

* processone/pr/2763:
  disallow room creation if archive not empty and clear_archive_on_room_destroy is false
  check if mod_mam is loaded before calling mod_mam:is_empty_for_room
  added cmds to list and destroy empty rooms by ejabberdctl
  allow check if archive is empty for or user or room
  option to prevent archive removal on room destroy
This commit is contained in:
Holger Weiss 2019-02-20 17:01:34 +01:00
commit 9c66cc5885
5 changed files with 137 additions and 29 deletions

View File

@ -40,7 +40,8 @@
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
get_room_config/4, set_room_option/3, offline_message/1, export/1,
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2]).
mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
is_empty_for_user/2, is_empty_for_room/3, check_create_room/4]).
-include("xmpp.hrl").
-include("logger.hrl").
@ -72,6 +73,8 @@
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
-callback is_empty_for_user(binary(), binary()) -> boolean().
-callback is_empty_for_room(binary(), binary(), binary()) -> boolean().
-optional_callbacks([use_cache/1, cache_nodes/1]).
@ -113,8 +116,6 @@ start(Host, Opts) ->
disco_sm_features, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:add(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
@ -126,11 +127,22 @@ start(Host, Opts) ->
false ->
ok
end,
case gen_mod:get_opt(clear_archive_on_room_destroy, Opts) of
true ->
ejabberd_hooks:add(remove_room, Host, ?MODULE,
remove_room, 50);
false ->
ejabberd_hooks:add(check_create_room, Host, ?MODULE,
check_create_room, 50)
end,
ejabberd_commands:register_commands(get_commands_spec());
Err ->
Err
end.
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 2) of
true -> Mod:use_cache(Host);
@ -180,8 +192,6 @@ stop(Host) ->
disco_sm_features, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
remove_room, 50),
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
@ -193,6 +203,14 @@ stop(Host) ->
false ->
ok
end,
case gen_mod:get_module_opt(Host, ?MODULE, clear_archive_on_room_destroy) of
true ->
ejabberd_hooks:delete(remove_room, Host, ?MODULE,
remove_room, 50);
false ->
ejabberd_hooks:delete(check_create_room, Host, ?MODULE,
check_create_room, 50)
end,
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
@ -559,6 +577,24 @@ export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-spec is_empty_for_user(binary(), binary()) -> boolean().
is_empty_for_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:is_empty_for_user(LUser, LServer).
-spec is_empty_for_room(binary(), binary(), binary()) -> boolean().
is_empty_for_room(LServer, Name, Host) ->
LName = jid:nodeprep(Name),
LHost = jid:nameprep(Host),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:is_empty_for_room(LServer, LName, LHost).
-spec check_create_room(boolean(), binary(), binary(), binary()) -> boolean().
check_create_room(Acc, ServerHost, RoomID, Host) ->
Acc and is_empty_for_room(ServerHost, RoomID, Host).
%%%===================================================================
%%% Internal functions
%%%===================================================================
@ -1211,6 +1247,8 @@ mod_opt_type(default) ->
(roster) -> roster
end;
mod_opt_type(request_activates_archiving) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(clear_archive_on_room_destroy) ->
fun (B) when is_boolean(B) -> B end.
mod_options(Host) ->
@ -1218,6 +1256,7 @@ mod_options(Host) ->
{default, never},
{request_activates_archiving, false},
{compress_xml, false},
{clear_archive_on_room_destroy, true},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},

View File

@ -28,7 +28,8 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3]).
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@ -198,6 +199,13 @@ select(_LServer, JidRequestor,
erlang:garbage_collect(),
Result.
is_empty_for_user(LUser, LServer) ->
not lists:member({LUser, LServer},
mnesia:dirty_all_keys(archive_msg)).
is_empty_for_room(_LServer, LName, LHost) ->
is_empty_for_user(LName, LHost).
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -30,7 +30,8 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3]).
extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3,
is_empty_for_user/2, is_empty_for_room/3]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@ -264,6 +265,23 @@ export(_Server) ->
[]
end}].
is_empty_for_user(LUser, LServer) ->
case ejabberd_sql:sql_query(
LServer,
?SQL("select @(count(*))d from archive"
" where username=%(LUser)s and %(LServer)H")) of
{selected, [{Res}]} ->
case Res of
0 -> true;
_ -> false
end;
_ -> false
end.
is_empty_for_room(LServer, LName, LHost) ->
LUser = jid:encode({LName, LHost, <<>>}),
is_empty_for_user(LUser, LServer).
%%%===================================================================
%%% Internal functions
%%%===================================================================

View File

@ -65,7 +65,8 @@
iq_set_register_info/5,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4]).
can_use_nick/4,
check_create_room/4]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@ -112,10 +113,14 @@
%% API
%%====================================================================
start(Host, Opts) ->
ejabberd_hooks:add(check_create_room, Host, ?MODULE,
check_create_room, 50),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
Rooms = shutdown_rooms(Host),
ejabberd_hooks:delete(check_create_room, Host, ?MODULE,
check_create_room, 50),
gen_mod:stop_child(?MODULE, Host),
{wait, Rooms}.
@ -442,7 +447,9 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
true ->
case check_user_can_create_room(
ServerHost, AccessCreate, From, Room) and
check_create_roomid(ServerHost, Room) of
ejabberd_hooks:run_fold(check_create_room,
ServerHost, true,
[ServerHost, Room, Host]) of
true ->
{ok, Pid} = start_new_room(
Host, ServerHost, Access,
@ -611,9 +618,10 @@ check_user_can_create_room(ServerHost, AccessCreate,
_ -> false
end.
check_create_roomid(ServerHost, RoomID) ->
check_create_room(Acc, ServerHost, RoomID, _Host) ->
Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id),
Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id),
Acc and
(byte_size(RoomID) =< Max) and
(re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).

View File

@ -34,6 +34,7 @@
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
rooms_empty_list/1, rooms_empty_destroy/1,
get_user_rooms/2, get_room_occupants/2,
get_room_occupants_number/2, send_direct_invitation/5,
change_room_option/4, get_room_options/2,
@ -194,6 +195,25 @@ get_commands_spec() ->
args = [{host, binary}, {days, integer}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = rooms_empty_list, tags = [muc],
desc = "List the rooms that have no messages in archive",
module = ?MODULE, function = rooms_empty_list,
args_desc = ["Server host"],
args_example = ["example.com"],
result_desc = "List of empty rooms",
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
args = [{host, binary}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
desc = "Destroy the rooms that have no messages in archive",
module = ?MODULE, function = rooms_empty_destroy,
args_desc = ["Server host"],
args_example = ["example.com"],
result_desc = "List of empty rooms that have been destroyed",
result_example = ["room1@muc.example.com", "room2@muc.example.com"],
args = [{host, binary}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = get_user_rooms, tags = [muc],
desc = "Get the list of rooms where this user is occupant",
module = ?MODULE, function = get_user_rooms,
@ -718,35 +738,41 @@ create_rooms_file(Filename) ->
ok.
%%----------------------------
%% List/Delete Unused Rooms
%%----------------------------
%%---------------------------------
%% List/Delete Unused/Empty Rooms
%%---------------------------------
%%---------------
%% Control
rooms_unused_list(ServerHost, Days) ->
rooms_unused_report(list, ServerHost, Days).
rooms_report(unused, list, ServerHost, Days).
rooms_unused_destroy(ServerHost, Days) ->
rooms_unused_report(destroy, ServerHost, Days).
rooms_report(unused, destroy, ServerHost, Days).
rooms_unused_report(Action, ServerHost, Days) ->
{NA, NP, RP} = muc_unused(Action, ServerHost, Days),
io:format("Unused rooms: ~p out of ~p~n", [NP, NA]),
rooms_empty_list(ServerHost) ->
rooms_report(empty, list, ServerHost, 0).
rooms_empty_destroy(ServerHost) ->
rooms_report(empty, destroy, ServerHost, 0).
rooms_report(Method, Action, ServerHost, Days) ->
{NA, NP, RP} = muc_unused(Method, Action, ServerHost, Days),
io:format("rooms ~s: ~p out of ~p~n", [Method, NP, NA]),
[<<R/binary, "@", H/binary>> || {R, H, _P} <- RP].
muc_unused(Action, ServerHost, Last_allowed) ->
muc_unused(Method, Action, ServerHost, Last_allowed) ->
%% Get all required info about all existing rooms
Rooms_all = get_rooms(ServerHost),
%% Decide which ones pass the requirements
Rooms_pass = decide_rooms(Rooms_all, ServerHost, Last_allowed),
Rooms_pass = decide_rooms(Method, Rooms_all, ServerHost, Last_allowed),
Num_rooms_all = length(Rooms_all),
Num_rooms_pass = length(Rooms_pass),
%% Perform the desired action for matching rooms
act_on_rooms(Action, Rooms_pass, ServerHost),
act_on_rooms(Method, Action, Rooms_pass, ServerHost),
{Num_rooms_all, Num_rooms_pass, Rooms_pass}.
@ -771,11 +797,11 @@ get_room_state(Room_pid) ->
%%---------------
%% Decide
decide_rooms(Rooms, ServerHost, Last_allowed) ->
Decide = fun(R) -> decide_room(R, ServerHost, Last_allowed) end,
decide_rooms(Method, Rooms, ServerHost, Last_allowed) ->
Decide = fun(R) -> decide_room(Method, R, ServerHost, Last_allowed) end,
lists:filter(Decide, Rooms).
decide_room({_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
decide_room(unused, {_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
C = get_room_config(Room_pid),
Persistent = C#config.persistent,
@ -811,6 +837,13 @@ decide_room({_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
true;
_ ->
false
end;
decide_room(empty, {Room_name, Host, _Room_pid}, ServerHost, _Last_allowed) ->
case gen_mod:is_loaded(ServerHost, mod_mam) of
true ->
mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
_ ->
false
end.
seconds_to_days(S) ->
@ -819,7 +852,7 @@ seconds_to_days(S) ->
%%---------------
%% Act
act_on_rooms(Action, Rooms, ServerHost) ->
act_on_rooms(Method, Action, Rooms, ServerHost) ->
ServerHosts = [ {A, find_host(A)} || A <- ejabberd_config:get_myhosts() ],
Delete = fun({_N, H, _Pid} = Room) ->
SH = case ServerHost of
@ -827,7 +860,7 @@ act_on_rooms(Action, Rooms, ServerHost) ->
O -> O
end,
act_on_room(Action, Room, SH)
act_on_room(Method, Action, Room, SH)
end,
lists:foreach(Delete, Rooms).
@ -835,13 +868,15 @@ find_serverhost(Host, ServerHosts) ->
{value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts),
ServerHost.
act_on_room(destroy, {N, H, Pid}, SH) ->
act_on_room(Method, destroy, {N, H, Pid}, SH) ->
Message = iolist_to_binary(io_lib:format(
<<"Room destroyed by rooms_~s_destroy.">>, [Method])),
p1_fsm:send_all_state_event(
Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
Pid, {destroy, Message}),
mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N);
act_on_room(list, _, _) ->
act_on_room(_Method, list, _, _) ->
ok.