diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 294d4f401..7dc80e462 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -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)}, diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl index 08d551585..f94dd2e49 100644 --- a/src/mod_mam_mnesia.erl +++ b/src/mod_mam_mnesia.erl @@ -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 %%%=================================================================== diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl index bcfa06708..6242de3c6 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -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 %%%=================================================================== diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 29474e511..ae3c6f9f7 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -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). diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 0dac45e8b..494a4ef3f 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -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, 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.